vue3响应式核心文章汇总:
vue3响应式核心之reactive源码详解
vue3响应式核心之effect源码详解
上篇vue3响应式核心之reactive源码详解详细讲解了reactive的原理和实现源码,本篇文章将讲解effect源码。
通常情况下我们是不会直接使用effect的,因为effect是一个底层的API,在我们使用Vue3的时候Vue默认会帮我们调用effect。 effect翻译为作用,意思是使其发生作用,这个使其的其就是我们传入的函数,所以effect的作用就是让我们传入的函数发生作用,也就是执行这个函数。 执行过程简图如下:

接下来先通过例子了解effect的基本用法,然后再去了解原理。
一、effect用法
1、基本用法
- const obj = reactive({count: 1})
-
- const runner = effect(() => {
- console.log(obj.count)
- })
-
- obj.count++
结果会先打印1, 然后在obj.count++
之后打印出2。
流程简图如下:

运行effect(fun)
- // 先执行
- fun() // 打印出1
-
- const runner = new ReactiveEffect(fn)
-
- return runner
-
- runner: {
- run() {
- this.fun() //执行fun
- },
- stop() {
-
- }
- }
console.log(obj.count)
track依赖收集 结构如下:

obj.count++
触发依赖,执行runner.run(), 实际运行的是
- () => {
- console.log(obj.count)
- }
所以又打印出2
2、lazy属性为true
此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
- const obj = reactive({count: 1})
-
- const runner = effect(() => {
- console.log(obj.count)
- }, {
- lazy: true
- })
- runner()
- obj.count++
只会打印出2
原因是effect源码中有如下逻辑:

3、options中包含onTrack
- let events = []
- const onTrack = (e) => {
- events.push(e)
- }
- const obj = reactive({ foo: 1, bar: 2 })
- const runner = effect(
- () => {
- console.log(obj.foo)
- },
- { onTrack }
- )
- console.log('runner', runner)
- obj.foo++
- console.log("events", events)
看下events的打印结果:

- [
- {
- effect: runner, // effect 函数的返回值
- target: toRaw(obj), // 表示的是哪个响应式数据发生了变化
- type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值
- key: 'foo'
- }
- ]
二、源码分析
1、effect方法的实现
- // packages/reactivity/src/effect.ts
- export interface ReactiveEffectOptions extends DebuggerOptions {
- lazy?: boolean
- scheduler?: EffectScheduler
- scope?: EffectScope
- allowRecurse?: boolean
- onStop?: () => void
- }
-
- export function effect<T = any>(
- fn: () => T, // 副作用函数
- options?: ReactiveEffectOptions // 结构如上
- ): ReactiveEffectRunner {
- // 如果 fn 对象上有 effect 属性
- if ((fn as ReactiveEffectRunner).effect) {
- // 那么就将 fn 替换为 fn.effect.fn
- fn = (fn as ReactiveEffectRunner).effect.fn
- }
- // 创建一个响应式副作用函数
- const _effect = new ReactiveEffect(fn)
- if (options) {
- // 将配置项合并到响应式副作用函数上
- extend(_effect, options)
- // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域)
- if (options.scope) recordEffectScope(_effect, options.scope)
- }
- if (!options || !options.lazy) { // options.lazy 不为true
- _effect.run() // 执行响应式副作用函数 首次执行fn()
- }
- // _effect.run作用域绑定到_effect
- const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
- // 将响应式副作用函数赋值给 runner.effect
- runner.effect = _effect
- return runner
- }
核心代码:
创建一个响应式副作用函数const _effect = new ReactiveEffect(fn)
,其运行结果如下:

非lazy状态执行响应式副作用函数_effect.run()
- if (!options || !options.lazy) { // options.lazy 不为true
- _effect.run() // 执行响应式副作用函数 首次执行fn()
- }
_effect.run
作用域绑定到_effect
- // _effect.run作用域绑定到_effect
- const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
返回副作用函数runner
2、ReactiveEffect函数源码
- run方法的作用就是执行副作用函数,并且在执行副作用函数的过程中,会收集依赖;
- stop方法的作用就是停止当前的ReactiveEffect对象,停止之后,就不会再收集依赖了;
- activeEffect和this并不是每次都相等的,因为activeEffect会跟着调用栈的深度而变化,而this则是固定的;
三、依赖收集相关
1、如何触发依赖收集
在副作用函数中, obj.count
就会触发依赖收集
- const runner = effect(() => {
- console.log(obj.count)
- })
触发的入口在get拦截器里面
- function createGetter(isReadonly = false, shallow = false) {
- // 闭包返回 get 拦截器方法
- return function get(target: Target, key: string | symbol, receiver: object) {
- // ...
- if (!isReadonly) {
- track(target, TrackOpTypes.GET, key)
- }
- // ...
- }
2、track源码
- const targetMap = new WeakMap();
- /**
- * 收集依赖
- * @param target target 触发依赖的对象,例子中的obj
- * @param type 操作类型 比如obj.count就是get
- * @param key 指向对象的key, 比如obj.count就是count
- */
- export function track(target: object, type: TrackOpTypes, key: unknown) {
- if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数
-
- // 如果 targetMap 中没有 target,就会创建一个 Map
- 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())) // createDep 生成dep = { w:0, n: 0}
- }
-
- const eventInfo = __DEV__
- ? { effect: activeEffect, target, type, key }
- : undefined
-
- trackEffects(dep, eventInfo)
- }
- }
shouldTrack在上面也讲过,它的作用就是控制是否收集依赖;
activeEffect就是我们刚刚讲的ReactiveEffect对象,它指向的就是当前正在执行的副作用函数;
track方法的作用就是收集依赖,它的实现非常简单,就是在targetMap中记录下target和key;
targetMap是一个WeakMap,它的键是target,值是一个Map,这个Map的键是key,值是一个Set;
targetMap的结构伪代码如下:
- targetMap = {
- target: {
- key: dep
- },
- // 比如:
- obj: {
- count: {
- w: 0,
- n: 0
- }
- }
- }

以上是最原始的depMap
dev环境为增加响应式调试会增加eventInfo
- const eventInfo = __DEV__
- ? { effect: activeEffect, target, type, key }
- : undefined
eventInfo结构如下:

trackEffects(dep, eventInfo)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackEffects
3、trackEffects(dep, eventInfo)源码解读
- export function trackEffects(
- dep: Dep,
- debuggerEventExtraInfo?: DebuggerEventExtraInfo
- ) {
- let shouldTrack = false
- if (effectTrackDepth <= maxMarkerBits) {
- if (!newTracked(dep)) {
- // 执行之前 dep = Set(0) {w: 0, n: 0}
-
- // 执行之后 dep = Set(0) {w: 0, n: 2}
- dep.n |= trackOpBit // set newly tracked
-
- shouldTrack = !wasTracked(dep)
- }
- } else {
- // Full cleanup mode.
- shouldTrack = !dep.has(activeEffect!)
- }
-
- if (shouldTrack) {
- // 将activeEffect添加到dep
- dep.add(activeEffect!)
- activeEffect!.deps.push(dep)
- if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑
- activeEffect!.onTrack(
- extend(
- {
- effect: activeEffect!
- },
- debuggerEventExtraInfo!
- )
- )
- }
- }
- }
dep.add(activeEffect!)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去

最终生成的depTarget结构如下:

四、触发依赖
比如例子中代码obj.count++
就会触发set拦截,触发依赖更新
- function createSetter(shallow = false) {
- return function set(
- target: object,
- key: string | symbol,
- value: unknown,
- receiver: object
- ): boolean {
- //...
- 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) // 触发ADD依赖更新
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新
- }
- }
- //...
- }
1、trigger依赖更新
- // 路径:packages/reactivity/src/effect.ts
- export function trigger(
- target: object,
- type: TriggerOpTypes,
- key?: unknown,
- newValue?: unknown,
- oldValue?: unknown,
- oldTarget?: Map<unknown, unknown> | Set<unknown>
- ) {
- const depsMap = targetMap.get(target) // 获取depsMap, targetMap是在track中创建的依赖
- 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)) {
- const newLength = Number(newValue)
- depsMap.forEach((dep, key) => {
- if (key === 'length' || key >= newLength) {
- deps.push(dep)
- }
- })
- } else {
- // schedule runs for SET | ADD | DELETE
- if (key !== void 0) {
- deps.push(depsMap.get(key))
- }
-
- // 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:
- 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))
- }
- }
- }
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:

执行以上语句之后的depsMap结构如下:

将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:

triggerEffects(deps[0], eventInfo)
- 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])
- }
- }
- }
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
2、triggerEffects(deps[0], eventInfo)
- export function triggerEffects(
- dep: Dep | ReactiveEffect[],
- debuggerEventExtraInfo?: DebuggerEventExtraInfo
- ) {
- // spread into array for stabilization
- const effects = isArray(dep) ? dep : [...dep]
- for (const effect of effects) {
- if (effect.computed) {
- triggerEffect(effect, debuggerEventExtraInfo)
- }
- }
- for (const effect of effects) {
- if (!effect.computed) {
- triggerEffect(effect, debuggerEventExtraInfo)
- }
- }
- }
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects

triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
3、triggerEffect(effect, debuggerEventExtraInfo)
- function triggerEffect(
- effect: ReactiveEffect,
- debuggerEventExtraInfo?: DebuggerEventExtraInfo
- ) {
- if (effect !== activeEffect || effect.allowRecurse) {
- // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行
- if (__DEV__ && effect.onTrigger) {
- effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
- }
- // 如果 effect 是一个调度器,就会执行 scheduler
- if (effect.scheduler) {
- effect.scheduler()
- } else {
- // 其它情况执行 effect.run()
- effect.run()
- }
- }
- }
effect.run()就是执行副作用函数
以上就是源码分析Vue3响应式核心之effect的详细内容,更多关于Vue3 effect的资料请关注w3xue其它相关文章!