前两篇写了响应式系统的两个核心模块effect,reactivity,这篇写一下响应式系统在源码中的应用吧。
Computed API
话不多说,首先来看一下利用响应式系统实现computed api,直接上代码注释。
// 3.x的computed 支持设置setter,setter的逻辑很简单,就不多说了,这里删掉了相关的代码。
// 大部分的情况下传入的是一个function, 也就是getter
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
let getter: ComputedGetter<T>
let dirty = true
let value: T
let computed: ComputedRef<T>
// 每个computed中都会维护一个lazy effect实例
const runner = effect(getter, {
lazy: true,
// mark effect as computed so that it gets priority during trigger
computed: true,
// 注意:这个scheduler并不执行effect,仅仅把dirty设置为true
scheduler: () => {
if (!dirty) {
dirty = true
// 触发依赖这个computed的effects
trigger(computed, TriggerOpTypes.SET, 'value')
}
}
})
computed = {
_isRef: true,
// expose effect so computed can be stopped
effect: runner,
get value() {
// 如果数据是脏的,就执行自身的effect获取最新数据,否则直接返回缓存的数据
if (dirty) {
value = runner()
dirty = false
}
// 此时如果有active effect的话,就会被收集到computed的depsMap里面
track(computed, TrackOpTypes.GET, 'value')
return value
}
} as any
return computed
}
我试着用语言描述一下:
- computed和ref一样,返回一个封装的对象,实际上他的_isRef就是true
- 内部维护一个lazy effect,之前也讲过,lazy effect不会立刻执行
- 在第一次获取这个computed的值的时候,执行这个lazy effect,此时active effect就是这个lazy effect,computed里面的响应式数据都会收集到这个lazy effect,
- 当computed的响应式数据发生改变,由于设置了scheduler,并不会去执行这个lazy effect,而是把这个computed标示为脏,然后直接trigger这个computed下所有的effect。(如果computed是在render中调用的话,那么就相当于重新去执行render的过程)
Watch API
去看watch api的源码packages/runtime-core/src/apiWatch.ts
, 会发现好长,其实watch api的原理最简单了,effect方法的作用是监听传入的函数,如果响应式数据发生改变,那么就重新执行这个函数,而effect方法支持scheduler配置,如果传入了scheduler方法,就不会直接重新执行这个effect,我们只需要在shceduler方法里面去执行watch api的回调即可。
终于写完响应式原理相关的了,发现写这种源码解读的文章是真难,不过这个过程需要组织语言去描述,自己还是加深了理解。后面接下去本来打算写compile相关的内容,但是感觉如果像响应式这样写的话也太难面面俱到了,也没有那么多精力。因此打算最后写几篇,内容就是vue3.x相比2.x快在哪里,做了哪些优化。
网友评论