Vue3 Reactive Handler 深度解析:全面拆解 reactive/readonly 代理体系

一、背景:Vue3 代理体系的核心意义

Vue3 的响应式系统基于 Proxy 重构,而整套系统的核心入口只有两个:

  • reactive(obj)
  • readonly(obj)

它们能工作,是因为 Vue 内部构造了四组极其精妙的处理器(handlers):

  • mutableHandlers ------ reactive 的处理器
  • readonlyHandlers ------ readonly 的处理器
  • shallowReactiveHandlers ------ 浅层 reactive
  • shallowReadonlyHandlers ------ 浅层 readonly

这些 handler 就是真正拦截对象所有行为(get/set/delete/has/ownKeys)的核心。

全文将逐段解析你提供的这段源码。


二、整体设计图示(关键结构图)

scss 复制代码
┌───────────────────────────────────────┐
│ reactive(target)                      │
│   │                                    │
│   ▼                                    │
│ new Proxy(target, mutableHandlers) →───▶ 触发 getter / setter / track / trigger
│                                         │
│ readonly(target)                        │
│   ▼                                     │
│ new Proxy(target, readonlyHandlers) ───▶ 拦截写入 + 告警
└───────────────────────────────────────┘

进一步细分 shallow:

bash 复制代码
reactive → mutableHandlers
shallowReactive → shallowReactiveHandlers

readonly → readonlyHandlers
shallowReadonly → shallowReadonlyHandlers

每个 handler 都继承自 BaseReactiveHandler(除了 readonly 额外封锁写入)。


三、源码逐段解析(逐行注释 + 原理)

以下直接解析你发的源码。


3.1 import 区域

typescript 复制代码
import {
  type Target,
  isReadonly,
  isShallow,
  reactive,
  reactiveMap,
  readonly,
  readonlyMap,
  shallowReactiveMap,
  shallowReadonlyMap,
  toRaw,
} from './reactive'

作用解析:

  • reactiveMap / readonlyMap:WeakMap,缓存 proxy → raw
  • toRaw:从代理中取原始对象
  • isReadonly / isShallow:元数据状态标记

3.2 arrayInstrumentations

Array 的特殊行为,比如:

  • push
  • includes
  • indexOf

这些需要特殊处理,以避免读写重复触发依赖。


3.3 一组常量与工具函数

go 复制代码
const isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`)

这些 key 访问时 不需要 track,避免无意义的依赖。

javascript 复制代码
const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .filter(key => key !== 'arguments' && key !== 'caller')
    .map(key => Symbol[key])
    .filter(isSymbol),
)

这段代码用于过滤所有 内建 Symbol,例如:

  • Symbol.iterator
  • Symbol.toStringTag

Vue 不会对它们进行 track,否则会污染依赖系统。


3.4 hasOwnProperty 重写版本

vbnet 复制代码
function hasOwnProperty(this: object, key: unknown) {
  if (!isSymbol(key)) key = String(key)
  const obj = toRaw(this)
  track(obj, TrackOpTypes.HAS, key)
  return obj.hasOwnProperty(key as string)
}

关键点

  1. hasOwnProperty 被代理后需要 track
  2. this 始终指向 raw,而不是 proxy

3.5 BaseReactiveHandler 核心:get 拦截器

源码如下(重点高亮注释版):

kotlin 复制代码
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}

  get(target, key, receiver) {
    // 1. 读取内部标识 
    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)
        ||
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      return
    }

    const targetIsArray = isArray(target)

    // 2. 处理数组方法 instrumentations
    if (!isReadonly) {
      let fn
      if (targetIsArray && (fn = arrayInstrumentations[key])) {
        return fn
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    // 3. 真正的读取
    const res = Reflect.get(
      target,
      key,
      isRef(target) ? target : receiver,
    )

    // 4. 内建 Symbol 或非 track key,不做依赖收集
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 5. 依赖收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 6. 浅层不做递归代理
    if (isShallow) {
      return res
    }

    // 7. ref unwrapping
    if (isRef(res)) {
      const value =
        targetIsArray && isIntegerKey(key) ? res : res.value
      return isReadonly && isObject(value) ? readonly(value) : value
    }

    // 8. 对象 → 深层 reactive 或 readonly
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

核心逻辑图示

scss 复制代码
get()
 ├── 内部标记? → 返回状态
 ├── 数组特殊函数? → 包装方法
 ├── 依赖收集 track()
 ├── shallow? → 直接返回
 ├── ref? → 解包
 ├── object? → 递归代理
 └── 返回原值

这是 Vue3 响应式系统最重要的逻辑之一。


四、MutableReactiveHandler:核心 setter

源码(高亮注释版):

scss 复制代码
set(target, key, value, receiver) {
  let oldValue = target[key]

  // 1. 非 shallow,需要转 raw
  if (!this._isShallow) {
    const isOldValueReadonly = isReadonly(oldValue)
    if (!isShallow(value) && !isReadonly(value)) {
      oldValue = toRaw(oldValue)
      value = toRaw(value)
    }

    // 2. oldValue 是 ref,value 不是 ref
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
      if (isOldValueReadonly) {
        warn(...)
        return true
      } else {
        oldValue.value = value
        return true
      }
    }
  }

  // 3. 判断 key 是否已存在
  const hadKey = isArray(target)
    ? Number(key) < target.length
    : hasOwn(target, key)

  // 4. 正常写入
  const result = Reflect.set(
    target,
    key,
    value,
    isRef(target) ? target : receiver
  )

  // 5. 触发 trigger
  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
}

核心行为

  • 支持 ref 自动解包赋值(ref.value = x
  • 新 key → ADD 事件
  • 存在的 key 改变 → SET 事件
  • shallow 模式不做 deep unwrap

五、ReadonlyReactiveHandler:阻止写入

javascript 复制代码
set() {
  warn("target is readonly")
  return true
}

deleteProperty() {
  warn("target is readonly")
  return true
}

readonly 代理禁止改动,但为了不破坏逻辑,返回 true


六、最终导出 4 套 handler

arduino 复制代码
export const mutableHandlers = new MutableReactiveHandler()
export const readonlyHandlers = new ReadonlyReactiveHandler()
export const shallowReactiveHandlers = new MutableReactiveHandler(true)
export const shallowReadonlyHandlers = new ReadonlyReactiveHandler(true)

其中:

  • shallow = 是否浅层
  • readonly = 是否只读

构成 2×2 的组合。


七、实际示例:执行流程演示

示例 1:普通 reactive

ini 复制代码
const state = reactive({ a: 1 })
state.a
state.a = 2

步骤:

  1. 触发 get → track
  2. 触发 set → trigger

示例 2:ref 自动解包

ini 复制代码
const r = ref(5)
const obj = reactive({ x: r })
obj.x = 10
console.log(r.value) // 10

set() 判断:

  • oldValue 是 ref,value 不是 ref
    → 自动写入 oldValue.value

示例 3:readonly 阻止写入

csharp 复制代码
const obj = readonly({ x: 1 })
obj.x = 2
// warn: failed: target is readonly

八、扩展:Vue3 响应式代理的优势

  • 深层代理 lazy(按需代理)
  • ref 自动解包规则
  • 数组方法特殊处理
  • 避免原型链污染
  • 降低依赖数量(内建 Symbol 不 track)

这些共同构成性能优异的响应式系统。


九、潜在问题与注意事项

  • shallow 模式不会递归代理
  • readonly 并非真正不可修改(可以绕过 Proxy)
  • ref 自动解包可能带来语义差异
  • 对象引用会导致深层递归代理,过大对象要小心

全文结束

本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
消失的旧时光-19435 小时前
Kotlinx.serialization 对多态对象(sealed class )支持更好用
java·服务器·前端
少卿6 小时前
React Compiler 完全指南:自动化性能优化的未来
前端·javascript
广州华水科技6 小时前
水库变形监测推荐:2025年单北斗GNSS变形监测系统TOP5,助力基础设施安全
前端
广州华水科技6 小时前
北斗GNSS变形监测一体机在基础设施安全中的应用与优势
前端
七淮6 小时前
umi4暗黑模式设置
前端
8***B6 小时前
前端路由权限控制,动态路由生成
前端
军军3606 小时前
从图片到点阵:用JavaScript重现复古数码点阵艺术图
前端·javascript
znhy@1236 小时前
Vue基础知识(一)
前端·javascript·vue.js
terminal0076 小时前
浅谈useRef的使用和渲染机制
前端·react.js·面试
我的小月月6 小时前
🔥 手把手教你实现前端邮件预览功能
前端·vue.js