专栏前言
在上一节,我们完成了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,至此完成依赖收集。
重点:reactive - key - effect // 依赖收集完成后,将会形成这样的从上到下的可追溯关系
reactive改变(依赖触发)
若干时间后,reactive属性发生变化,触发reactive属性的赋值操作,进而触发proxy的set事件,同时完成trigger(依赖触发),根据指定的reative + key,找到特定effect运行,完成依赖触发,形成响应式。
重点:reactive + key 找到指定effect,进而完成触发
具体逻辑
proxy处理
经过真实的源码分析之后,我们都知道reactive实际上就是proxy,我们仿照源码的格式,将reactive经过proxy处理后返回就好了。
// 缓存proxy
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
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即可
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即可。
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,然后执行即可。
这就形成了我们看到的“响应式” 。
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源码,然后在简历上留下浓墨重彩的一笔~