介绍computed
跟之前一样,在介绍computed源码之前还是先说一下他的用法,和具体使用场景。
- computed被称为计算属性,顾名思义,在碰到一些需要大量计算的业务场景的时候我们会选择他,如一些购物车之类的。
- computed只会在相关响应式依赖发生改变时才会重新求值。如果响应式数据发生变化,则computed会依据它所依赖的数据进行重新计算。
- computed具备有缓存机制的。 在看源码之前,先看一个使用computed的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<script>
const { reactive, computed, effect } = VueReactivity
const countInfo = reactive({count: 1})
const personal = computed(() => countInfo.count + 1)
effect(() => {
document.getElementById('app').innerHTML = `数字为: ${personal.value}`
})
setTimeout(() => {
countInfo.count += 1
}, 2000)
</script>
</body>
</html>
如上代码页面一开始显示2,两秒后变为3。这说明computed在其依赖项改变之后会重新计算并触发试图更新,并且在访问computed属性是需要额外加一层.value。
computed源码
computed其实是个effect函数,带着这个结论会更容易理解computed的执行机制。
import { ReactiveEffect, trackEffects, triggerEffects } from "./effect";// 引入的这些函数都是前几篇介绍了的
export const computed = (getterOrOption) => {
let onlyGetter = isFunction(getterOrOption); // 判断传进来的是不是函数(computed接受函数或者对象作为参数)
let getter; // 访问计算属性值的方法
let setter; // 设置计算属性值得方法
if(onlyGetter) {
getter = getterOrOption;
setter = () => {console.log('no set')}
}else {
getter = getterOrOption.get;
setter = getterOrOption.set;
}
return new ComputedRefImpl(getter, setter); // 重点: computed返回一个ComputedRefImpl
}
class ComputedRefImpl{
public effect;
public _dirty = true; // 缓存机制标识,默认应该取值的时刻进行计算
public _value;
public dep = new Set; // 储存计算属性外层的effect依赖
constructor(getter, public setter) {
// 我们将用户的getter放到effect中,这里面的属性就会被effect收集起来
this.effect = new ReactiveEffect(getter, () => {
// 属性发生变化,就会执行调度
if(!this._dirty) {
this._dirty = true;
triggerEffects(this.dep)
}
} // 该函数就是个调度器-scheduler
)
}
get value() {
// 要有依赖收集的能力
trackEffects(this.dep); // 计算属性收集外层的effect,用于计算属性改变触发effect更新
if(this._dirty) {
this._dirty = false;
this._value = this.effect.run();
}
return this._value;
}
set value(newValue) {
this.setter(newValue); // 赋值时走该方法
}
}
如上代码可知,在computed方法中通过ComputedRefImpl类实例化一个对象,当我们在effect中使用计算属性时,就会触发get方法,触发trackEffects就行依赖收集。这是_dirty为true,即会运行effect的run方法。这个方法我们前面讲reactive时就讲过,在run方法中会运行fn方法(这里的getter),并返回fn方法的返回值。在执行fn方法时,又会访问响应式数据(这就是computed的依赖数据必须是响应式的原因),此时会进行依赖收集,而收集到的effect就是当前的computed。当依赖的响应式数据改变了,会触发更新,执行triggerEffects,这是的dep里收集的是computed。在scheduler里又会执行triggerEffects,但这时的dep是computed的外层的effect,所以就触发了视图的更新。
总结:
计算属性依赖的数据必须是响应式的。依赖项变化能触发计算属性重新计算的原理就是依赖数据把computed当作effect一样收集起来,当其值发生改变时,会触发收集的effect(computed)执行,在执行过程中又会执行triggerEffects方法,继而执行计算属性收集的外层effect,最终触发了试图更新。