阅读源码原则
- 简化和调试,掌握核心流程
- 不被过多细节束缚,只专注于主线
reactive 和 effect 方法的源码实现
1 )用户代码 示例,业务代码如下:
<script src='../../dist/vue.global.js'></script>
<body>
<div id='app'></div>
</body>
<script>
const { reactive, effect } = Vue
const obj = reactive({
name: '张三'
})
effect(()=>{
document.querySelector('#app').innerText = obj.name // 注意,这里是 getter 行为
})
const timer = setTimeout(() => {
clearTimeout(timer)
obj.name = '李四' // 这里是 setter 行为
}, 2000)
</script>
在上述代码中,我们在 reactive 中传入了一个 对象 构建了一个 obj 对象,在 effect 中 传入了一个回调函数
我们想要从用户示例代码中,追根溯源,看下 reactive 和 effect 内部 到底是怎么执行的
2 )进行reactive的debug
-
现在chrome的Sources面板的Pages子面板找到 reactive.ts 中,找到 reactive() 方法,打上一个断点
-
刷新页面,走到了断点处, 程序是这样走的:
export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. // 这里跳过 if (isReadonly(target)) { return target } // 直接走到这里 return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) } -
代码进入到 createReactiveObject 方法
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { // 这里会跳过 if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object // 这里匹配不到会跳过 if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy // 这里从 proxyMap 中读取一个实例,这里 proxyMap 对应一个 WeakMap, 我们暂时把它当成一个普通的map对象 const existingProxy = proxyMap.get(target) // 这里读取不到,也会跳过 if (existingProxy) { return existingProxy } // only specific value types can be observed. // 这里尝试读取 targetType, 目前值是 1 const targetType = getTargetType(target) // 这里不匹配也可以直接跳过 if (targetType === TargetType.INVALID) { return target } // 这里是重点:创建一个代理对象 const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 主要看这里的三目运算, 判断当前 targetType 是否是指定值 ) // 这里将target存到map中建立一个对应关系 proxyMap.set(target, proxy) return proxy // 最终return 这个 proxy 对象 } -
其中上述说的 targetType 是一个枚举对象
const enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 } -
我们跳到 baseHandlers 方法中,看发生了什么, 往上查找代码,它是 createReactiveObject 的第三个参数
- 也就是 reactive 方法 中 return createReactiveObject 的 第三个参数:mutableHandlers
- 而 mutableHandlers 是 baseHandlers.ts 中定义的
export const mutableHandlers: ProxyHandler<object> = { get, set, deleteProperty, has, ownKeys } - 在上述代码中有两个关键的属性,get和set,这里我们要考虑到 Proxy 中的 getter 和 setter 行为被触发的时机
- 我们需要注意,我们自己的业务代码中的 getter 和 setter, 一旦触发了它们,下面两个函数就会被执行
- 先来看下 get
const get = /*#__PURE__*/ createGetter() - 这个 get 是一个 createGetter 的返回,return 了一个 get函数, 就是上述Handlers中的get
function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { // 下面一些列的判断都会被跳过 if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly } else if (key === ReactiveFlags.IS_SHALLOW) { return shallow } else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) ) { return target } // 这里不匹配也会被跳过 const targetIsArray = isArray(target) // 这里不匹配也会被跳过 if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver) } // 这里 Reflect.get 返回的是 receiver 的 key const res = Reflect.get(target, key, receiver) // 这里不匹配也会被跳过 if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res } // 这里会执行,注意下这里的 track 函数,主要为了 依赖收集和依赖触发 // 这里的 track 是一个依赖收集的过程 if (!isReadonly) { track(target, TrackOpTypes.GET, key) } if (shallow) { return res } if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value } if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res) } // 最后这里会执行,返回 res 结果 return res } }
- 先来看下 get
-
我们看下上述 getter 中的 track 发生了什么, 在 effect.ts 中
export function track(target: object, type: TrackOpTypes, key: unknown) { // 注意这里,activeEffect 在 ReactiveEffect类 run函数里进行的赋值;shouldTrack 也被赋值为 true,所以这里会执行 if (shouldTrack && activeEffect) { // 这里的 targetMap 也是一个 WeekMap, 这里的get 是 undefined let depsMap = targetMap.get(target) // 因为是 undefined,所以,这里会执行,对 targetMap 进行一个赋值操作 if (!depsMap) { targetMap.set(target, (depsMap = new Map())) // 这时候,这里的 targetMap 就有值了,以 target 这个对象为key, 一个新Map对象作为value } // 这里,同上,也是取不出来值的,这里同样是 undefined let dep = depsMap.get(key) // 这里会执行 if (!dep) { // 此时将key作为key, 将createDep()作为value存储进depsMap这个map中, 这里的 dep 本质上是一个set, 参考 createDep 源码实现 depsMap.set(key, (dep = createDep())) } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined // 这里注意, trackEffects(dep, eventInfo) } } -
通过以上代码,我们可知 targetMap 这个WeekMap还是比较复杂的,其结构说明如下:
- targetMap
- key: target
- value: Map
- key: key
- value: Set
- targetMap
-
我们再看看,上述的 createDep() 发生了什么, 在 dep.ts 中
export const createDep = (effects?: ReactiveEffect[]): Dep => { // 这里是一个set集合 const dep = new Set<ReactiveEffect>(effects) as Dep dep.w = 0 dep.n = 0 return dep } -
我们再看下 trackEffects 发生了什么
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } // 关注下这里 if (shouldTrack) { // 这里的dep, 本质是一个 Set, 这里将 effect 实例添加进这个Set中保存 // 下面两行建立了 dep 和 activeEffect 之间的联系,这样我们就可以通过被代理对象的key获取到当前的 activeEffect, 也就是 ReactiveEffect实例,这里我们完成了依赖收集 dep.add(activeEffect!) activeEffect!.deps.push(dep) // 由上面两步,我们的 targetMap 对象中就保存了 ReactiveEffect 实例 if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack({ effect: activeEffect!, ...debuggerEventExtraInfo! }) } } } -
由上可知,依赖收集的过程
- 本质上就是建立起 targetMap 和 ReactiveEffect 之前的关系,让我们可以根据指定对象的指定属性,来找到该属性对应的 ReactiveEffect
- 而 ReactiveEffect 实例中存在一个 fn 函数,就是我们 effect 的第一个回调函数,这里就是当前触发代理对象 getter 行为时的函数
- 这时候,指定对象的指定属性 与 触发getter行为的回调函数建立起了关系, 这时候完成了依赖的收集过程
-
在我们业务代码中,effect 回调中进行了 getter 操作,而 setTimeout 中进行了 setter 操作, 我们回过头来再看一下这个 setter
- 再来看下 set 同上述 get 类似
const set = /*#__PURE__*/ createSetter() - 这里 set 是 createSetter 返回的一个 set 函数
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { let oldValue = (target as any)[key] if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false } if (!shallow) { if (!isShallow(value) && !isReadonly(value)) { oldValue = toRaw(oldValue) value = toRaw(value) } if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value return true } } else { // in shallow mode, objects are set as-is regardless of reactive or not } const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) // 注意,这里 result 是 true,表示 赋值成功了 const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // 这里会触发 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result } }
- 再来看下 set 同上述 get 类似
-
响应性的核心是依赖收集和触发依赖的过程,上述的 trigger 就是触发依赖,我们看下这个函数,在 effect.ts 中
export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { // 这里,基于 targetMap 获取 map const depsMap = targetMap.get(target) if (!depsMap) { // never been tracked return } let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE // 这里会执行 if (key !== void 0) { deps.push(depsMap.get(key)) // 将 set 集合 存放于 deps 中 } // also run for iteration key on ADD | DELETE | Map.SET // 这里会执行匹配 switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break /// 这里匹配到了 case TriggerOpTypes.SET: // 但是我们的target不是一个map对象,此处会跳过 if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined // 这里会执行 if (deps.length === 1) { if (deps[0]) { if (__DEV__) { // 这里会执行,这是核心的依赖触发 triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } } -
我们看看 triggerEffects 发生了什么?
export function triggerEffects( dep: Dep | ReactiveEffect[], // 这个就是我们的 Set 集合 debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization // 这里的 effects 是 ReactiveEffect 数组 const effects = isArray(dep) ? dep : [...dep] // 我们可以看到下面两个for循环,感觉这样拆开写意义并不是很大 // 实际上这两个循环的意义在于computed中计算属性中关于触发依赖死循环调用的优化处理 for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { // 这里会执行 if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } } -
同样,我们可以看到 triggerEffect 发生了什么
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { effect.scheduler() } else { // 这里会执行,在上述run代码中,本质是在触发我们的 fn 函数,也就是 effect 的第一个回调函数,也随即会触发代理对象的 getter 行为 effect.run() } } } -
我们看下我们业务代码中的 fn 函数
document.querySelector('#app').innerText = obj.name
- 由上可知,我们的页面会随即更新 obj.name,也就是业务代码中定时器中的setter后的值,上述这行代码,在执行 obj.name 时会再次触发 getter 行为
- 直到页面上渲染出新的值,在整个 setter 行为中,主要做了如下工作
- 修改 obj 的值
- 调用 targetMap 下保存的 fn
- 当我们的业务代码中,执行了 effect 回调的时候,发生了什么呢?
3 )进行effect的debug
-
在chrome的Sources面板的Pages子面板找到 effect.ts 中,找到 effect() 方法,打上一个断点
-
当走到了断点处, 程序是这样走的:
// 在业务代码中,我们只传递了第一个参数,回调函数 export function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn } // 这里把我们自己业务代码中的回调传递过来,基于 ReactiveEffect 类构造出了一个 _effect 实例 const _effect = new ReactiveEffect(fn) // 这里 options 是 undefined 不存在跳过 if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) } // 这里会匹配,执行实例的 run 方法 if (!options || !options.lazy) { _effect.run() } const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect return runner } -
我们这里进入 ReactiveEffect 这个类中,看下
export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean /** * @internal */ private deferStop?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, // 这里的fn就是我们传入的函数 public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { recordEffectScope(this, scope) } // 着重关注下这里 run() { if (!this.active) { return this.fn() } let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack while (parent) { if (parent === this) { return } parent = parent.parent } try { // 这个this是 ReactiveEffect 类的实例,将当前被激活的 effect 挂载到当前实例的parent上 this.parent = activeEffect // 此时,activeEffect 会指向当前实例,其中被挂载的fn函数,就是我们业务代码中的 effect 的第一个回调参数 activeEffect = this shouldTrack = true trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { initDepMarkers(this) } else { cleanupEffect(this) } // 注意这里的return,就是我们自己业务代码中的回调参数那个箭头函数, 一旦这个函数被执行,就会触发 代理对象的 getter 行为 // 就是会触发 上述 get 属性,也就是 createGetter 方法 return this.fn() } finally { if (effectTrackDepth <= maxMarkerBits) { finalizeDepMarkers(this) } trackOpBit = 1 << --effectTrackDepth activeEffect = this.parent shouldTrack = lastShouldTrack this.parent = undefined if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup if (activeEffect === this) { this.deferStop = true } else if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop() } this.active = false } } } -
回顾下 effect 做的几件事情
- 生成 ReactiveEffect 实例
- 触发 fn 方法,从而激活 getter
- 建立 targetMap 和 activeEffect 之间的联系
dep.add(activeEffect)activeEffect.deps.push(dep)
-
综上,我们基于业务模块,基于如下流程,来梳理了 reactive 响应性核心代码
- reactive 函数
- effect 函数
- 触发setter
-
其实上述过程我们可以简化为
- 创建 proxy
- 收集 effect 依赖
- 触发收集的依赖
删减后的核心实现代码
我们把它拆分成三个部份:rollup构建程序和配置,源码包以及示例程序
1 )rollup构建程序
rollup.config.js
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import typescript from '@rollup/plugin-typescript'
export default [
{
// 入口文件
input: 'packages/vue/src/index.ts',
// 打包的出口
output: [
// 比如:commonjs, esm 等,我们不需要支持那么多
// 只需要导出 iife 模式的包
{
// 开启 sourceMap
sourcemap: true,
// 导出文件地址
file: './packages/vue/dist/vue.js',
// 生成包的格式
format: 'iife',
// 变量名
name: 'Vue'
}
],
// 处理插件
plugins: [
// ts
typescript({
sourceMap: true
}),
// 模块导入的路径补全
resolve(),
// 转 commonjs 为 ESM
commonjs()
]
}
]
package.json配置
"type": "module",
"scripts": {
"dev": "rollup -c -w",
"build": "rollup -c"
}
2 )源码包
2.1 目录结构如下
- packages
- reactivity
- src
- baseHandlers.ts
- dep.ts
- effect.ts
- index.ts
- reactive.ts
- src
- vue
- dist
- src
- index.ts
- examples
- reactive.html
- reactivity
2.2 reactivity 模块包的核心实现
baseHandlers.ts 核心实现
import { track, trigger } from './effect'
const get = createGetter()
const set = createSetter()
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
function createGetter() {
return function get(target: object, key: string | symbol, receiver: object) {
// 触发get
const res = Reflect.get(target, key, receiver)
// 对应依赖收集和触发: 每次触发get时,要把触发getter行为的函数收集起来,以便触发setter行为时,调用对应函数
track(target, key)
return res
}
}
function createSetter() {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) {
const res = Reflect.set(target, key, value, receiver)
// 在setter行为时,触发依赖
trigger(target, key, value)
return res
}
}
dep.ts 核心实现
import { ReactiveEffect } from './effect'
export type Dep = Set<ReactiveEffect>
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
return dep
}
effect.ts 核心实现
import { Dep, createDep } from './dep'
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() {
activeEffect = this // 挂载当前的 activeEffect 变量
return this.fn()
}
}
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
export function track(target: object, key: unknown) {
// console.log('track: 收集依赖')
if (!activeEffect) 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 = createDep()))
}
trackEffects(dep)
// depsMap.set(key, activeEffect)
// console.log(targetMap)
}
// 利用 dep 依次跟踪指定 key 的所有 effect
export function trackEffects(dep: Dep) {
dep.add(activeEffect!)
}
// 处理 trigger 里的逻trigger辑
export function trigger(target: object, key: unknown, newValue: unknown) {
// console.log('trigger: 触发依赖')
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const dep: Dep | undefined = depsMap.get(key)
if (!dep) {
return
}
triggerEffects(dep)
}
export function triggerEffects(dep: Dep) {
const effects = Array.isArray(dep) ? dep : [...dep]
// 依次触发循环
for (const effect of effects) {
triggerEffect(effect)
}
}
// 触发指定依赖
export function triggerEffect(effect: ReactiveEffect) {
effect.run()
}
index.ts 核心实现
export { reactive } from './reactive'
export { effect } from './effect'
reactive.ts 核心实现
import { mutableHandlers } from './baseHandlers'
export const reactiveMap = new WeakMap<object, any>()
export function reactive (target: object) {
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
function createReactiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
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
}
2.3 vue模块的核心实现
位置:vue/src/index.ts
export { reactive, effect } from '@vue/reactivity'
3 )示例程序
位置:vue/examples/reactive.html
<script src="../dist/vue.js"></script>
<body>
<div id='app'>
<p id='p1'></p>
<p id='p2'></p>
</div>
</body>
<script>
const { reactive, effect } = Vue
// console.log(reactive)
const obj = reactive({
name: '张三'
})
// console.log(obj.name) // 触发getter
// obj.name = '李四' // 触发 setter
effect(()=>{
document.querySelector('#p1').innerText = obj.name // 这里一定要暴露出来一个getter的行为,这样我们才可完成依赖的收集
})
effect(()=>{
document.querySelector('#p2').innerText = obj.name // 这里一定要暴露出来一个getter的行为,这样我们才可完成依赖的收集
})
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
295



被折叠的 条评论
为什么被折叠?



