Skip to content

vue 响应式原理

更新: 2025/3/12 17:59:43 字数: 0 字

响应式的本质

  响应式的本质是函数和数据的关联,被监控的函数会和数据(响应式数据并且在函数中使用)关联起来,当数据变化时,会通知函数执行。

源码剖析

  在 vue 中响应式被写在了 reactivity 这个包中。
  其中有两个关键的方法,reactiveeffectreactive 用于定义响应式数据,effect 负责进行依赖的收集。

示例

   state 是定义的响应式数据,effect 是收集依赖的函数。当数据发生变化时,会触发对应的里面有该数据的 effect 中的函数执行。

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app"></div>
  <script src="../reactivity/dist/@vue/reactivity.global.js"></script>
  <script>
    const { effect, reactive } = vueReactivity

    let state = reactive({
      name: '张三',
      age: 18,
      count: 0,
      arr: [1, 2, 3],
      obj: {
        a: 1,
        b: 2,
        c: 3
      }
    });
    // 1.里面的方法默认执行
    // 2.获取代理的数据 ref proxy   执行 get 方法   收集effect
    effect(() => {
      app.innerHTML = state.name + state.arr.length
    });
    effect(() => {
      app.innerHTML = state.name
    });
    // 执行了两边 effect 中的函数
    setTimeout(() => {
      // 修改数据会触发 set 执行 effect
      state.name = '李四'
      // state.arr.length = 1
    }, 1000)
  </script>

</body>

</html>

reactive 函数原理

ts
// 1. 根据函数来创建 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,
  )
}


// 2. 创建 raective 对象
/**
 * target 是要代理的对象
 * isReadonly 该对象是不是只能进行读取
 * baseHandlers 是基本的代理处理器
 * collectionHandlers 是你要代理的这一类的处理器
 * proxyMap 是这一类的 map 集合,最后会将创建好的代理放进去
 */
function createReactiveObject(
    target: Target,
    isReadonly: boolean,
    baseHandlers: ProxyHandler<any>,
    collectionHandlers: ProxyHandler<any>,
    proxyMap: WeakMap<Target, any>,
) {
    if (!isObject(target)) {
        if (__DEV__) {
            warn(
                `value cannot be made ${isReadonly ? 'readonly' : '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
    const existingProxy = proxyMap.get(target)
    if (existingProxy) {
        return existingProxy
    }
    // only specific value types can be observed.
    const targetType = getTargetType(target)
    if (targetType === TargetType.INVALID) {
        return target
    }
    // targetType 是用于判断数据类型的
    // function getTargetType(value: Target) {
    // return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    //   ? TargetType.INVALID
    //   : targetTypeMap(toRawType(value))
    // }
    // export const toRawType = (value: unknown): string => {
    //   // extract "RawType" from strings like "[object RawType]"
    //   return toTypeString(value).slice(8, -1)
    // }
    // 数据 value 中有 __v_skip 字段
    // 这个数据不能进行扩展 返回 TargetType.INVALID = 0
    // 这个数据是可以进行扩展的 去寻找他的 toRawType 对应的值

    // WARING:
    // 上面经过一系列的判断来排除 target 已经被代理或者是其他类型
    // 下面是创建代理对象
    const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
    )
    proxyMap.set(target, proxy)
    return proxy
}

handler 处理器

  proxy 要使用的 handler 的封装。
   这里就是通过 proxy 对象来对数据进行代理,当数据被读取的时候会触发 get 方法,然后会收集依赖。

ts
// 1. 四种类型的 handler 处理器
export const mutableHandlers: ProxyHandler<object> =
  /*@__PURE__*/ new MutableReactiveHandler()

export const readonlyHandlers: ProxyHandler<object> =
  /*@__PURE__*/ new ReadonlyReactiveHandler()

export const shallowReactiveHandlers: MutableReactiveHandler =
  /*@__PURE__*/ new MutableReactiveHandler(true)

// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
  /*@__PURE__*/ new ReadonlyReactiveHandler(true)



// 这四个 handler 处理器的类都继承了 BaseReactiveHandler
// 是对 ProxyHandler<Target> 接口的实现
// Target 是范型
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}

  // 这里对 get 方法做了统一的处理,也就是依赖的收集
  // 这个 get 方法是对我们在 使用 例如:data.age 时对获取 age 方法的拦截  
  // target 是原始对象
  // key 是要读取的键值
  // receiver 是 proxy 本身
  get(target: Target, key: string | symbol, receiver: object): any {
    // 对于一些特有属性的 get 值的特殊处理
    if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
    const isReadonly = this._isReadonly
    const isShallow = this._isShallow
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return isShallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? isShallow
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver is not the reactive proxy, but has the same prototype
        // this means the receiver is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // early return undefined
      return
    }

    // 判断原始对象是不是数组
    const targetIsArray = isArray(target)

    if (!isReadonly) {
      let fn: Function | undefined
      if (targetIsArray && (fn = arrayInstrumentations[key])) {
        return fn
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    const res = Reflect.get(
      target,
      key,
      // if this is a proxy wrapping a ref, return methods using the raw ref
      // as receiver so that we don't have to call `toRaw` on the ref in all
      // its class methods
      isRef(target) ? target : receiver,
    )

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      // 若是该对象不是只读的我们需要对其进行依赖的收集也就是 track
      // 传入原始对象,GET,键值
      track(target, TrackOpTypes.GET, key)
    }

    if (isShallow) {
      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)
    }

    return res
  }
}

effect 函数

ts
// 传入函数和配置返回一个 ReactiveEffectRunner 对象
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
  // 传入的 又是一个 effect 那么, fn 函数就是里面那个 efffect 的 fn 函数
  if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  // 创建 ReactiveEffect 对象 
  const e = new ReactiveEffect(fn)
  if (options) {
    extend(e, options)
  }
  try {
    e.run()
  } catch (err) {
    e.stop()
    throw err
  }
  const runner = e.run.bind(e) as ReactiveEffectRunner
  runner.effect = e
  return runner
}

ReactiveEffect 对象

ts
export let activeEffectScope: EffectScope | undefined

export class ReactiveEffect<T = any>
  implements Subscriber, ReactiveEffectOptions
{
  /**
   * @internal
   */
  deps?: Link = undefined
  /**
   * @internal
   */
  depsTail?: Link = undefined
  /**
   * @internal
   */
  flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
  /**
   * @internal
   */
  next?: Subscriber = undefined
  /**
   * @internal
   */
  cleanup?: () => void = undefined

  scheduler?: EffectScheduler = undefined
  onStop?: () => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void

  constructor(public fn: () => T) {
    if (activeEffectScope && activeEffectScope.active) {
      activeEffectScope.effects.push(this)
    }
  }

  pause(): void {
    this.flags |= EffectFlags.PAUSED
  }

  resume(): void {
    if (this.flags & EffectFlags.PAUSED) {
      this.flags &= ~EffectFlags.PAUSED
      if (pausedQueueEffects.has(this)) {
        pausedQueueEffects.delete(this)
        this.trigger()
      }
    }
  }

  /**
   * @internal
   */
  notify(): void {
    if (
      this.flags & EffectFlags.RUNNING &&
      !(this.flags & EffectFlags.ALLOW_RECURSE)
    ) {
      return
    }
    if (!(this.flags & EffectFlags.NOTIFIED)) {
      batch(this)
    }
  }

  run(): T {
    // TODO cleanupEffect

    if (!(this.flags & EffectFlags.ACTIVE)) {
      // stopped during cleanup
      return this.fn()
    }

    this.flags |= EffectFlags.RUNNING
    cleanupEffect(this)
    prepareDeps(this)
    const prevEffect = activeSub
    const prevShouldTrack = shouldTrack
    activeSub = this
    shouldTrack = true

    try {
      return this.fn()
    } finally {
      if (__DEV__ && activeSub !== this) {
        warn(
          'Active effect was not restored correctly - ' +
            'this is likely a Vue internal bug.',
        )
      }
      cleanupDeps(this)
      activeSub = prevEffect
      shouldTrack = prevShouldTrack
      this.flags &= ~EffectFlags.RUNNING
    }
  }

  stop(): void {
    if (this.flags & EffectFlags.ACTIVE) {
      for (let link = this.deps; link; link = link.nextDep) {
        removeSub(link)
      }
      this.deps = this.depsTail = undefined
      cleanupEffect(this)
      this.onStop && this.onStop()
      this.flags &= ~EffectFlags.ACTIVE
    }
  }

  trigger(): void {
    if (this.flags & EffectFlags.PAUSED) {
      pausedQueueEffects.add(this)
    } else if (this.scheduler) {
      this.scheduler()
    } else {
      this.runIfDirty()
    }
  }

  /**
   * @internal
   */
  runIfDirty(): void {
    if (isDirty(this)) {
      this.run()
    }
  }

  get dirty(): boolean {
    return isDirty(this)
  }
}

track 依赖收集

ts


export let shouldTrack = true
export let activeSub: Subscriber | undefined // 当前活动的 sub

/**
 * Tracks access to a reactive property.
 *
 * This will check which effect is running at the moment and record it as dep
 * which records all effects that depend on the reactive property.
 *
 * @param target - Object holding the reactive property.
 * @param type - Defines the type of access to the reactive property.
 * @param key - Identifier of the reactive property to track.
 */
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  if (shouldTrack && activeSub) {
    // 1. 通过原始数据在 targetMap 中找到目标数据
    let depsMap = targetMap.get(target)
    // 判断是否存在,不存在就 new 一个新的
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    // 2. 通过 key 从 依赖 Map 中查询对应的 key 对应的依赖 
    let dep = depsMap.get(key)
    // 不存在就重新加入
    if (!dep) {
      depsMap.set(key, (dep = new Dep()))
      dep.map = depsMap
      dep.key = key
    }
    if (__DEV__) {
      dep.track({
        target,
        type,
        key,
      })
    } else {
      dep.track()
    }
  }
}
//
// -- [key, value(key, dep)]
// 最后通过 Dep 这个数据类型来做依赖的收集

Dep 数据结构

ts
/**
 * Represents a link between a source (Dep) and a subscriber (Effect or Computed).
 * Deps and subs have a many-to-many relationship - each link between a
 * dep and a sub is represented by a Link instance.
 *
 * A Link is also a node in two doubly-linked lists - one for the associated
 * sub to track all its deps, and one for the associated dep to track all its
 * subs.
 *
 * @internal
 */
export class Link {
    /**
     * - Before each effect run, all previous dep links' version are reset to -1
     * - During the run, a link's version is synced with the source dep on access
     * - After the run, links with version -1 (that were never used) are cleaned
     *   up
     */
    version: number

    /**
     * Pointers for doubly-linked lists
     */
    nextDep?: Link
    prevDep?: Link
    nextSub?: Link
    prevSub?: Link
    prevActiveLink?: Link

    constructor(
        public sub: Subscriber,
        public dep: Dep,
    ) {
        this.version = dep.version
        this.nextDep =
            this.prevDep =
                this.nextSub =
                    this.prevSub =
                        this.prevActiveLink =
                            undefined
    }
}

道友再会.