Vue3 笔记
Vue3 笔记
setup 函数
setup 函数是 vue3 的核心 也是所有函数的入口 这个函数传入两个参数,分别为 props 和 contextprops 为 父组件传递的参数,而 context 为 attrs, emit, slotsprops 是响应式的,但是不可以 使用 解构或者展开,这样会 导致响应式 失败(原因会在第二点讲) context 可以使用解构,slots 相当于以前的 $slotsemit 相当于以前的 $emit,attrs 则是在组件标签上的内容 props 和 attrs 的区别: 如下面代码 所示,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
ref 和 reactive 是构建响应式对象的 函数,但是 也有着显著的区别 在使用 ref 定义的值之后,必须使用 xxx.value 才能获得对应的值,而 reactive 则不需要 reactive 响应化的数据不能使用解构或者展开,要不然会失去响应 那么为什么 props 和 reactive 不能解构或者展开,而 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')