在昨天的reactive以及effect实现之后,我们需要对代码进一步完善,尽量实现vue3框架的所有功能
今天要实现的是readonly以及stop, 先来介绍一下这俩
目录
readonly
readonly 顾名思义就是只可读,不可改,对比正常的响应式对象,我们可以得到它们的差异
编辑
因为readonly只可读不可改,所以我们对于set改值操作自然可以忽略,并且不需要再收集依赖了,我们依赖收集的目的就是为了在响应式数据发生变化时,触发依赖,达到数据视图实时同步的目的,既然数据不可变,那我们也不需要再收集了,那么我们就可以书写代码了
export function readonly(raw) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key)
return res
},
set(target, key, value) {
return true
}
})
}
stop
stop的功能是当我们调用stop函数时,取消对响应式数据的监听,如何做到这个呢,我们一起来想一下思路,我们如何对数据实现监听的呢,肯定是跟依赖收集和触发有关,因为在我们的dep中存放着很多依赖,当我们触发set时,会将dep中的依赖取出来执行,因此我们可以利用一个思路,当我们调用stop之后,可以将收集的依赖清空,这样它没有取不出来东西就执行不了,就达到了停止监听的效果,顺着这个思路,我们来实现一下
我们上一节提到过在调用run方法时,activeEffect会把reactiveEffect这个类的实例绑定到自身,并且this指向也会变成它,这样我们可以在收集依赖的时候往他身上也存一份,当调用stop函数时,我们也就调用了其上的stop方法来清空依赖
我们希望的效果是这样:
编辑
清空依赖
class reactiveEffect {
private _fn: any
//用于将那个依赖存进去
deps = []
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
return this._fn()
}
stop() {
//这里存放的dep是set结构,所以需要delete来操作
this.deps.forEach((dep: any) => {
dep.delete(this)
})
}
}
let activeEffect
export function effect(fn) {
const _effect = new reactiveEffect(fn)
const runner: any = _effect.run.bind(_effect)
runner._effect = _effect
return runner
}
const targetMap = new WeakMap()
export function track(target, key) {
const depMap = targetMap.get(target)
if (!depMap) {
const depMap = new Map()
targetMap.set(target, depMap)
}
const dep = depMap.get(key)
if (!dep) {
const dep = new Set()
depMap.set(key, dep)
}
dep.add(activeEffect)
//这里我们将依赖存一份
activeEffect.deps.push(dep)
}
export function stop(runner) {
runner._effect.stop()
}
这样我们就实现了stop函数的功能
停止依赖收集
但是这里有个问题,当我们再次改变reactive数据的值的时候,发现还会收集依赖,触发依赖,那我们这样做的话,清除依赖就没有意义了,所以要在源头上停止依赖的收集
我们可以用一个变量shouldTrack来控制是否收集依赖
const targetMap = new WeakMap()
let shouldTrack = true
export function track(target, key) {
//没有依赖时
if (!activeEffect) return
//不应该收集依赖时
if (!shouldTrack) return
const depMap = targetMap.get(target)
if (!depMap) {
const depMap = new Map()
targetMap.set(target, depMap)
}
const dep = depMap.get(key)
if (!dep) {
const dep = new Set()
depMap.set(key, dep)
}
dep.add(activeEffect)
//这里我们将依赖存一份
activeEffect.deps.push(dep)
}
class reactiveEffect {
private _fn: any
//用于将那个依赖存进去
deps = []
active = true
onStop?: () => void
constructor(fn, public schduler?: Function) {
this._fn = fn
this.schduler = schduler
}
run() {
if (!this.active) {
return this._fn()
}
shouldTrack = true
activeEffect = this
const res = this._fn()
shouldTrack = false
return res
}
stop() {
if (this.active) {
this.active = false
//这里存放的dep是set结构,所以需要delete来操作
clearEffect(this)
if (this.onStop) {
this.onStop()
}
}
}
}
当我们调用stop时,active会变成false,所以下一次执行fn的时候,我们会判断一下active的状态,如果时是false我们会直接返回fn的值,不做依赖收集。 我们这里声明了一个shouldTrack变量,
当我们没有调用stop时,active是true,因此run函数执行时会做依赖收集并且每一次收集前会将shouldTrack的状态初始化为true,再依赖收集结束后,会将shouldTrack状态变成false,这样如果我们调用stop时,shouldTrack状态一直都是false,那我我们执行track函数的时候都会跳过依赖的收集,就实现了在源头上控制了依赖的停止收集
优化代码
对于stop函数,我们调用一次后就已经清空了依赖,有的用户可能一直调用,这样我们后面再清空都是undefined了,没有必要,所以我们可以拿个active标记,来控制是否调用stop
class reactiveEffect {
private _fn: any
//用于将那个依赖存进去
deps = []
active = true
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
return this._fn()
}
stop() {
if (this.active) {
this.active = false
//这里存放的dep是set结构,所以需要delete来操作
clearEffect(this)
}
}
}
function clearEffect(effect) {
effect.deps.forEach((dep: any) => {
dep.delete(effect)
})
}
写在最后
本章讲解了如何实现readonly以及stop,主要是思路的实现,可能没有测试不是很直观,之后将jest测试也加入进来,通过白盒测试,从TDD(测试驱动开发)的思想更加直观的来理解vue3的源码