(3.1)vue3 手摸手实现mini版reactive.md
专栏前言
在上一节,我们完成了vue3的reactive的核心源码解读,总的来说还是非常复杂,文章的表现能力有限,我想可能有很多同学无法完全理解其精髓,所以在本节,我将带领大家完成mini版本源码的输出。
仅保留最核心逻辑,极大减低阅读难度,200行代码实现reactive + effect,话不多说,我们直接开始!
简易版vue3仓库地址,还请大家不要吝啬star,留个标记,下次迷路~
逻辑图
逻辑流程
reative初始化
将reactive处理为proxy,同时预先声明set get方法,赋值、取值均通过Reflect完成,get中存在track(依赖收集),set中存在trigger(依赖触发),完成reactive的初始化。
effect初始化(依赖收集)
cb = callback = 回调函数 effect(() => {}) // () => {} 就是cb
初始化effect函数,通过一个类ReactiveEffect运行其cb,同时将当前cb存储到公共变量,cb中读取了reactive的属性,进而触发proxy的get,同时完成track(依赖收集),让reative收集到存储在公共变量中的effect的cb,至此完成依赖收集。
1
| 重点:reactive - key - effect // 依赖收集完成后,将会形成这样的从上到下的可追溯关系
|
reactive改变(依赖触发)
若干时间后,reactive属性发生变化,触发reactive属性的赋值操作,进而触发proxy的set事件,同时完成trigger(依赖触发),根据指定的reative + key,找到特定effect运行,完成依赖触发,形成响应式。
1
| 重点:reactive + key 找到指定effect,进而完成触发
|
具体逻辑
proxy处理
经过真实的源码分析之后,我们都知道reactive实际上就是proxy,我们仿照源码的格式,将reactive经过proxy处理后返回就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const reactiveMap = new WeakMap<object, any>()
export function reactive(target: object) { return createReactiveObject(target, mutableHandlers, reactiveMap) }
function createReactiveObject( target: object, baseHandlers: ProxyHandler<object>, proxyMap: WeakMap<object, any> ) { const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } const proxy = new Proxy(target, baseHandlers) proxyMap.set(target, proxy) return proxy }
|
get set函数编写
以上代码我们完成了变量的proxy处理,为了完成后续的响应式,我们需要预先声明好get set函数,我们依旧仿照源码格式,并只保留核心逻辑,get阶段返回结果,并触发(依赖收集)track,set阶段通过Reflect完成赋值,并触发(依赖触发)trigger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| export const mutableHandlers: ProxyHandler<object> = { get, set, }
const get = createGetter() const set = createSetter()
function createGetter() { return function get(target: object, key: string, receiver: object) { const res = Reflect.get(target, key, receiver) track(target, key)
if (isObject(res)) { return reactive(res) } return res } }
function createSetter() { return function set(target: object, key: string, newValue: unknown, receiver: object) { const res = Reflect.set(target, key, newValue, receiver) trigger(target, key, newValue) return res } }
|
effect实现
effect的核心的实现,就是在运行effect的时候保存当前的this,以便于后续流程中的依赖收集,所以其核心代码非常简单,保证一下2点即可。
- 运行effect本身
- 保存effect的fn到activeEffect即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export function effect<T = any>(fn: () => T) { const _effect = new ReactiveEffect(fn) _effect.run() }
export let activeEffect: ReactiveEffect | undefined
export class ReactiveEffect<T = any> { constructor(public fn: () => T) {} run() { try { activeEffect = this return this.fn() } finally { activeEffect = undefined } } }
|
依赖收集(track)
按照时序,effect函数初始化阶段会执行,effect函数本身也会被保存到activeEffect中,同时触发effect中的reactive中的get事件,进而触发track,我们在track中完成 reactive- key - effect之间关系的构建,确保以后可以在set阶段找到指定的effet的fn即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export function track(target: object, key: string) { if (!activeEffect) { return } let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (!dep) { dep = createDep() depsMap.set(key, dep) } trackEffects(dep) }
function trackEffects(dep: Dep) { dep.add(activeEffect!) }
|
依赖触发(trigger)
若干时间后,reative中的某个属性发生了变化,也就会发生set事件,这时候其实就很简单了,我们只需要通过reactive - key找到对应的effect的fn,然后执行即可。
这就形成了我们看到的“响应式”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export function trigger(target: object, key: string, newValue: unknown) { let depsMap = targetMap.get(target) if (!depsMap) { return } const dep: Dep | undefined = depsMap.get(key)
if (!dep) { return } triggerEffects(dep) }
function triggerEffects(dep: Dep) { const effects = [...dep] for (const effect of effects) { triggerEffect(effect) } }
function triggerEffect(effect: ReactiveEffect) { effect.fn() }
|
最后
到此为止,我们简易版的reactive + effect的全部源码就完成了,虽然vue3的源码很复杂,但是我们抽丝剥茧,仅保留核心逻辑,大幅降低vue3源码阅读的难度,让绝大多数的前端开发者都可以读懂核心实现~
最后,建议大家clone源码到本地实际运行一下,静下心来一步一步调试,将简易版逻辑弄明白,有兴趣的可以在看看正式的vue3源码,然后在简历上留下浓墨重彩的一笔~