实现 effect 函数
第 1 步
packages/reactivity/src/effect.ts
// 用户在调用 effect 的时候,传进来的是个 fn 函数
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
// 需要记录 effect 依赖了哪些属性,同时让属性记录依赖了哪个effect
class ReactiveEffect {
// 表示是否是响应式
active = true
// 表示 effect 依赖了哪些属性,同时当前属性依赖了哪个 effect
deps = []
constructor(public fn) {
}
// 调用 run 的时候,让 fn 执行
run() {
if (!this.active) {
// 如果不是响应式的,则直接执行 fn 函数,不用往下走了
return this.fn()
}
}
}
第 2 步
问题描述:需要考虑怎么让 effect 和属性相互记录,同时,需要考虑 effect 函数的用法,如下的伪代码
effect(() => {
state.name
effect(() => {
state.age
})
state.school
})
假如最外层的 effect 是 effect1,里面的 effect 是 effect2,那么 name 需要和 effect1 相互记录,age 需要和 effect2 相互记录,school 需要和 effect1 相互记录,即访问 name 和 school 的时候,触发的是 effect1,访问 age 的时候,触发的是 effect2。
这个时候可以使用栈结构来存储,当执行 effect1 的时候,先将 effect1 放入到栈中,访问 name 属性的时候,则将栈顶的 effect(即 effect1)和 name 相互记录;当执行 effect2 的时候,将 effect2 再放入栈中,访问 age 属性的时候,同样找栈顶的 effect(即 effect2)和 age 相互记录;当 effect2 执行完的时候,再将 effect2 从栈顶弹出;接着访问 school 属性,同样找栈顶的 effect(即 effect1)和 school 相互记录。这样就完成了 effect 和属性相互记录的过程。
packages/reactivity/src/effect.ts
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
// --------------------------新增 start----------------------------
// 存储相互依赖的 effect 和属性
let effectStack = []
// 当前激活的 effect
let activeEffect = null
// --------------------------新增 end----------------------------
// 需要记录 effect 依赖了哪些属性,同时让属性记录依赖了哪个effect
class ReactiveEffect {
// 表示是否是响应式
active = true
// 表示 effect 依赖了哪些属性,同时当前属性依赖了哪个 effect
deps = []
constructor(public fn) {
}
// 调用 run 的时候,让 fn 执行
run() {
if (!this.active) {
// 如果不是响应式的,则直接执行 fn 函数,不用往下走了
return this.fn()
}
// --------------------------新增 start----------------------------
// 避免出现同一个 effect 被收集多次
if (!effectStack.includes(this)) {
try {
effectStack.push(this)
activeEffect = this
// 此时会执行用户传入的 fn 函数,则会去取值,也就是走到了 reactive 函数中 proxy 的 get 函数中(依赖收集)
return this.fn()
} finally {
// 当当前 effect 函数执行完之后,需要将该 effect 函数从栈中删除,同时将当前激活的 effect 重新赋值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
// --------------------------新增 end----------------------------
}
}
第 3 步
问题描述:由于第 2 步中调用 fn() 函数的时候,会进入到 get 函数中,所以可以在 get 函数中完成依赖收集
依赖收集是靠 track 函数来完成的
packages/reactivity/src/effect.ts
...
// --------------------------新增 start----------------------------
// 判断是否是在 effect 中访问了对象的属性
export function isTracking() {
return activeEffect !== undefined
}
// 一个属性可能对应多个 effect,一个 effect 也可能对应多个属性,所以他们两者之间的关系是多对多关系
// 所以数据结构的格式就是 {对象:{属性: [effect1, effect2]}},即上面的 targetMap
// 用来存储 effect 和属性
// 保存的是指定对象到指定属性再到指定 effect 函数的绑定
const targetMap = new WeakMap()
export function track(target, key) {
// 只有写在 effect 中的属性才需要收集,所以这里判断一下
if (!isTracking) {
return
}
// 判断 weakmap 中是否已经保存过该对象(target 在 weakmap 中是 key)
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 判断 weakmap 中是否已经保存过当前属性(当前属性在 weakmap 中是 value)
let dep = depsMap.get(key)
if (!dep) {
// 用 Set 类型是为了防止重复
dep = new Set()
depsMap.set(key, dep)
}
// 判断当前 key 中是否已经有了当前的 effect
const shouldTrack = dep.has(activeEffect)
if (!shouldTrack) {
dep.add(activeEffect)
// 让 effect 来记录当前属性
activeEffect.deps.push(dep)
}
console.log(targetMap);
}
// --------------------------新增 end----------------------------
packages/reactivity/src/reactive.ts
...
const mutableHandlers:ProxyHandler<Record<any, any>> = {
// recevier 表示代理对象的本身,即 createdReactiveObject 函数中的 proxy
get(target, key, recevier) {
// 判断 target 对象中是否有 __v_isReactive 属性,如果有,则表示该对象被代理过
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
// --------------------------新增 start----------------------------
track(target, key)
// --------------------------新增 end----------------------------
// 等价于 target[key]
// 使用 Reflect 主要是他会对一些边界条件做一些处理,如取不到该属性,同时设置值的时候,还会自动返回 true 或者 false
const res = Reflect.get(target, key, recevier)
return res
},
...
}
...
第 4 步
问题描述:上面 track 函数是访问属性,下面 trigger 函数是在设置或修改属性的时候用的。trigger
函数是完成依赖触发
packages/reactivity/src/effect.ts
...
// --------------------------新增 start----------------------------
export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 说明当前修改的对象并没有依赖任何 effect
return
}
const deps = []
if (key !== undefined) {
deps.push(depsMap.get(key))
}
let effects = []
for (const dep of deps) {
effects.push(...dep)
}
for (const effect of effects) {
if (effect !== activeEffect) {
effect.run()
}
}
}
// --------------------------新增 end----------------------------
...
packages/reactivity/src/reactive.ts
const mutableHandlers:ProxyHandler<Record<any, any>> = {
...
set(target, key, value, recevier) {
// --------------------------新增 start----------------------------
const oldValue = (target as any)[key]
// 等价于 target[key] = value,同时会返回 true
const res = Reflect.set(target, key, value, recevier)
// 如果值变化了再执行
if (oldValue !== value) {
trigger(target, key)
}
return res
// --------------------------新增 end----------------------------
}
}
packages/reactivity/example/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
const {reactive, effect} = VueReactivity
const obj = {
name: 'xiaoming',
age: 12
}
const state1 = reactive(obj)
effect(() => {
console.log(state1.name);
})
setTimeout(() => {
state1.name = 'xiaohong'
}, 1000)
</script>
</body>
</html>
效果如下:
第 5 步
问题描述:effect 函数需要默认会执行一次,并且,会返回一个函数,还可以手动强制重新调用一下
packages/reactivity/src/effect.ts
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
// --------------------------新增 start--------------------------
return _effect.run.bind(_effect)
// --------------------------新增 end----------------------------
}
packages/reactivity/example/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
const {reactive, effect} = VueReactivity
const obj = {
name: 'xiaoming',
age: 12
}
const state1 = reactive(obj)
const runner = effect(() => {
console.log(state1.name);
})
runner()
</script>
</body>
</html>
效果如下:
第 6 步
问题描述:实现让 effect 停止响应式,让 effect 和属性解耦,如组件销毁的时候就可以用这个
packages/reactivity/src/effect.ts
class ReactiveEffect {
...
// --------------------------新增 start----------------------------
// 让 effect 和属性解耦
stop() {
if (this.active) {
clearUpEffect(this)
this.active = false
}
}
}
function clearUpEffect(effect) {
const {deps} = effect
for (const dep of deps) {
// 删除属性对应的 effect
dep.delete(effect)
}
}
// --------------------------新增 end----------------------------
packages/reactivity/example/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
const {reactive, effect} = VueReactivity
const obj = {
name: 'xiaoming',
age: 12
}
const state1 = reactive(obj)
const runner = effect(() => {
console.log(state1.name);
})
runner.effect.stop()
setTimeout(() => {
state1.name = 'xiaohong'
}, 1000)
</script>
</body>
</html>
效果如下:
推荐文章: