社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
作者 | Jrain
来源 | https://juejin.im/post/5d9da45af265da5b8072de5d
文章涉及到的代码我也已经上传到仓库,结合代码阅读本文会更为流畅哦!
yarn dev reactivity
,然后进入 packages/reactivity
目录找到产出的 dist/reactivity.global.js
文件。index.html
,写入如下代码:<script src="./dist/reactivity.global.js"></script>
<script>
const { reactive, effect } = VueObserver
const origin = {
count: 0
}
const state = reactive(origin)
const fn = () => {
const count = state.count
console.log(`set count to ${count}`)
}
effect(fn)
</script>
state.count++
,便可看到输出 set count to 1
。reactive()
函数把 origin
对象转化成了 Proxy 对象 state
;使用 effect()
函数把 fn()
作为响应式回调。当 state.count
发生变化时,便触发了 fn()
。接下来我们将以这个例子结合上文的流程图,来讲解这套响应式系统是怎么运行的。origin
对象转化成响应式的 Proxy 对象 state
。fn()
作为一个响应式的 effect 函数。Object.defineProperty()
,改写了对象的 getter/setter,完成依赖收集和响应触发。reactive()
函数:export function reactive(target) {
const observed = new Proxy(target, handler)
return observed
}
完整代码在 reactive.js。这里的handler
就是改造 getter/setter 的关键,我们放到后文讲解。
fn()
被 effect()
包裹之后,就会变成一个响应式的 effect 函数,而 fn()
也会被立即执行一次。fn()
里面有引用到 Proxy 对象的属性,所以这一步会触发对象的 getter,从而启动依赖收集。export function effect (fn) {
// 构造一个 effect
const effect = function effect(...args) {
return run(effect, fn, args)
}
// 立即执行一次
effect()
return effect
}
export function run(effect, fn, args) {
if (effectStack.indexOf(effect) === -1) {
try {
// 往池子里放入当前 effect
effectStack.push(effect)
// 立即执行一遍 fn()
// fn() 执行过程会完成依赖收集,会用到 effect
return fn(...args)
} finally {
// 完成依赖收集后从池子中扔掉这个 effect
effectStack.pop()
}
}
}
fn()
触发了 Proxy 对象的 getter 的时候。简单来说,只要执行到类似 state.count
的语句,就会触发 state 的 getter。state
~~代理前的对象origin
,而 value 则是该对象所对应的 depsMap。count
),而 value 则是触发过该属性值所对应的各个 effect。const state = reactive({
count: 0,
age: 18
})
const effect1 = effect(() => {
console.log('effect1: ' + state.count)
})
const effect2 = effect(() => {
console.log('effect2: ' + state.age)
})
const effect3 = effect(() => {
console.log('effect3: ' + state.count, state.age)
})
{ target -> key -> dep }
的对应关系就建立起来了,依赖收集也就完成了。代码如下:export function track (target, operationType, key) {
const effect = effectStack[effectStack.length - 1]
if (effect) {
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (dep === void 0) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(effect)) {
dep.add(effect)
}
}
}
{ count: 0, age: 18 }
的 Proxy,并构造了三个 effect。在控制台上看看效果:effects
和 computedEffects(计算属性)
队列中,最后通过 scheduleRun()
挨个执行里面的 effect。export function trigger (target, operationType, key) {
// 取得对应的 depsMap
const depsMap = targetMap.get(target)
if (depsMap === void 0) {
return
}
// 取得对应的各个 dep
const effects = new Set()
if (key !== void 0) {
const dep = depsMap.get(key)
dep && dep.forEach(effect => {
effects.add(effect)
})
}
// 简化版 scheduleRun,挨个执行 effect
effects.forEach(effect => {
effect()
})
}
这里的代码没有处理诸如数组的 length 被修改的一些特殊情况,感兴趣的读者可以查看 vue-next 对应的源码,或者这篇文章,看看这些情况都是怎么处理的。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!