Vue3 - 使用 computed() 计算属性响应式更新数据

为了实现页面上的数据和 JS 内部的变量值同步更新、双向绑定,我们当然可以使用 ref()
、reactive()
配合 v-model
达成这一点。
但是问题在于,如果一个值(设为 x )依赖于其他变量共同构成,这时候我们期望的行为是:
- 其他变量变化,x 也应当对应变化
- 甚至更高级的,x 变化,反过来影响其他值也变
但是实际上:
- 由于 x 在第一次定义的时候就已经计算完成,即使其他值变了,并没有什么东西去提醒 x :你该重新算算你自己是谁了!
- 反之, x 变了,也没什么东西去通知其他值改变(虽然这种情况可能比较少)
最后的结果就是,无论是页面上还是 JS 变量的值,都不会因为彼此的变化触发对方的更新。为了解决这个问题, computed()
方法应运而生。
情境分析
下面的代码模拟了上面情境所提到的困境。
提供了三个输入框,分别用于测试在页面上修改姓、名和全名。
期望的状态是姓和名的修改应该影响全名的变化,反之亦然。
1 | <template> |
JS 部分,我们暂时用 ref()
包裹全名,并且提供了一个按钮,点击可以改一下全名的值(得益于 v-model
,用页面上输入框也能改)
1 | <script lang="ts" setup> |
测试的结果是,姓和名确实能改,全名也能改,而且也能响应式的渲染到页面上,唯独没达到预期的是,并不能通过更改姓和名影响全名的值。
至于为啥,文章开头的情境部分已经说过了。
用法
computed()
有两种用法。
1. 传入单个函数,返回值只读。
还是以刚才改全名的例子,我们这次把代码改写成这样,不用 ref()
而是改用 computed()
包裹:
1 | // 改写例子中的全名赋值语句 |
可以看到我们往 computed()
括号里塞了一个箭头函数。
在传入单个函数的情况下,传入的函数只需要做一件事:
- 交代清楚被赋值的变量的值是怎么构成并且计算出的。(这有点像说清楚一道菜是由哪些原材料,经过哪些步骤做出来的)
比如这个例子中,传入的函数就写清楚了全名是怎么算出来的。
关于返回值和“只读”
返回值
那这时候的 fullName
到底是啥?
值得注意的是,虽然其看起来像个字符串,但是我们 log 一下:
1 | console.log(fullName) |
会发现其实返回的东西是一个 ComputedRefImpl
,长这样:
这个 ComputedRefImpl
有点像 ref()
返回的 RefImpl
(Reference Implementation) 类型,但是有一个关键区别:
其本身不存值(浏览器控制台的 value
那边是灰灰的,可能也在暗示这一点),你用 fullName.value
访问的那个值是他根据“菜的原材料和制作过程”实时计算出来的。
不过尽管如此,你依然可以通过 .value
访问其值,就像对其他 ref()
定义的数据一样。
下面这张表简单归纳了一下三者关系:
名称 | reactive() 返回值 | ref() 返回值 | computed() 返回值 |
---|---|---|---|
内部实现类 (简化) | JavaScript Proxy | RefImpl | ComputedRefImpl |
角色 | 数据源 (对象) | 数据源 (通用) | 派生数据 (计算) |
处理数据类型 | 对象、数组 | 任何类型 | 任何类型(计算结果) |
访问方式 | obj.prop | refVar.value | computedVar.value |
由于返回的是类似于 RefImpl 的东西,所以 computed 计算出来的值也是响应式的,计算好以后会触发页面的渲染。
只读
只读意味着你不能对其像这样赋值:
1 | // ✖错误 |
浏览器会警告:
[Vue warn] Write operation failed: computed value is readonly.
如果你想直接 fullName = 'xxx'
那就更不可能了。
仔细看看其声明语句,关键字是 const
,因为其是引用数据类型,更改的是其属性的值。
其实仔细想想就会发现问题了。就拿全名这个案例举例,你更改姓和名,由于你之前定义过计算全名的规则,程序自然能推断出全名的新值。
但是你把全名改了,却没说清楚对应的怎么反过来影响姓和名,那程序怎么能知道?
正因如此,computed()
的第二个用法就来了:
2. 传入包含 get() 函数和 set() 函数的对象,返回值可读写
我们进一步改写之前的赋值语句,这次传入一个对象,里面包含了两个函数。
其中 get()
函数其实等同于只传一个函数时的那个函数,而 set()
则说清了当 .value
被重新赋值的时候,程序应该怎么做。
1 | let fullName = computed({ |
当“原材料”(在这个例子中是姓和名)发生变化的时候,computed()
会自动监测到,并且重新计算出 fullName
的新值。
当 fullName
被赋值的时候,被赋的值会作为形参传递给 set()
。而 set()
中又正好定义了怎么处理由“菜”到“原材料” 的逆向过程(尽管这过程不常用),如此一来“原材料”的值也可以因此发生变化了,到这里,形成了完美的闭环。
特性
缓存性质
即使你多次调用计算出来的值,computed()
也不会反复执行其中的语句。
仅仅当你对“原材料”进行更改以后,其才会触发重新计算和渲染。
个人感受
不得不感叹尤雨溪研发这些工具和函数时候的脑洞,能设计出这么精妙绝伦且实用的系统,真是不简单。
- 标题: Vue3 - 使用 computed() 计算属性响应式更新数据
- 作者: 三葉Leaves
- 创建于 : 2024-09-28 00:00:00
- 更新于 : 2025-06-21 12:01:45
- 链接: https://blog.oksanye.com/38412b1cee96/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。