拆分 reactive 相关模块,并通过可配置的 getter 和 setter 实现 shallowReactive 与 readonly

2025-09-27 05:05:12

代码拆分

虽然目前的 reactive 已经能工作,但依赖收集、代理拦截和代理创建都写在一起,违反了单一原则

将代码按职责拆成三个模块:

  • dep.ts:负责 tracktriggerDep
  • baseHandlers.ts:负责 Proxygetset
  • reactive.ts:负责创建代理、缓存代理和暴露 API

拆分 dep.ts

保存依赖收集和更新派发的逻辑。

import type { Link, ReactiveNode } from './system'
import { activeSub } from './effect'
import { link, propagate } from './system'

const targetMap: WeakMap<object, Map<unknown, Dep>> = new WeakMap()

export function track(target: object, key: unknown) {
  if (!activeSub) {
    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 Dep()))
  }

  link(dep, activeSub)
}

export function trigger(target: object, key: unknown) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }

  const dep = depsMap.get(key)
  if (!dep) {
    return
  }

  propagate(dep.subs)
}

class Dep implements ReactiveNode {
  subs: Link
  subsTail: Link
}

这样 deps.ts 存放的就是链表相关的逻辑

拆分 baseHandlers.ts

baseHandlers.ts 保存代理对象的拦截逻辑。

getset 提前创建好,也可以避免每次调用 createReactiveObject 时都重新创建一份 handler 对象。

import { isRef } from '@vue/reactivity'
import { hasChanged, isObject } from '@vue/shared'
import { track, trigger } from './dep'
import { reactive, ReactiveFlags } from './reactive'

const get = createGetter()
const set = createSetter()

function createGetter() {
  return function get(target, key, receiver) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return true
    }

    if (key === ReactiveFlags.RAW) {
      return target
    }

    track(target, key)

    const res = Reflect.get(target, key, receiver)

    if (isRef(res)) {
      return res.value
    }

    if (isObject(res)) {
      return reactive(res)
    }

    return res
  }
}

function createSetter() {
  return function set(target, key, value, receiver) {
    const oldValue = target[key]

    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }

    const res = Reflect.set(target, key, value, receiver)

    if (hasChanged(oldValue, value)) {
      trigger(target, key)
    }

    return res
  }
}

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set
}

这样 baseHandlers.ts 只关心代理行为:

  • get 中处理响应式标记、依赖收集、ref 解包和嵌套对象代理
  • set 中处理 ref 写入、相同值判断和更新派发

改造 reactive.ts

reactive.ts 只负责创建代理对象。

import { isObject } from '@vue/shared'
import { mutableHandlers } from './baseHandlers'

export enum ReactiveFlags {
  IS_REACTIVE = '__v_isReactive',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.RAW]?: any
}

const reactiveMap = new WeakMap<Target, any>()

export function reactive(target: object) {
  return createReactiveObject(target, reactiveMap, mutableHandlers)
}

function createReactiveObject(
  target: object,
  proxyMap: WeakMap<Target, any>,
  baseHandlers: ProxyHandler<object>
) {
  if (!isObject(target)) {
    return target
  }

  if ((target as Target)[ReactiveFlags.RAW]) {
    return target
  }

  const existProxy = proxyMap.get(target)
  if (existProxy) {
    return existProxy
  }

  const proxy = new Proxy(target, baseHandlers)

  proxyMap.set(target, proxy)
  return proxy
}

export function isReactive(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

shallowReactive

shallowReactive 只代理对象第一层。

如果属性值还是对象,不会继续递归转成响应式对象。

it('shallowReactive 只处理第一层响应式', () => {
  const state = shallowReactive({
    a: {
      b: 1
    }
  })
  const fn = vi.fn()

  effect(() => {
    state.a.b
    fn()
  })

  expect(isReactive(state)).toBe(true)
  expect(isShallow(state)).toBe(true)
  expect(isReactive(state.a)).toBe(false)

  state.a.b = 2
  expect(fn).toBeCalledTimes(1)

  state.a = {
    b: 3
  }
  expect(fn).toBeCalledTimes(2)
})

这个测试要验证三件事:

  • state 本身是响应式对象
  • state 是浅层响应式对象
  • 修改深层属性 state.a.b 不触发更新,替换第一层属性 state.a 会触发更新

所以 shallowReactive 的关键点在 get

读取第一层属性时仍然会收集依赖,但拿到结果后直接返回,不能继续递归调用 reactive

增加响应式标记

现在需要区分三类状态:

  • 是否是 reactive
  • 是否是 readonly
  • 是否是 shallow

先增加对应的标记和判断方法。

export enum ReactiveFlags {
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.IS_SHALLOW]?: boolean
  [ReactiveFlags.RAW]?: any
}

export function readonly(target: object) {
}

export function shallowReactive(target: object) {
}

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

export function isShallow(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}

这些标记和之前的 IS_REACTIVERAW 一样,并不是真实挂在对象上的属性,而是通过代理的 get 拦截返回。

改造 createGetter

reactiveshallowReactivereadonly 的读取行为很接近。

可以把差异收敛到两个参数里:

  • isReadonly:是否只读
  • shallow:是否浅层代理
function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key, receiver) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    }

    if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    }

    if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    }

    if (key === ReactiveFlags.RAW) {
      return target
    }

    if (!isReadonly) {
      track(target, key)
    }

    const res = Reflect.get(target, key, receiver)

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      return res.value
    }

    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

每个分支对应不同能力:

  • 读取响应式标记时,直接返回当前代理类型
  • readonly 不收集依赖
  • shallow 在读取第一层属性后直接返回,不处理 ref 解包和嵌套对象代理
  • 深层对象会根据当前类型继续转成 readonlyreactive

处理 readonly set

readonly 的读取可以复用 createGetter(true, false),但写入行为需要单独处理。

只读对象被修改时,不应该真正写入,也不应该触发更新。

function createReadonlySetter() {
  return function set(target, key) {
    console.warn(`${target} is readonly`)
    return true
  }
}

这里返回 true 是为了表示 set 拦截已经处理完成,避免在严格模式下因为返回 false 抛错。

组合 handlers

最后创建不同的 getset,再组合成对应的 handlers。

const get = createGetter(false, false)
const readonlyGet = createGetter(true, false)
const shallowGet = createGetter(false, true)

const set = createSetter()
const readonlySet = createReadonlySetter()

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set
}

export const shallowReactiveHandlers: ProxyHandler<object> = {
  get: shallowGet,
  set
}

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set: readonlySet
}

这样三个 API 就可以复用同一个 createReactiveObject

const reactiveMap = new WeakMap<Target, any>()
const shallowReactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>()

export function reactive(target: object) {
  return createReactiveObject(target, reactiveMap, mutableHandlers)
}

export function shallowReactive(target: object) {
  return createReactiveObject(target, shallowReactiveMap, shallowReactiveHandlers)
}

export function readonly(target: object) {
  return createReactiveObject(target, readonlyMap, readonlyHandlers)
}