vue3 的源码结构如下
├── compiler-core // 编译
├── compiler-dom //
├── compiler-sfc // .vue文件编译
├── compiler-ssr // 服务端编译
├── global.d.ts
├── reactivity // 响应式核心
├── runtime-core // 运行时
├── runtime-dom // web运行时
├── runtime-test
├── server-renderer // 服务端渲染
├── shared
├── size-check
├── template-explorer
└── vue
@vue/reactivity
是vue3响应式的核心,也是本文要分析的
先看一段摘自官方源码的测试用例:
it('should observe basic properties', () => {
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
})
问题:dummy的值什么时候被改变的?
关键概念:
- reactivity: 响应式对象
- effect: reactivity改变后所产生的效果
- computed和ref是基于上述两者的封装
简化流程:
- 1.定义reactiviy对象
- 2.收集依赖
- 3.改变reactivity属性
- 4.触发effect,产品效果
上述测试用例具体过程分析:
- 1.定义reactivity对象: 通过es6的proxy定义对象的set,get方法:reactivity({ num: 0 }),返回一个reactivity对象
- 2.定义effect:定义effect(() => (dummy = counter.num))
- 3.收集依赖: 定义effect后会立即执行 () => (dummy = counter.num),触发reactivity对象的get方法收集依赖,这里收集的是要触发的effect
- 4.改变reactivity的属性:执行counter.num = 7
- 5.触发effect:触发reactivity对象的set方法,执行effect: () => (dummy = counter.num)
经过上面的分析,应该就知道dummy的值什么的时候被改变的了
代码分析:
// 定义reactivity对象的时候会设置reactivity对象的get,set代理,如下截取了部分关键代码
// reactive.ts
// reactivity对象属性的get代理,收集依赖关键在track方法
function get(target: Target, key: string | symbol, receiver: object) {
// ...省略
if (!isReadonly) {
// 收集依赖
track(target, TrackOpTypes.GET, key)
}
// ...省略
}
// reactivity对象属性的set代理,触发effect的关键的在 trigger方法
function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 触发effect
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
// effect.ts
// track方法
function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// ...省略部分
}
// effect.ts
// trggier 方法
function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
// ...省略
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
// effect.ts
// effect 长什么样?
function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
// 创建effect后,立即执行effect,就完成收集依赖
if (!options.lazy) {
effect()
}
return effect
}
// effect.ts
// 通过createReactiveEffect创建effect
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}