vue3响应性API学习笔记
Author:zhoulujun Date:
官方文档:https://v3.cn.vuejs.org/api/basic-reactivity.html#reactive
这方面也可以瞅瞅:深入理解 Vue3 Reactivity API https://mp.weixin.qq.com/s/CYTSFsXkOevpZb38psvxZQ
Vue2.x vs Vue3.x
对响应方式来讲:Vue3.x 将使用Proxy ,取代Vue2.x 版本的 Object.defineProperty
为何要将Object.defineProperty换掉呢?
Vue2.x经常遇到的一个问题,数据更新了啊,为何页面不更新呢?
什么时候用$set更新,什么时候用$forceUpdate强制更新,你是否也一度陷入困境?
后来的学习过程中开始接触源码,才知道一切的根源都是 Object.defineProperty。
在vue2.0中vm.items[indexOfItem] = newValue这种是无法检测的。事实上,Object.defineProperty 本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。
Object.defineProperty 并非不能监控数组下标的变化,Vue2.x 中无法通过数组索引来实现响应式数据的自动更新是 Vue 本身的设计导致的,不是 defineProperty 的锅。
Object.defineProperty 和 Proxy 对比存在哪些优缺点呢?
Object.defineProperty 只能劫持对象的属性,而 Proxy 是直接代理对象。
由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作。
Object.defineProperty 对新增属性需要手动进行 Observe。
由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。也正是因为这个原因,使用 Vue 给 data 中的数组或对象新增属性时,需要使用vm.$set 才能保证新增的属性也是响应式的。
递归监听和非递归监听
Vue3的响应式系统被放到了一个单独的@vue/reactivity模块中(Vue3采用lerna做package的划分,而响应式能力@vue/reactivity被划分到了单独的一个package中),其提供了reactive、effect、computed等方法,其中reactive用于定义响应式的数据,effect相当于是Vue2中的watcher,computed用于定义计算属性。
reactive() 方法本质是传入一个要定义成响应式的target目标对象,然后通过Proxy类去代理这个target对象,最后返回代理之后的对象
export function reactive(target) { return new Proxy(target, { get() {//TODO}, set() {//TODO} }); }
实际代理Set、Map、WeakMap、WeakSet等集合类比这个复杂,这里略过。
具体阅读:Vue3响应式原理与reactive、effect、computed实现 https://segmentfault.com/a/1190000023380448
Composition API: 对比ref和reactive
这两个API都是为了给JavaScript普通的数据类型赋予响应式特性(reactivity)。
ref 针对原始数据类型,ref是vue3 中使用值类型变成响应式的方法——使用 const nameRef = ref('a'), 进行改变值:nameRef.a = 1
ref 的作用就是将一个原始数据类型(primitive data type)转换成一个带有响应式特性
reactive 用于对象,reactive是vue3 中使用引用类型变成响应式的方法——使用 const demo= reactive({a:1}),改变值:demo.a = 1
reactive (等价于Vue2中的Vue.observable() )来赋予对象(Object) 响应式的特性
ref本质也是reactive,ref(obj)等价于reactive({value: obj})
vue3中实现响应式数据的方法是就是使用ref和reactive,所谓响应式就是界面和数据同步,能实现实时更新
vue2中响应式是通过defineProperty实现的,vue3中是通过ES6的Proxy来实现的
在vue中使用ref的值,不用通过.value获取
在js中使用ref的值,必须通过.value获取
Vue3第一篇之ref和reactive详解扩展 https://juejin.cn/post/6977929393511514148
Vue3中,我们可以用Composition API: ref 来改写data属性,相比于Vue2,用ref 的好处就是传值时可以不用再写this
reactive 内部是可以使用计算属性等各种方法,它只是把数据实现响应式而已
reactive 后return 的数据最好是用toRefs 转化一下,好处谁用谁知道
跟 ref 混合使用时候可以用isRef 判断类型
toRef和toRefs
从一个对象中拿出一个属性,操作这个属性,这个属性并不是响应式的
toRef为响应式对象上的某个属性创建一个Ref引用,更新时引用对象会同步更新,注意如果通过toRef创建的数据修改时,并不会触发视图界面的更新,因为toRef的本质是引用,与原始数据有关联。
toRef 是对定义的响应对象的某个属性进行引用——toRef 延续单个响应式对象的属性,const nameRef = toRef(person, 'name')
toRefs,它可以将一个响应型对象(reactive object) 转化为普通对象(plain object),同时又把该对象中的每一个属性转化成对应的响应式属性(ref)。说白了就是放弃该对象(Object)本身的响应式特性(reactivity),转而给对象里的属性赋予响应式特性(reactivity)。
toRefs 延续响应式对象的全部属性。
shallowRef和shallowReactive
ref和reactive都属于递归监听,也就是数据的每一层都是响应式的,如果数据量比较大,非常消耗性能,非递归监听只会监听数据的第一层。
ref定义的数据每一层都是响应式数据
shallowRef定义的数据,只有第一层是响应式的,即只有在更改.value的时候才能实现响应式
const age = ref({ a: 1, b: { c: { d: 1 } } });//每一层都是响应式数据 const age = shallowRef({ a: 1, b: { c: { d: 1 } } }); //只有第一层是响应式的,即只有在更改.value的时候才能实现响应式
使用shallowRef后,可以通过triggerRef()方法主动更新界面,实现界面刷新
同理,reactive和shallowReactive(shallowReactive没有类似triggerRef()的方法)
toRaw与markRaw
toRaw的出现是解决什么问题呢?
toRaw可以将由reactive或readonly函数转换成响应式代理的普通对象,对普通对象的属性值进行修改,就不会更新视图界面。一般用于渲染具有不可变数据源的大列表,跳过代理转换可以提高性能。
有些时候我们不希望数据进行响应式实时更新,可以通过toRaw获取ref或reactive引用的原始数据,通过修改原始数据,不会造成界面的更新,只有通过修改ref和reactive包装后的数据时才会发生界面响应式变化。
let obj1 = {...}; //state和obj1是引用关系,state的本质是一个Proxy对象,其中引用了obj1 let state = reactive(obj1); //通过toRaw方法获取到原始数据,其实是获取到obj1的内存地址,obj2和obj1是完全相等的 let obj2 = toRaw(state) console.log(obj1 === obj2);//true
在使用reactive定义数据时一般不会先定义一个obj,再将他传给reactive,都是直接在reactive中写数据的。
markRaw
与toRaw不同,markRaw包装后的数据永远不会被追踪!
markRaw标记一个对象,使其永远不会转换为响应式数据,只能返回这个对象本身,一般用于某些值不该被设置为响应式的,比如第三方类实例或vue对象等场景。
let obj1 = {name: "lijing", age: 18} let obj2 = markRaw(obj1); //此时reactive包装的数据虽然是响应式对象,但是不会被跟踪,也不会产生效应式效果 let state1 = reactive(obj2) console.log(obj1 === obj2);//true
markRaw标记一个永远不是响应式的数据, 哪怕后面用reactive转也是不响应式的。
markRaw 和下方的 shallowXXX API 使你可以有选择地退出默认的深度响应式/只读转换模式,并将原始的,未被代理的对象嵌入状态图中。它们可以根据情况灵活运用:
有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当渲染具有不可变数据源的大列表时,跳过 proxy 转换可以提高性能。
readonly与shallowReadonly
readonly深度只读数据,传入响应式或纯对象,返回一个原始对象的只读代理,对象内部任何嵌套的属性也是只读的
在某些特定的情况下,不希望对数据进行更新操作,那就可以包装生产一个只读代理对象来读取数据,防止对数据的修改或删除等操作。
shallowReadonly浅只读数据,传入响应式或纯对象,返回一个原始对象的只读代理,但是这个只读只是第一层只读,非深度只读。
effect与watchEffect
当我们修改数据的时候,能够触发传入effect的回调函数执行。
effect
Vue3.0 中的 effect 同样会在响应式数据发生改变时,去执行对象的注册回调。看下面的代码
const a = ref(1) effect( () => { console.log(a.value) } ) a.value = 2 // 会打印2
effect 在注册完成后,如果没有传入相关参数(下面介绍那些参数),会立即执行一次回调函数用来依赖收集。
Effect 的配置参
api | 类型 | 参数 | 默认值 | 备注 |
---|---|---|---|---|
lazy | boolean | ~~~ | false | 此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行 |
scheduler | function | runner | none | 传入此参数,开启了调度模式,只有手动调用 runner 时,才会执行对应的注册回调 |
onTrack | function | trackType[] | none | 参数包含了依赖数据的每次更新进行的操作对象 |
onTrigger | function | trigger[] | none | 此方法是当依赖的数据被改变是会显示相应值得改变栈 |
onStop | function | none | none | 在调用 stop 停止 effect 时触发执行 |
interface trackType{ effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是发生属性变化的数据的源对象 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值(更多类型下面介绍) key: 'foo' // 变化的键,可以是任意合法的键 } interface tigger{ effect: runner, target: toRaw(obj), type: TriggerOpTypes.SET, key: 'foo', oldValue: 1, newValue: 2 }
effect的使用:Vue3.0 中的 effect https://juejin.cn/post/6850418117777195016
effect的原理:Vue3.0 中的 effect https://juejin.cn/post/6850418117777195016
watchEffect
vue2中vue实例里面有一个 $watch 方法 在sfc(sigle file component)里面有一个 watch 选项。他可以实现在一个属性变更的时候,去执行我们想要的行为。
vue3 除了 watch api, 还新增了一个 watchEffect 的 api
watchEffect与 watch 有什么不同?
watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行
watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。
watchEffect 是拿不到的旧值。
watch 可以获取到新值与旧值(更新前的值)
watchEffect在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行
watch 不需要,因为他一开始就指定了依赖。
watchEffect 是在 setup 或者 生命周期里面注册的话,在组件取消挂载的时候会自动的停止掉
ref、toRef、toRefs 都是composition API,一般在生命周期函数 setup 中使用。
setup 会比 options API 的生命周期函数 beforeCreate 更先执行
参考文章:
Vue3中ref、toRef、toRefs的区别 https://juejin.cn/post/6954789258607460359
浅谈Vue3的watchEffect用途 https://segmentfault.com/a/1190000023669309
vue3新增Suspense组件 https://blog.csdn.net/wu_xianqiang/article/details/108726301
简单对比vue2.x与vue3.x响应式 https://www.cnblogs.com/ypSharing/p/14807897.html
Vue3 对比 Vue2.x 差异性、注意点、整体梳理,与React hook比又如何? https://juejin.cn/post/6892295955844956167
转载本站文章《vue3响应性API学习笔记》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8685.html