• home > webfront > ECMAS > vue3 >

    vue3响应性API学习笔记

    Author:zhoulujun Date:

    学习笔记:shallowRef,shallowReactive,响应性基础,reactive, reftoRef,toRefs,toRaw,markRaw,readonly,shallowReadonly,effect,watchEffect

    官方文档: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换掉呢?

    1. Vue2.x经常遇到的一个问题,数据更新了啊,为何页面不更新呢?

    2. 什么时候用$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 延续响应式对象的全部属性

    追加评论区 卡梅隆的意见:‘toRefs 就是把响应式的reactive对象,分解成无数的 ref 双向绑定’,官方解释是:‘toRefs把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应)

    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()的方法)

    reactive和shallowReactive vue3

    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类型参数默认值备注
    lazyboolean~~~false此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
    schedulerfunctionrunnernone传入此参数,开启了调度模式,只有手动调用 runner 时,才会执行对应的注册回调
    onTrackfunctiontrackType[]none参数包含了依赖数据的每次更新进行的操作对象
    onTriggerfunctiontrigger[]none此方法是当依赖的数据被改变是会显示相应值得改变栈
    onStopfunctionnonenone在调用 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