Vue3 笔记

我睡着的时候不困唉大约 9 分钟前端框架基础语法vue3

Vue3 笔记

setup 函数

setup 函数是 vue3 的核心 也是所有函数的入口 这个函数传入两个参数,分别为 propscontextprops 为 父组件传递的参数,而 contextattrs, emit, slotsprops 是响应式的,但是不可以 使用 解构或者展开,这样会 导致响应式 失败(原因会在第二点讲) context 可以使用解构,slots 相当于以前的 $slotsemit 相当于以前的 $emitattrs 则是在组件标签上的内容 propsattrs 的区别: 如下面代码 所示,name 属性 在 props 参数对象中定义了的,就会进入 props 里面, 否则 其他在组件标签上的内容会进入 attrs 里面

<template>
  <div class="hello">
    <span>{{ msg }}</span>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup(props, { attrs, emit, slots }) {
    console.log(props, attrs, emit, slots)
  }
}
</script>

reactive、ref 与 toRefs

refreactive 是构建响应式对象的 函数,但是 也有着显著的区别 在使用 ref 定义的值之后,必须使用 xxx.value 才能获得对应的值,而 reactive 则不需要 reactive 响应化的数据不能使用解构或者展开,要不然会失去响应 那么为什么 propsreactive 不能解构或者展开,而 ref 之后的数据又必须使用 .value 访问 呢? 无论是 Object.defineProperty 还是 proxy,只能对 对象数据保持 响应式 如果是一个 基本属性的话,那改变了就是改变了,vue 内部是不能监听到他的变化的 所以 在 ref 中,一个 基本类型 变成了对象,而且使用 value 来获取 当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性 当 reactive props 结构的结果为基本类型 ,那么同样也是失去了监听的效果 但是当 reactive 内部的值是一个对象的话,那么解构或者展开依旧保持响应,这是内部处理了深度响应结果 一、reactive、ref

<template>
  <div class="hello">
    <button @click="inc">点击</button>
    <p>{{ state.username }}</p>
    <p>{{ state.password }}</p>
    <p>{{ count }}</p>
  </div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
  name: '',
  setup() {
    const count = ref(0)
    const inc = () => {
      count.value++
    }
    const state = reactive({
      username: 'jack',
      password: 'rose'
    })
    return {
      count,
      inc,
      state
    }
  }
}
</script>

二、toRefstoRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象

<template>
  <div class="homePage">
    <p>第 {{ year }} 年</p>
    <p>姓名: {{ nickname }}</p>
    <p>年龄: {{ age }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref, toRefs } from 'vue'
export default defineComponent({
  setup() {
    const year = ref(0)
    const user = reactive({ nickname: 'xiaofan', age: 26, gender: '女' })
    setInterval(() => {
      year.value++
      user.age++
    }, 1000)
    return {
      year,
      // 使用reRefs
      ...toRefs(user)
    }
  }
})
</script>

生命周期钩子

TODO: 作用待补充

vue2作用vue3作用
beforeCreate----setup----
created----setup----
beforeMount----onBeforeMount----
mounted----onMounted----
beforeUpdate----onBeforeUpdate----
updated----onUpdated----
beforeDestroy----onBeforeUnmount----
destroyed----onUnmounted----
activated----onActivated----
deactivated----onDeactivated----
errorCaptured----onErrorCaptured----
----onRenderTriggered----
----onRenderTracked----

watch 与 watchEffect

一、computed、readonly、watch、watchEffect a. computed 返回的值 就和 ref 一样,都是 需要使用 .value 获取,理由同上 b. watch 可以监听一个值,也可以同时监听多个值 c. readonly 返回一个只读代理,即使是对象里面的对象,也是 readonly

<script>
import {
  ref,
  computed,
  reactive,
  readonly,
  toRefs,
  watch,
  watchEffect
} from 'vue'
export default {
  name: '',
  setup() {
    // 建立一个响应式对象
    const count = ref(0)
    const count2 = ref(0)
    const double = computed(() => count.value * 2)
    const state = reactive({
      username: {
        fistname: 'smit'
      },
      password: 'rose'
    })

    const copy = readonly(state) // 即使是 username.firstname 也是只读的
    // watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
    watch(count, (value) => {
      // 监听 ref
      console.log(value, 'obj.double')
    })

    watch(
      () => state.password,
      (value) => {
        // 监听 state
        console.log(value)
      }
    )
    // 监听 多个数据源
    watch([count, count2], ([countNow, count2Now], [countPrev, count2Prev]) => {
      console.log([countNow, count2Now], [countPrev, count2Prev])
    })

    return {
      double,
      count,
      ...toRefs(state) //将响应式的对象变为普通对象
    }
  }
}

// 如果想要watch 函数立即执行的话,就可以使用 watchEffect 了
watchEffect(() => {
  console.log('watch', 'count.value')
})
//watchEffect 会在第一时间执行,在执行的同时会收集内部的依赖,和computed类似,所以不需要指定依赖
//正如名称所示,可以执行一些有副作用的函数,比如 ajax 请求
//例如 使用一个 响应式的参数,参数为 page.page, page.pagesize ,就可以在这里调用
//这个一般还可以和 onMounted 生命周期组合
</script>

二、侦听复杂嵌套对象 侦听复杂嵌套对象必须传递第三个参数 deep:true

<script>
import { watch } from 'vue'
export default {
  name: '',
  setup() {
    const state = reactive({
      room: {
        id: 100,
        attrs: {
          size: '140平方米',
          type: '三室两厅'
        }
      }
    })
    watch(
      () => state.room,
      (newType, oldType) => {
        console.log('新值:', newType, '老值:', oldType)
      },
      { deep: true }
    )
  }
}
</script>

三、stop 停止监听 在组件销毁之前我们想要停止掉某个监听可以调用 watch() 函数的返回值

<script>
import { watch } from 'vue'
export default {
  name: '',
  setup() {
    const state = reactive({
      room: {
        id: 100,
        attrs: {
          size: '140平方米',
          type: '三室两厅'
        }
      }
    })
    const stopWatchRoom = watch(
      () => state.room,
      (newType, oldType) => {
        console.log('新值:', newType, '老值:', oldType)
      },
      { deep: true }
    )

    setTimeout(() => {
      // 停止监听
      stopWatchRoom()
    }, 3000)
  }
}
</script>

父子传值

一、 props 父组件向子组件传参(最常用)二、provide 父组件向子组件传参 父组件 provide 传递数据 vue

<template>
  <div>
    <provideComponent />
  </div>
</template>
<script>
// provide  父组件向子组件传参
import { provide } from "vue";
import provideComponent from "../components/provide.vue";
export default {
  name: "",
  components: {
    provideComponent,
  },
  setup() {
    // provide('数据名称', 要传递的数据)
    provide("success", "我是父组件向子组件传递的值");
  },
};
</script>

子组件

<template>
  <div>
    {{ childMessage }}
  </div>
</template>
<script>
import { inject } from 'vue'
// provide  父组件向子组件传参 子组件 引入 inject 接收父组件传递的值
export default {
  name: '',
  setup() {
    // 调用 inject 函数,通过指定的数据名称(和父级provide数据名一致),获取到父级传的值,一定要return出去
    const childMessage = inject('success')
    return {
      childMessage
    }
  }
}
</script>

通过 ref 获取 dom 元素

console.dir()可以显示一个对象的所有属性和方法 一、ref 获取单个 dom 元素

<template>
  <div ref="myref">获取单个DOM</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
  name: '',
  setup() {
    const myref = ref(null)
    onMounted(() => {
      console.dir(myref.value)
    })

    return {
      myref
    }
  }
}
</script>

二、ref 获取多个 dom 元素

<template>
  <ul>
    <li v-for="(item, index) in arr" :key="index" :ref="setRef">
      {{ item }}
    </li>
  </ul>
</template>
<script>
import { ref, nextTick } from 'vue'
export default {
  name: '',
  setup() {
    const arr = ref([1, 2, 3])
    // 存储 dom 数组
    const fefArr = ref([])
    const setRef = (el) => {
      fefArr.value.push(el)
    }
    nextTick(() => {
      console.dir(fefArr.value)
    })

    return {
      arr,
      setRef
    }
  }
}
</script>

自定义 Hooks

封装成一个 hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。 useCount.ts 实现

import { ref, Ref, computed } from 'vue'
type CountResultProps = {
  count: Ref<number>
  multiple: Ref<number>
  increase: (delta?: number) => void
  decrease: (delta?: number) => void
}
export default function useCount(initValue = 1): CountResultProps {
  const count = ref(initValue)
  const increase = (delta?: number): void => {
    if (typeof delta !== 'undefined') {
      count.value += delta
    } else {
      count.value += 1
    }
  }
  const multiple = computed(() => count.value * 2)

  const decrease = (delta?: number): void => {
    if (typeof delta !== 'undefined') {
      count.value -= delta
    } else {
      count.value -= 1
    }
  }
  return {
    count,
    multiple,
    increase,
    decrease
  }
}

组件中使用 useCount 这个 hook:

<template>
  <p>count: {{ count }}</p>
  <p>倍数: {{ multiple }}</p>
  <div>
    <button @click="increase()">加1</button>
    <button @click="decrease()">减一</button>
  </div>
</template>

<script lang="ts">
import useCount from "../hooks/useCount";
setup() {
  const { count, multiple, increase, decrease } = useCount(10);
    return {
      count,
      multiple,
      increase,
      decrease,
    };
},
</script>

移除过滤器 filters 使用 computed 替代

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountInUSD }}</p>
</template>

<script>
export default {
  props: {
    accountBalance: {
      type: Number,
      required: true
    }
  },
  computed: {
    accountInUSD() {
      return '$' + this.accountBalance
    }
  }
}
</script>

实例之 button 组件

components 文件夹下 ZButton.vue 文件

<template>
  <button
    class="z-button"
    :class="classes"
    :type="nativeType"
    :disabled="buttonDisabled || loading"
    @click="handleClick"
  >
    <i class="z-icon-loading" v-if="loading"></i>
    <i :class="icon" v-else-if="icon"></i>
    <slot></slot>
  </button>
</template>

<script>
import { computed, inject, toRefs, getCurrentInstance } from 'vue'
export default {
  name: 'ZButton',
  props: {
    size: {
      type: String,
      validator(val) {
        if (val === '') return true
        return ['medium', 'small', 'mini'].indexOf(val) !== -1
      }
    },
    type: {
      type: String,
      validator(val) {
        if (val === '') return true
        return (
          ['primary', 'success', 'warning', 'danger', 'info', 'text'].indexOf(
            val
          ) !== -1
        )
      }
    },
    nativeType: {
      type: String,
      default: 'button'
    },
    plain: Boolean,
    round: Boolean,
    circle: Boolean,
    loading: Boolean,
    disabled: Boolean,
    icon: String
  },
  setup(props, context) {
    // context
    /* 
    context是setup的第二个参数,context是一个对象,里边包含了三个属性。分别是
    attrs:attrs与vue2.0的this.$attrs是一样的,即外部传入的未在props中定义的属性。对于attrs与props一样,我们不能对attrs使用es6解构,必须使用attrs.name的写法。
    slots:slots对应的是组建的插槽,与Vue2.0的this.$slots是对应的,与props和attrs一样,slots也是不能解构的。
    emit:emit对应的是Vue2.0的this.$emit,即对外暴露事件
    */
    const { size, disabled } = toRefs(props)
    // 按钮大小
    const buttonSize = useButtonSize(size)
    // 按钮禁用
    const buttonDisabled = useButtonDisabled(disabled)
    // 按钮样式
    const classes = useClasses({
      props,
      size: buttonSize,
      disabled: buttonDisabled
    })
    // 绑定事件
    const handleClick = (e) => {
      context.emit('click', e)
    }

    return {
      classes,
      buttonDisabled,
      handleClick
    }
  }
}

/* 按钮样式 */
const useClasses = ({ props, size, disabled }) => {
  return computed(() => {
    return [
      size.value ? `z-button--${size.value}` : '',
      props.type ? `z-button--${props.type}` : '',
      {
        'is-plain': props.plain,
        'is-round': props.round,
        'is-circle': props.circle,
        'is-loading': props.loading,
        'is-disabled': disabled.value
      }
    ]
  })
}

/* 按钮是否禁用 */
const useButtonDisabled = (disabled) => {
  return computed(() => {
    const elForm = inject('elForm', {})
    return disabled?.value || elForm.disabled
  })
}

/* 按钮大小 */
const useButtonSize = (size) => {
  return computed(() => {
    // inject 注入
    const elFormItem = inject('elFormItem', {})
    return (
      size?.value ||
      elFormItem.elFormItemSize ||
      getCurrentInstance().ctx.$ELEMENT?.size
    )
  })
}
</script>
<style lang="scss" scoped>
$name: 'z-button';
.#{$name} {
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: #fff;
  border: 1px solid #dcdfe6;
  color: #606266;
  -webkit-appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  transition: 0.1s;
  font: {
    weight: 500;
    size: 14px;
  }
  // 禁止元素的文字被选中
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  padding: 12px 20px;
  border-radius: 4px;
  &:hover,
  &:focus {
    color: #409eff;
    border-color: #c6e2ff;
    background-color: #ecf5ff;
  }
}
// type样式 start
.d-button--primary {
  color: #fff;
  border-color: #409eff;
  background-color: #409eff;
  &:hover,
  &:focus {
    background: #66b1ff;
    border-color: #66b1ff;
    color: #fff;
  }
}
.#{$name}--success {
  color: #fff;
  border-color: #67c23a;
  background-color: #67c23a;
  &:hover,
  &:focus {
    background: #85ce61;
    border-color: #85ce61;
    color: #fff;
  }
}
.#{$name}--info {
  color: #c0bbc5;
  border-color: #f2fced;
  background-color: #f2fced;
  &:hover,
  &:focus {
    background: #e0ebdb;
    border-color: #e0ebdb;
    color: #fff;
  }
}
.#{$name}--warning {
  color: #fff;
  border-color: #e6a23c;
  background-color: #e6a23c;
  &:hover,
  &:focus {
    background: #ebb563;
    border-color: #ebb563;
    color: #fff;
  }
}
.#{$name}--danger {
  color: #fff;
  border-color: #f56c6c;
  background-color: #f56c6c;
  &:hover,
  &:focus {
    background: #fc6666;
    border-color: #fc6666;
    color: #fff;
  }
}
// type样式 end

// 朴素按钮样式 start
.#{$name}.is-plain {
  &:hover,
  &:focus {
    background: #fff;
    border-color: #489eff;
    color: #409eff;
  }
}
.#{$name}--primary.is-plain {
  color: #409eff;
  background: #ecf5ff;
  &:hover,
  &:focus {
    background: #409eff;
    border-color: #409eff;
    color: #fff;
  }
}
.#{$name}--success.is-plain {
  color: #67c23a;
  background: #c2e7b0;
  &:hover,
  &:focus {
    background: #67c23a;
    border-color: #67c23a;
    color: #fff;
  }
}
.#{$name}--info.is-plain {
  color: #909399;
  background: #d3d4d6;
  &:hover,
  &:focus {
    background: #909399;
    border-color: #909399;
    color: #fff;
  }
}
.#{$name}--warning.is-plain {
  color: #e6a23c;
  background: #f5dab1;
  &:hover,
  &:focus {
    background: #e6a23c;
    border-color: #e6a23c;
    color: #fff;
  }
}
.#{$name}--danger.is-plain {
  color: #f56c6c;
  background: #fbc4c4;
  &:hover,
  &:focus {
    background: #f56c6c;
    border-color: #f56c6c;
    color: #fff;
  }
}
// 朴素按钮样式 end

// round属性
.#{$name}.is-round {
  border-radius: 20px;
  padding: 12px 23px;
}
// circle属性
.#{$name}.is-circle {
  border-radius: 50%;
  padding: 12px;
}
// icon配套样式

// 找到css中类名包含d-icon-且后面有个span的元素
.#{$name} [class*='d-icon-'] + span {
  margin-left: 5px;
}
// disabled属性
.#{$name}.is-disabled {
  cursor: no-drop;
}
</style>

plugins 插件文件下 ZComponents.js 文件

import ZButton from '../components/ZComponents/ZButton'

export default {
  install(Vue) {
    Vue.component(ZButton.name, ZButton)
  }
}

main.js 中使用

// 自定义UI组件
import ZComponents from './plugins/ZComponents'

createApp(App).use(ZComponents).mount('#app')