VUE3的详细笔记配合测试的demo
vue2 和 vue3 的区别
vue2 数据定义在 data,方法定义在 methods,操作一个数据往往会影响到 data, methods, computed, watch由此带来了大量的逻辑耦合,因此 vue2 给出了解决方案那就是 Mixin,但是 Mixin 经常导致命名冲突等各种问题,所以 Vue3 推出了 Composition API 用以解决以上痛点。
setup
vue3新增了一个属性选项 -> setup,该选项是 Composition API 在组件内的入口,在组件创建的时候会先于 beforeCreate 执行,接收两个参数 setup(props, context)。
props
props 是响应式的,也正因为是响应式的,因此不能使用解构的方式去获取其中的数据,否则响应式会失效,错误示范如下:export default defineComponent({ setup(props, contxt){ let { name } = props console.log(name) //此时获取到的name值已经无法响应组件中的 name 值的变化了 } })
- context
在 setup 中我们是访问不到 vue2 里那种能拿到组件数据的 this 对象的,因此 context 提供了 this 对象最常用的三个属性 attr, slot, emit 分别对应 vue2 中的 $attr(属性), $slot(插槽), $emit(发射事件),这几个属性都是自动同步最新的值,因此我们拿到的数据也是最新的。
关于reactive、ref 和 toRefs
在 vue2 中我们定义数据是在组件的 data 中,在 vue3 中我们可以使用 reactive, ref 来定义数据使用。
reactive
一般来说,我们使用 reactive 定义数据的时候定义的都是对象或者函数这种引用类型的,reactive 不能代理基本数据类型的数据【string, number, boolean...】比如我创建一个属性 a, 值为 test 的对象:const obj = reactive({ a: 'test' }) console.log(obj.a) //这里会输出 test
toRefs
上面使用 reactive 定义的数据 return 出去之后,在标签中使用的时候要使用 obj.a 这样的方式引入 dom 结构中去,这样的就很麻烦,但是我们又不能通过解构的方式 return{ ...obj },这样数据就失去了实时响应,在这种情况下我们有想要直接使用结构后的数据就可以使用 toRefs 进行解构来节省操作(原因暂时还不是太清楚,留个坑)const obj = reactive({ count: 1, price: 20 }) //使用 toRefs 解构构返回 return { ...toRefs(obj) } //标签中就可以直接使用解构后的属性 <p>数量:{{count}},价格:{{price}}</p>
ref
与 reactive 相对应的,一般使用 ref 定义的都是普通类型的数据,不过 ref 也是可以定义对象类型的数据的,ref 定义数据如下(建议都使用 ref 定义数据):let num = ref(2) let str = ref('hello') let obj = ref({ name: zjh }) console.log(num.value) // 2 console.log(str.value) // hello // 这里需要注意一下,所有使用 ref 创建的响应式数据在访问的时候都需要加上 .value ,标签中不需要 console.log(obj.value.name) // zjh
Vue3的生命周期
生命周期这里要注意的是,在 vue3 中,使用 setup 代替了 beforeCreate&created 两个钩子,但是你依旧可以在 vue3 项目中使用 beforeCreate&created 因为vue3会向下兼容,这样做相当于在 vue3 项目写 vue2 代码,然后 setup 会比 beforeCreate&created 执行的要更早。
vue2 中代表销毁的钩子 beforeDistroy&destroyed 两个钩子被代表卸载的 onBeforeUnmount&onUnmounted 给代替了,除此之外其他的生命周期在关键字前加上 on 关键字,如果需要使用某个生命周期需要从 vue 中引入:import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered } from "vue"
监听数据watch & watchEffect
watch 监听数据变化是惰性的,只有在数据发生了改变的时候才会触发相应的回调。
watch
watch 有三个参数 watch(source, callback, [options])- source:可以支持 string,Object,Function,Array; 用于指定要侦听的数据源,你的响应式变量。
- callback:触发监听之后执行的回调函数,有两个参数 (newV, oldV),拿到的新的值和更改之前变量的值(旧值)。
options:提供监听模式 deep、immediate 和 flush 选项
- deep: 设置值为 true 时,能够监听到层层嵌套的数据,对象嵌套数组...这里有一个坑就是,监听这种复杂数据的时候,拿到的新值和旧值是一样的,暂时不清楚原因,使用的时候务必注意。
- immediate:解除 watch 的惰性状态,在组件创建的时候就执行一次该回调,其余表现和正常的监听没有区别。
- flush:有待学习。
示例如下:
<template> <div> <h2>数量:{{count}}</h2> <h2>姓名:{{name}}</h2> <h2>deep:{{inside.test}}</h2> <h2>refdeep:{{refObj.inside.test}}</h2> <button @click="change">事件</button> </div> </template> <script> import { defineComponent, reactive, ref, toRefs, watch } from "vue"; export default defineComponent({ setup(props, context){ const obj = reactive({ count: 10 }) let name = ref('zjh') const deepObj = reactive({ inside: { test: 'deep' } }) const refObj = ref({ inside: { test: 'deep' } }) //监听 ref 数据 watch(name, (newV, oldV) => { console.log('newName', newV, ',oldName', oldV); //执行 change 输出 'newNametry,oldNamezjh' }) //深度监听 ref 的嵌套数据 (有坑) 当监听的数据是一个多层嵌套的复杂数据时,拿到的新值和旧值是一样的 watch(refObj, (newV, oldV) => { console.log('n_refdeep', newV.inside.test, ',o_refdeep', oldV); //执行 change 输出 'n_refdeep ref deep ,o_refdeep{proxy}' }, {deep: true}) //监听 reactive 数据 watch(() => obj.count, (newV, oldV) => { console.log('newCount', newV, ',oldCount', oldV) //执行 change 输出 'newCount12,oldCount10' }) //深度监听 reactive (有坑) 当监听的数据是一个多层嵌套的复杂数据时,拿到的新值和旧值是一样的 watch(() => deepObj.inside, (newV, oldV) => { console.log('newDeep', newV.test); console.log('oldDeep', oldV); }, {deep: true}) // deep 为 false 的时候监听不到 deepObj.inside.test 的变化,dom 却已经改变了 // watch 监听多个数据 , (有坑) reactive 为多层嵌套数据的时候检测到的新值和旧值是一样的 watch([name, refObj], (newV, oldV) => { console.log('oldName', oldV[0]); // oldName zjh console.log('newName', newV[0]); //输出 newName try console.log('newRefObj', newV[1].inside.test); //输出 newRefObj ref deep }, {deep: true}) function change(){ obj.count = 12 name.value = 'try' deepObj.inside.test = 'get deep!' refObj.value.inside.test = 'ref deep' console.log('??', refObj.value.inside.test); } return {...toRefs(obj), ...toRefs(deepObj), refObj, name, change} } }) function methods(){ return{ log(){ console.log('???') } } } </script>
- 停止watch监听
watch() 监听函数会返回一个可执行的方法,我们调用这个返回的方法可以停止对相应数据的监听,注意:停止监听并不会阻止数据的运算,数据会随着运算而改变,但是不会触发 watch() 函数的回调了。
let num = ref(1) // 监听num,接收 watch() 函数的返回值,执行这个返回值可以停止对该数据的监听 const stopWatch = watch(num, (newV, oldV) => { console.log('newNum', newV); }) function change(){ // num的值大于10的时候停止监听 num.value += 2 if(num.value > 10){ console.log('value', num.value); //停止监听,不影响数据运算 += 2 stopWatch() //停止监听 } }
关于watchEffect
我查到 watchEffect 和 watch 在使用上并无太大差别,所以只做了了解没有再单独练习了- watchEffect不需要指定监听的数据对象,以打印为例,只需要在 watchEffect 的回调中 console.log(num), watchEffect 就会自动收集资源找到 num 变量去监听它。
- 在组件创建时 watchEffect 会先执行一次用来自动收集依赖(数据资源,依赖就是你要监听的数据变量),相当于 watch 加了 {immediate: true}。
- watchEffect 无法获取到变化前的值,只能获取变化后的值。
对比 vue2.x 与 vue3.x 响应式
- 最直接的不同就是 vue2 响应式是使用的 Object.defineProperty,Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性劫持监听,如果遍历到的对象属性还是对象或者数组这种就需要递归进行深度深度遍历,然后新增属性的时候需要再次遍历劫持,这也是我们有时候需要使用 $set 更新或者 $forceUpdate 强制更新的原因。
- 然后 vue3 使用的是 proxy 会直接代理对象,不需要再去遍历操作。【我知道这样因为是直接代理的,所以就不需要在进行 $set/$forceUpdate 的操作了,但是对于 proxy 不了解,大家自己查吧,欢迎交流。】
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。