Vue3源码梳理:reactive的响应性核心实现

【vue3源码】七、reactive——Object的响应式实现 本文介绍vue3对reactive object的响应式实现。 阅读详情

阅读源码原则

  • 简化和调试,掌握核心流程
  • 不被过多细节束缚,只专注于主线

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
          }
        }
        
  • 我们看下上述 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
  • 我们再看看,上述的 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
      	}
      }
      
  • 响应性的核心是依赖收集和触发依赖的过程,上述的 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
    • vue
      • dist
      • src
        • index.ts
      • examples
        • reactive.html

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>
Vue 2到Vue 3升级实战:从评估、迁移到验证的全流程指南 前端框架的版本升级是大型项目演进中的关键环节,其核心在于响应式系统与组件化架构的迭代。Vue 3通过基于Proxy的全新响应式机制和Composition API,在性能优化与开发体验上实现了显著提升,这为构建高性能、可维护的现代Web应用提供了坚实的技术基础。在实际工程实践中,此类升级往往涉及依赖库兼容性、API变更适配以及构建工具链重构等多重挑战,尤其对于使用了Element UI等大型UI组件库的项目,需要系统性的迁移策略。本文聚焦于Vue 2.9.6至Vue 3.0的升级全过程,详细拆解了从项目现状 阅读详情

相关推荐

Vue3面试核心考点:响应式原理、组合式API与工程化实战解析

前端技术栈快速迭代,Vue3已成为主流框架。理解其响应式原理是掌握框架内功的关键,从Vue2的Object.defineProperty到Vue3的Proxy代理,不仅解决了新增属性、数组索引等检测难题,还通过懒代理提升了性能。组合式API中的setup、ref与reactive、computed与watch,以及组件通信、Vite构建、动态路由权限等工程化问题,更是面试与实战中的高频场景。从数据响应式到组件化设计,再到现代构建工具链,本文梳理了Vue3的底层机制与工程实践,将原理、考点与实际项目中的常见坑

weixin_31102231的博客 295

【Vue3源码学习】— CH2.3 MutableReactiveHandler详解

MutableReactiveHandler 类在 Vue 3 的响应式系统中扮演着核心角色,它继承BaseReacriverHandler类,并且扩展了处理非只读响应式对象的各种操作,包括属性的设置(set)、删除(deleteProperty)、存在性检查(has)和键的枚举(ownKeys)等。

YSG的博客 2258

前端犄角旮旯,非常规面试题总结(不断更新....)

27、判断类型的方式有哪些?1、typeof操作符:用来检测数据的类型,返回的是一个表示类型的字符串。2、instanceof操作符:用来检测对象是否为指定的构造函数的实例。3、方法:可以返回对象的准确类型。4、方法:用来判断一个变量是否为数组。(一)typeof和instanceof区别typeof返回的是一个字符串,表示变量的数据类型,可能的返回值有numberstringbooleanfunctionundefinedobject。instanceof返回的是一个布尔值,true或。

qq_51398495的博客 1666

Vue源码学习(五)——reactive,ref

上面说到是通过调用render函数,触发track进行依赖收集,但是又是为什么可以进行收集???我们一般会将reactive,ref声明响应式对象放在setup中,因此就执行reactive,ref,然后通过调用render函数,触发track进行依赖收集,但是为什么可以进行收集,什么是track,我们需要看一下reative源码。

m0_74793405的博客 889

ClickHouse分布式表原理:大数据分片存储与查询

我是一名资深大数据工程师,专注于ClickHouse优化和分布式架构设计。拥有5年以上大数据开发经验,曾为多家电商、金融公司提供ClickHouse解决方案。欢迎关注我的博客(),一起探讨大数据技术!留言互动:你在使用ClickHouse分布式表时遇到过什么问题?欢迎在评论区分享,我会一一解答!

AI大模型应用之禅 959

机器学习 线性回归 头歌实训

第1关:简单线性回归与多元线性回归 第2关:线性回归的正规方程解 第3关:衡量线性回归的性能指标 第4关:scikit-learn线性回归实践 - 波斯顿房价预测

weixin_57270536的博客 1万+

Vue3 响应性基础 API --reactive

基本用法 返回对象的响应式副本,只能代理对象,不能代理普通值 const obj = reactive({count: 0, name: 'hyh'}) const count = reactive(1) // value cannot be made reactive: 1 响应式转换是“深层”的——它影响所有嵌套 property const obj = reactive({count: 0, name: 'hyh', info: { age: 25 }}) console.log(obj

weixin_41489378的博客 3510

浪潮服务器ESXI6.7部署实战:手动集成RAID驱动避坑指南

本文详细介绍了在浪潮服务器上部署ESXI6.7时手动集成RAID驱动的完整流程和避坑指南。从驱动获取、解压技巧到封装实战,逐步解决国产服务器常见的驱动缺失问题,特别针对PM8060 RAID卡提供具体解决方案,帮助IT运维人员顺利完成ESXI部署。

weixin_33725722的博客 462

Vue3 reactive 源码分析

本文通过抽取Vue3 reactive源码中的主要部分,来理解其响应式`object`的实现方式。

weixin_43867412的博客 833

西门子S7-200 SMART 实战解析:滑动平均值滤波算法在模拟量信号处理中的应用

本文详细解析了在西门子S7-200 SMART PLC中实现滑动平均值滤波算法的实战应用。通过处理模拟量信号如温度、压力等,该算法有效抑制信号干扰,提升系统稳定性。文章涵盖算法原理、程序框架、信号采集及调试优化,为工业自动化工程师提供实用指南。

gitlab7runner的博客 1157

电源设计之路-Buck电源环路设计(Type IIIA&Type IIIB)

在对Buck 环路补偿Type II拓扑进行仿真测试后,对瞬态波形以及伯德图已经有感官上的认知;然后再进一步对Type IIIA & IIIB进一步的学习;进一步了解Cout电容量和ESR对环路补偿的影响;认识到一个电源,不是参考Datasheet上的参数进行选值就行;更需要根据实际情况,功率电感值、输出电容值以及不同电容会有不一样的ESR,这些都会对环路参数产生一定的影响

2503_92839756的博客 759

Vue3源码梳理:ref的响应性核心实现

ref做的三件事情返回RefImpl实例在构造函数中,对传入数据进行处理:当前传入value是简单类型,则直接使用;否则,使用reactive函数包裹,让ref.value的值变成响应式数据启动两个函数:get value, set value。

Wang的专栏 761

e-puck场景搭建

可以注意到一个黑色的小窗口出现在三维视图的左上角。它显示了电子吸盘机器人的摄像头拍摄的图像。该图像将保持黑色,直到机器人的控制器明确启用相机。最后,单击右上角的“x”可以关闭它。在对话框中,选择PROTO节点(Webots项目)/robots/gctronic/e-puck/e-puck(Robot)。一个电子吸盘机器人应该出现在竞技场的中央。通过按Alt+左键单击+拖动,对机器人施加力。无法对WoodenBox节点施加力,因为默认情况下,它们没有质量,并且被视为粘在地板上。将新控制器与机器人链接。

weixin_66540163的博客 769

Vue3源码之Refs

前言 Vue3的响应性API除了reactive、readonly等基础的,还提供了相关Refs API其中包括ref、customRef等。在之前的[响应系统](https://blog.youkuaiyun.com/s1879046/article/details/118757873)的文章中,对reactive的逻辑有较为详细的分析,本文会对梳理相关API的逻辑,对比reactive API看看二者的不同,加强对Refs API使用的理解。

玉案轩窗的博客 1495

CCF编程培训师资认证(PTA)标准(教学能力T)

随着计算机科学的不断发展,计算机相关技术应用到社会各个行业,越来越多的青少年参加到了计算机程序设计的学习中,这对编程培训教师提出了更高的专业要求和职业要求。CCF开展编程培训师资认证项目(Programming Trainer Accreditation,PTA),旨在建立一个规范的、科学的、权威且有公信力的三方认证。认证考试分编程专业能力(P)和教学能力(T)两部分。本标准根据《CCF编程培训师资认证条例》第十条制定,旨在明确教学能力(T)的测试标准。

lan_in的博客 1714

9、傅里叶变换:连续时间与离散时间信号分析的核心工具

本文深入探讨了傅里叶变换在连续时间和离散时间信号分析中的核心作用,详细介绍了连续时间傅里叶变换(CTFT)和离散时间傅里叶变换(DTFT)的定义、性质及其应用。文章涵盖了傅里叶级数到傅里叶变换的推导过程、常见变换对、系统分析中的关键性质,并讨论了采样后频谱的周期延拓与混叠问题。此外,还展示了傅里叶变换在滤波器设计、通信系统调制解调等实际场景中的应用,最后展望了其在图像处理、生物医学等领域的广阔前景。

jump7的博客 68

Vue.js核心原理与面试高频考点解析

响应式系统是现代前端框架的核心机制,通过数据绑定实现视图自动更新。Vue2采用Object.defineProperty实现响应式,而Vue3升级为Proxy方案,解决了数组变异和新增属性的监听问题。虚拟DOM技术通过JS对象描述页面结构,配合Diff算法实现高效更新,其中key的合理使用对性能优化至关重要。在Vue3组合式API中,ref和reactive分别处理基础类型和对象类型的响应式转换,toRefs可保持解构后的响应性。这些核心技术广泛应用于表单处理、状态管理等场景,也是前端工程师面试中的高频考点

weixin_34212762的博客 620

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

VUE3 面试题总结

单文件组件< script setup > < script setup > </script> 可以直接引入,定义,逻辑,无需return返出数据 直接import引入子组件后在页面直接使用,无需注册 替代props功能的是: defineProps({}) 替代emit功能的是:defineEmits([]) 点击的dom上@click="skip" 例:cons...

hjdjhh的博客 2273

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Vue3笔记

渲染快,虚拟 DOM 重写(优化 diff 算法),静态节点提升(Static Hoisting)和补丁标记(Patch Flags),减少运行时开销。Vue 3 于 2020 年 9 月发布,是 Vue.js 的重大升级版本,主要改进包括性能优化、新特性引入和更好的 TypeScript 支持。3.更好的 TypeScript 支持:源码使用 TypeScript 重写,提供完整的类型定义,开发体验更友好,IDE 提示更精准。目的:解决逻辑复用和代码组织问题(替代 Options API 的局限性)。

zxz86的博客 657

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

2026年前端面试趋势:WebSocket优化与Vue3响应式实战

WebSocket作为现代实时通信的核心技术,通过全双工协议实现低延迟数据传输,在直播弹幕、在线协作等场景具有不可替代的优势。其技术原理基于HTTP升级机制,通过建立持久连接减少握手开销,相比传统轮询方案可降低80%以上的延迟。在工程实践中,开发者需要掌握连接管理、心跳检测、消息分区等优化手段,特别是在处理百万级并发时,需结合Nginx负载均衡和集群部署方案。Vue3的响应式系统通过Proxy重构带来性能提升,其中ref和reactive的差异化使用直接影响应用性能,如表单字段推荐使用ref而复杂对象使用r

weixin_34117211的博客 286

Vue3 setup函数返回值全解析:对象、渲染函数与Promise

在Vue3组合式API中,setup函数是组件逻辑初始化的统一入口,它在组件实例创建前执行,因而this不可用,但通过props和context可访问组件上下文。setup的返回值设计直接决定了模板数据绑定、自定义渲染及异步加载的实现方式——返回对象可暴露ref/reactive等响应式数据,返回函数可替代template编写h函数或JSX,返回Promise则需配合Suspense处理异步数据流程。理解这些返回值类型,有助于掌握Composition API的核心机制,并在工程实践中正确选择script

weixin_33508411的博客 280

Vue3 setup返回值详解:从模板渲染到响应式原理的完整指南

在Vue3组合式API中,setup函数是逻辑组织的核心入口,其返回值决定了模板渲染上下文的数据来源。理解返回对象与渲染函数的区别,以及ref/reactive响应式数据的暴露规则,是掌握现代前端框架的关键。通过返回值,组件能够将内部状态和方法安全地暴露给模板,同时保持逻辑的聚合性与可维护性。在实际工程中,无论是构建用户交互组件、列表页还是复杂表单,合理设计setup的返回值都能显著降低调试成本。当遇到数据不更新或模板显示undefined时,大多与返回值或响应式丢失有关。本文从概念到原理,结合典型报错与最

weixin_34310369的博客 358

深入理解 Vue 3 Composition API:为什么它是现代前端的必修课

摘要 Vue 3是一款渐进式JavaScript框架,用于构建用户界面。它采用声明式渲染和响应式设计,支持单页应用开发。Vue 3的主要升级包括: Composition API提升代码组织和复用性 基于Proxy的响应式系统解决Vue 2的痛点 更好的TypeScript支持和更小的体积 安装使用Vite构建工具: 准备Node.js环境 通过npm create vue@latest创建项目 安装依赖并启动开发服务器 核心功能: 响应式数据:使用ref和reactive 模板指令:v-bind、v-mo

jaychoushsj的博客 164

前端春招实习面经:高频考点与面试实战技巧复盘

前端面试是检验开发者综合能力的试金石,也是春招实习生的必争之地。无论是JavaScript闭包、this指向、事件循环等语言基础,还是Vue响应式原理、组件通信、diff算法等框架核心,面试官都倾向于以场景化问题考察候选人的工程思维。理解这些技术背后的设计初衷,并能结合浏览器缓存、性能优化等实际部署经验进行阐述,是脱颖而出的关键。本文汇总了真实春招实习面试中的高频考点与复盘思路,从答题框架、踩坑记录到学习路径,为备战实习面试的候选人提供可直接演练的模拟题清单与沟通话术。

weixin_33754065的博客 488

3.2 ref reactive pop 钩子函数

ref

weixin_40746230的博客 751

vue3 reactive源码解析

中的数据双向绑定原理主要得益于的使用,它提供了一种更清晰的方式来拦截和自定义对象的操作。在Vue3中,Proxy被用来替换Vue2中的Object.defineProperty方法,使得数据绑定更加高效和强大。Proxy对象允许开发者定义基本操作的自定义行为,如属性查找、赋值、枚举、函数调用等。它类似于一个拦截器,可以在访问对象属性时进行拦截,并可以自定义这些行为。reactive:Vue 3 中的reactive函数用于创建响应式对象。它接受一个普通对象作为参数,并返回该对象的响应式代理。在内部,

weixin_43995143的博客 1683
上一篇: nodejs微服务:微服务集群
下一篇: Vue3源码梳理:ref的响应性核心实现
Wang's Blog
博客等级 码龄12年 7433粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值