Vue 3.0 计算属性的实现原理分析

326 阅读3分钟
Vue 3.0 系列文章

Vue 3.0组件的渲染流程

Vue 3.0组件的更新流程和diff算法详解

揭开Vue3.0 setup函数的神秘面纱

Vue 3.0 Props的初始化和更新流程的细节分析

Vue3.0 响应式实现原理分析

Vue 3.0 计算属性的实现原理分析

Vue3.0 常用响应式API的使用和原理分析(一)

Vue3.0 常用响应式API的使用和原理分析(二)

Vue 3.0 Provide和Inject实现共享数据

Vue 3.0 Teleport的使用和原理分析

Vue3侦听器和异步任务调度, 其中有个神秘角色

Vue3.0 指令

Vue3.0 内置指令的底层细节分析

Vue3.0 的事件绑定的实现逻辑是什么

Vue3.0 的双向绑定是如何实现的

Vue3.0的插槽是如何实现的?

探究Vue3.0的keep-alive和动态组件的实现逻辑

Vuex 4.x

Vue Router 4 的使用,一篇文章给你讲透彻

我们在上一章节介绍了响应式的原理,本文我们来探讨一下Vue 3.0计算属性的实现原理。如果没有阅读过上一篇文章,建议请先阅读上一篇文章再来看本文,否则可能会有些迷糊。

使用方法

我们先来看看计算属性的用法:

计算属性

代码说明: 使用computedAPI 定义了一个计算属性computedCounter,它有一个get方法返回的值是counter这个响应式数据的平方,有一个set方法,可以对counter设置值;

实现的效果:counter的值变化,computedCounter的值也随之变化。

实现逻辑

computed API

我们先来看看computed是如何处理传入的参数的:

computed

代码解释:

  1. computed接收两种形式的传参,第一种是只传一个函数(例如:computed(() => counter.value * counter.value)),第二种是传入一个包含getset两个函数的对象;
  2. 第一种传参会将函数赋值给getter方法,第二种传参会将getset两个函数赋值给gettersetter方法;
  3. gettersetter方法用ComputedRefImpl封装起来;
  4. 返回ComputedRefImpl这个对象。

ComputedRefImpl

接下来我们看看ComputedRefImpl内部的实现:

ComputedRefImpl

代码解释:

  1. 构造函数中直接将传入的setter赋值给_setter,利用ReactiveEffectgetter封装起来,然后将封装的结果赋值给effect
  2. 提供了get函数和set函数。

ComputedRefImpl##get

get

get函数的调用时机肯定是获取对应的值时候调用。 我们前面的例子就是渲染子树节点处理<p>{{ computedCounter }}</p>时需要获取对应的computedCounter的值。

  1. 调用get函数时候收集依赖,收集的是组件渲染函数componentUpdateFn
  2. 第一次执行时_dirtytrue,所以会执行effect.run()。这个函数的执行后的逻辑是:
    • 先将effect压入effectStack栈中,并将activeEffect指向effect
    • 执行getter方法即counter.value * counter.value;
    • 当调用counter.value时会触发counterget函数,counter然后收集依赖,此时收集的依赖是effect函数。
    在这里插入图片描述
  3. 得到新值。

ComputedRefImpl##set 修改响应式数据触发的逻辑

set value(newValue: T) {
    this._setter(newValue)
}

当修改counter的值时会分发依赖,由于counter有两个依赖:

  • computedCountereffect函数:
if (!this._dirty) {
  this._dirty = true
  triggerRefValue(this)
}

执行的逻辑就是将_dirty设置为true,然后触发依赖也就是组件的副作用渲染函数

注意:这个_dirty设置为true是关键。

  • 组件的副作用渲染函数

组件的副作用渲染函数会触发组件的重新渲染,然后当获取computedCounter值的时候会调用get函数,此时_dirty设置为true, 和第一渲染一样,会调用effect.run()重新计算新值。

if (self._dirty) {
    self._dirty = false
    elf._value = self.effect.run()!
}

此时和第一次渲染一样,就进入了新的一轮依赖收集。循环往复,周而复始,直到程序结束。

总结

  • 如果依赖的数据不变,计算属性不会重新计算,直接使用缓存的值,虽然这种性能的提升不明显,但是这种优化思维时刻需要具备的。

  • 此外,如果不去获取计算属性,也不会去计算对应的值,具有延迟获取的特性。