作者:秦志英
前言
上一篇文章中咱们剖析了Vue3响应式的整个流程,本篇文章咱们将剖析Vue3中的computed计算属性是如何实现的。
在Vue2中咱们曾经对计算属性理解的很分明了,在Vue3中提供了一个computed
的函数作为计算属性的API,上面咱们来通过源码的角度去剖析计算属性的运行流程。
computed
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
- 在最开始应用函数重载的形式容许
computed
函数承受两种类型的参数:第一种是一个getter函数, 第二种是一个带get和set的对象。 - 接下就是在函数外部依据传入的不同类型的参数初始化函数外部的
getter
和setter
函数,如果传入的是一个函数类型的参数,那么getter就是这个函数,setter就是一个空的操作,如果传入的参数是一个对象,则getter就等于这个对象的get函数,setter就等于这个对象的set函数。 - 在函数的结尾返回了一个
new ComputedRefImpl
,并将后面咱们标准化后的参数传递给了这个构造函数。
上面咱们就来剖析一下ComputedRefImpl
这个构造函数。
ComputedRefImpl
class ComputedRefImpl<T> {
// 缓存后果
private _value!: T
// 从新计算开关
private _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 对传入的getter函数进行包装
this.effect = effect(getter, {
lazy: true,
// 调度执行
scheduler: () => {
if (!this._dirty) {
this._dirty = true
// 派发告诉
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
}
// 拜访计算属性的时候 默认调用此时的get函数
get value() {
// 是否须要从新计算
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
// 拜访的时候进行依赖收集 此时收集的是拜访这个计算属性的副作用函数
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
ComputedRefImpl类在外部保护了_value
和_dirty
这两个十分重要的公有属性,其中_value
应用用来缓存咱们计算的后果,_dirty
是用来管制是否须要重现计算。接下来咱们来看一下这个函数的外部运行机制。
- 首先构造函数在初始化的时候应用了
effect
函数对传入getter
进行了一层包装(上一篇文章中咱们剖析过effect函数的作用就是将传入的函数变成可响应式的副作用函数)
,然而这里咱们在effect中传入了一些配置参数,还记得后面咱们剖析trigger函数的时候有这一段代码:
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
当属性值产生扭转之后,会触发trigger
函数进行派发更新,将所有依赖这个属性的effect函数循环遍历,应用run
函数执行effect,如果effect的参数中配置了scheduler
,则就执行scheduler
函数,而不是执行依赖的副作用函数。当计算属性依赖的属性发生变化的时候,回执行包装getter
函数的effect, 然而因为配置了scheduler
函数,所以真正执行的是scheduler
函数,在scheduler
函数中并没有执行计算属性的getter
函数求取新值,而是将_dirty
设置为false,而后告诉依赖计算属性的副作用函数进行更新, 当依赖计算属性的副作用函数收到告诉的时候就会拜访计算属性的get函数,此时会依据_dirty
值来确定是否须要从新计算。
回到咱们的这个构造函数中,只须要记得咱们在构造函数初始化三个重要的点:第一:对传入的getter函数应用effect函数进行包装。第二:在应用effect包装的过程中,咱们会执行getter函数,此时getter函数执行过程中对于拜访到的属性会将以后的这个计算属性收集到对应的依赖汇合中, 第三:传入了配置参数lazy
和scheduler
,这些配置参数在以后的这个计算属性所订阅的属性产生扭转的时候,用来管制计算属性的调度机会。
- 接着咱们持续剖析
get value
,当咱们拜访计算属性的值时候实际上拜访的就是这个函数的返回值, 它会依据_dirty
的值来判断是否须要从新计算getter函数,_dirty
为true须要从新执行effect函数,并将effect
的值置为false,否则就返回之前缓存的_value
值。在拜访计算属性值的阶段会调用track
函数进行依赖收集,此时收集的是拜访计算属性值的副作用函数, key始终是vlaue。 - 最初就是当设置计算属性的值的时候会执行set函数,而后调用咱们传入的
_setter
函数。
示例流程
至此计算属性的执行流程就剖析结束了,咱们来联合一个示例来残缺的过一遍整个流程:
<template>
<div>
<button @click="addNum">add</button>
<p>计算属性:{{computedData}}</p>
</div>
</template>
<script>
import { ref, watch,reactive, computed } from 'vue'
import { effect } from '@vue/reactivity'
export default {
name: 'App',
setup(){
const testData = ref(1)
const computedData = computed(() => {
return testData.value++
})
function addNum(){
testData.value += 10
}
return {
addNum,
computedData
}
},
}
</script>
上面是一张流程图,当点击页面中的按钮扭转testData的value值时,产生的变动流程就是上面的红线局部。
- 首先初始化页面的时候,testData通过ref()之后变成响应式数据,会对拜访testData.value的值进行依赖收集,当testData.value的值发生变化的话,会对依赖这个值的依赖汇合进行派发更新
- computed中传入了一个getter函数,getter函数外部有对testData.value的拜访,此时以后的这个计算属性的副作用函数就订阅了testData.value的值,computed返回了一个值,而页面中的组件有对computed返回值的拜访,页面的渲染副作用函数就订阅了computed的返回值,所以这个页面中有两个依赖汇合。
- 当咱们点击页面中的按钮,会扭转testData.value的值,此时会告诉订阅计算属性的副作用函数进行更新操作,因为咱们在生成计算属性副作用的时候配置了
scheduler
,所以执行的是scheduler
函数,scheduler
函数并没有立刻执行getter函数进行从新计算,而是将ComputedRefImpl
类外部的公有变量_dirty
设置为true,而后告诉订阅以后计算属性的副作用函数进行更新操作。 - 组件中的渲染副作用函数执行更新操作的时候会拜访到
get value
函数,函数外部会依据_dirty值来判断是否须要从新计算,因为后面的scheduler
函数将_dirty设置为true所以此时会调用getter函数的副作用函数effect,这个时候才会从新计算并将后果返回,页面数据更新。
总结
计算属性两个最大的特点就是
- 延时计算 计算属性所依赖的值产生扭转的时候并不会立刻执行getter函数去从新计算新的后果,而是关上从新计算的开关并告诉订阅计算属性的副作用函数进行更新。如果以后的计算属性没有依赖汇合就不执行从新计算逻辑,如果有依赖触发计算属性的get,这个时候才会调用this.effect()进行从新计算。
- 缓存后果 当依赖的属性没有产生扭转的,拜访计算属性会返回之前缓存在
_value
中的值。
对 Electron 感兴趣?请关注咱们的开源我的项目 Electron Playground,带你极速上手 Electron。
咱们每周五会精选一些有意思的文章和音讯和大家分享,来掘金关注咱们的 晓前端周刊。
咱们是好将来 · 晓黑板前端技术团队。
咱们会常常与大家分享最新最酷的行业技术常识。
欢送来 知乎、掘金、Segmentfault、CSDN、简书、开源中国、博客园 关注咱们。
发表回复