(3.1)vue3 手摸手实现mini版reactive.md

专栏前言

​ 在上一节,我们完成了vue3reactive的核心源码解读,总的来说还是非常复杂,文章的表现能力有限,我想可能有很多同学无法完全理解其精髓,所以在本节,我将带领大家完成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的属性,进而触发proxyget,同时完成track(依赖收集),让reative收集到存储在公共变量中的effectcb,至此完成依赖收集。

1
重点:reactive - key - effect // 依赖收集完成后,将会形成这样的从上到下的可追溯关系

reactive改变(依赖触发)

​ 若干时间后,reactive属性发生变化,触发reactive属性的赋值操作,进而触发proxyset事件,同时完成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
// 缓存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阶段返回结果,并触发(依赖收集)trackset阶段通过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源码,然后在简历上留下浓墨重彩的一笔~