首页>>前端>>Vue->聊聊使用 composition

聊聊使用 composition

时间:2023-11-29 本站 点击:0

背景

部门要开发一款小程序,技术栈为 Vue。

此时 Vue3 刚发布 beta 版,Taro3 还没有横空出世情况下,为了能享受 composition-api 的红利,我们将目标瞄向了 composition-api plugin

使用这个插件可以让 Vue2 支持 composition-api 部分功能

而这个部分功能就是引发问题的根源

现象

为了简化认知成本,写了 2 个简易 demo,技术栈均为 Vue2,分别用 composition-api plugin 和 optional-api 实现相似的功能

composition-api plugin

<template><div><div>CompositionApiPlugin</div><div>reactiveObj:{{reactiveObj}}</div><div>computedValue:{{computedValue}}</div><button@click="onClick">adda</button></div></template><script>import{reactive,defineComponent,computed,set}from'@vue/composition-api'exportdefaultdefineComponent({setup(){constreactiveObj=reactive({})constcomputedValue=computed(()=>{returnreactiveObj.a})return{reactiveObj,computedValue,onClick:()=>{set(reactiveObj,'a',1)}}}})</script>

optional-api

<template><divid="app"><div>OptionalApi</div><div>reactiveObj:{{reactiveObj}}</div><div>computedValue:{{computedValue}}</div><button@click="onClick">adda</button></div></template><script>importVuefrom"vue";exportdefault{data(){return{reactiveObj:{}}},computed:{computedValue(){returnthis.reactiveObj.a}},methods:{onClick(){Vue.set(this.reactiveObj,'a',1)}},}</script>

结果

定义一个空的响应式对象 reactiveObj,一个值为 reactiveObj.a 的计算属性 computedValue

由于 Vue2 直接给响应式对象设置一个不存在的 key 会导致视图无法更新,所以这里借助 Vue.set 主动通知 reactiveObj 的订阅,触发更新

理论上 reactiveObjcomputedValue 都会被更新,最终在页面上显示 { "a":1 } 和数字 1

来看结果

reactiveObj 都被成功更新了,而只有 optional-api 版本的 computedValue 得到了更新,这是为什么呢?

分析

找寻问题源头

打开 Vue devtools 查看数据源

可以发现 composition-api plugin 版本 computedValue 数据层面也没有更新

继续追查,computedValue 未被更新的原因无非 2 种

Vue.setreactiveObj 赋值后,没有通知 reactiveObj 的订阅更新

Vue.set 通知了 reactiveObj 的订阅更新,但 computedValue 没有收到更新消息

为了弄清缘由,给 Vue.set 设置断点,查看此时 reactiveObj 里收集的订阅(下图 subs 数组)

可以发现,在调用 Vue.set 之前 reactiveObj 只收集了一个订阅,通过 expression 字段的值可以断定它是一个渲染 watcher(即和视图相关的 watcher,当渲染 watcher 更新时,视图会重新渲染)

这里问题来了,因为在 setup 时调用了 computed 函数,收集 reactiveObj 作为依赖,所以在 reactiveObj 里理论上应该还有一个订阅,即名为 computedValue 的计算 watcher

正确的逻辑应该为以下流程:

在 optional-api 里调试可以发现,此时的 reactiveObj 内确实存储了 2 个订阅,分别为渲染 watcher 和名为 computedValue 的计算 watcher

reactiveObj 缺少的订阅可以推倒出,应该是前面提到的第二种情况,即

composition-api plugin 版本的计算属性 computedValue 没有收集 reactiveObj 作为依赖,所以无论 reactiveObj 如何更新,computedValue也无法被更新

分析问题原因

找到问题源头后,接着分析

为什么 computedValue 没有收集到 reactiveObj 作为依赖?

这里得简述下 Vue 依赖收集更新的原理

Vue 内部通过调用 Object.defineProperty ,拦截对象的 getter/setter,得到一个响应式对象

当计算属性访问(依赖)响应式对象时,会触发其 getter,在该对象的 __ob__.dep.subs 里添加一个订阅

当某个 key 的值被修改时,会遍历其 subs 里所有的订阅并通知更新

碎花我们给 2 个版本的计算属性分别打断点,查看计算属性依赖收集的行为

composition-api plugin

可以发现,在访问 reactiveObj.a 时,并没有触发 reactiveObj 的 getter,所以没有收集到依赖,也就是说,此时的计算属性值是一个常量

optional-api

从图里可以看到,optional-api 版本会触发 reactiveObj 的 getter

分析 reactive 函数

来到了最后一个问题,为什么在 composition-api plugin 里的 reactive 函数创建的响应式对象,没有触发 getter?

还是老办法,通过源码查看底层实现,reactive 的核心实现在 composition-api plugin src/reactivity/reactive.ts第 247 行

抛开一些边缘判断,其实 composition-api plugin 提供的 reactive 函数就是 Vue.observable 的语法糖

熟悉 Vue2 的同学应该了解, Vue.observable 返回的对象,本身并不是响应式的(但是会对其已知的 key 做递归的响应式绑定),这一点官方文档上也有明确标注

而 optional-api 里,reactiveObj 本身是响应式的,原因在于 optional-api 在组件初始化时,会对整个 data 做递归的响应式绑定

所以 composition-api plugin 里,computedValue 无法对一个普通对象收集依赖,而 optional-api 里,由于 reactiveObj 是响应式的,所以 computedValue可以正常收集依赖

题外话

例子中的 reactiveObj 是一个空对象,并且是因为设置了一个不存在的值,导致视图没有更新

那么如果是一个已知 key 呢?将代码改造一下,给 reactiveObj 预先添加一个名为 a 的 key

<template><div><div>CompositionApiPlugin</div><div>reactiveObj:{{reactiveObj}}</div><div>computedValue:{{computedValue}}</div><button@click="onClick">adda</button></div></template><script>import{reactive,defineComponent,computed}from'@vue/composition-api'exportdefaultdefineComponent({setup(){constreactiveObj=reactive({a:1//预先添加的key})constcomputedValue=computed(()=>{returnreactiveObj.a})return{reactiveObj,computedValue,onClick:()=>{reactiveObj.a=2}}}})</script>

竟然也能成功更新!

前面提到 reactive 函数返回的对象虽然本身不是响应式,但是会对其已知的 key 做递归的响应式绑定

也就是说此时计算属性在访问 reactiveObj.a 时,会触发 a 的 getter, 最终实现依赖收集

但如果给 reactive 传入一个空对象,由于 composition-api plugin 底层还是会用 Object.defineProperty 拦截 getter/setter 作响应式的绑定,所以无法处理不存在的 key

当然解决办法也很简单,使用 Vue3 版本的 reactive

它的底层基于 Proxy 实现, 而 Proxy会代理整个对象,可以拦截对象几乎任何操作,包括创建一个不存在的 key

<template><div><div>Vue3</div><div>reactiveObj:{{reactiveObj}}</div><div>computedValue:{{computedValue}}</div><button@click="onClick">adda</button></div></template><script>import{reactive,defineComponent,computed}from'vue'exportdefaultdefineComponent({setup(){constreactiveObj=reactive({})constcomputedValue=computed(()=>{returnreactiveObj.a})return{reactiveObj,computedValue,onClick:()=>{reactiveObj.a=1}}}})</script>

总结

计算属性需要收集依赖才能实现数据更新,而收集依赖的前提必须是响应式

composition-api plugin 里使用 reactive 函数创建的对象,和 optional-api 里在 data 里定义的对象,行为略有区别

前者创建的对象,本身并不是响应式的,但会对它已知的 key 作递归的响应式绑定

后者会在组件初始化时将 data 里的所有值作递归的响应式绑定

composition-api plugin 的 reactive 函数,和 Vue3 版本的 reactive 函数,行为略有区别

前者返回的是源对象,并且无法对不存在的 key 作响应式绑定

后者返回的是新的代理对象,允许对不存在的 key 作响应式绑定

作者:yeyan1996


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/811.html