(最新升级)Vue3入门与项目实战 掌握完整知识体系(已完结)

101 阅读5分钟

微信图片_20250704095931.jpg

(最新升级)Vue3入门与项目实战 掌握完整知识体系(已完结)-----97it.夏の哉-----top/--------410/

Vue3 响应式系统揭秘:ref/reactive 原理、依赖收集与更新机制详解

引言

Vue3 的响应式系统是其核心特性之一,它使得数据变化能够自动驱动视图更新。与 Vue2 基于 Object.defineProperty 的实现不同,Vue3 采用了 ES6 的 Proxy 重构了响应式系统,带来了更好的性能和更强大的功能。本文将深入剖析 Vue3 响应式系统的核心实现原理,包括 ref/reactive 的工作原理、依赖收集与触发更新的机制。

一、Vue3 响应式基础

1. reactive 的实现原理

reactive 是 Vue3 中用于创建响应式对象的核心 API,其底层基于 Proxy 实现:

function reactive(target) {
  // 如果尝试观察一个只读的代理,则返回只读版本
  if (target && target.__v_isReadonly) {
    return target
  }
  
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
  // 如果不是对象,直接返回
  if (!isObject(target)) {
    return target
  }
  
  // 如果目标已经是代理,直接返回
  if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
    return target
  }
  
  // 如果已经存在代理,返回已有代理
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 只有特定类型的值能被观察
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  
  proxyMap.set(target, proxy)
  return proxy
}

Proxy 处理器对象 mutableHandlers 主要实现了以下几个陷阱:

const mutableHandlers = {
  get: createGetter(),
  set: createSetter(),
  deleteProperty,
  has,
  ownKeys
}

2. ref 的实现原理

ref 是对 reactive 的补充,主要用于处理基本类型的响应式问题:

function ref(value) {
  return createRef(value)
}

function createRef(rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  
  return new RefImpl(rawValue, shallow)
}

class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow
    this.dep = undefined
    this.__v_isRef = true
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }
  
  get value() {
    trackRefValue(this)
    return this._value
  }
  
  set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      triggerRefValue(this)
    }
  }
}

ref 的核心是通过类的属性访问器(getter/setter)来实现响应式,对于对象类型的值,内部仍然会使用 reactive 进行转换。

二、依赖收集与触发更新

1. 依赖收集(track)

Vue3 的依赖收集发生在属性访问时,主要通过 track 函数实现:

function track(target, type, key) {
  if (!shouldTrack || activeEffect === undefined) {
    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)
}

function trackEffects(dep) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit
      shouldTrack = !wasTracked(dep)
    }
  } else {
    shouldTrack = !dep.has(activeEffect)
  }
  
  if (shouldTrack) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
  }
}

2. 触发更新(trigger)

当响应式数据发生变化时,会通过 trigger 函数触发更新:

function trigger(target, type, key, newValue, oldValue, oldTarget) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  
  let deps = []
  if (type === TriggerOpTypes.CLEAR) {
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= newValue) {
        deps.push(dep)
      }
    })
  } else {
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }
    
    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)) {
          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 effects = []
  for (const dep of deps) {
    if (dep) {
      effects.push(...dep)
    }
  }
  
  triggerEffects(createDep(effects))
}

function triggerEffects(dep) {
  for (const effect of dep) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

三、Effect 与响应式副作用

Vue3 通过 effect 函数来创建响应式副作用,这是响应式系统与组件更新之间的桥梁:

function effect(fn, options) {
  if (fn.effect) {
    fn = fn.effect.fn
  }
  
  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) {
      recordEffectScope(_effect, options.scope)
    }
  }
  
  if (!options || !options.lazy) {
    _effect.run()
  }
  
  const runner = _effect.run.bind(_effect)
  runner.effect = _effect
  return runner
}

class ReactiveEffect {
  constructor(fn, scheduler = null, scope) {
    this.fn = fn
    this.scheduler = scheduler
    this.active = true
    this.deps = []
    this.parent = undefined
    recordEffectScope(this, scope)
  }
  
  run() {
    if (!this.active) {
      return this.fn()
    }
    
    let parent = activeEffect
    let lastShouldTrack = shouldTrack
    
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true
      
      trackOpBit = 1 << ++effectTrackDepth
      
      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        cleanupEffect(this)
      }
      
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }
      
      trackOpBit = 1 << --effectTrackDepth
      
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
    }
  }
  
  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

组件的渲染函数就是在一个 effect 中执行的,当响应式数据变化时,effect 会重新执行,从而触发组件更新。

四、响应式系统的优化

Vue3 的响应式系统相比 Vue2 做了多项优化:

  1. Proxy 的优势

    • 可以检测到属性的添加和删除
    • 可以检测数组索引的变化和 length 的变化
    • 支持 Map、Set、WeakMap、WeakSet 等集合类型
  2. 嵌套自动解包

    • 访问 ref 值时不需要 .value(在模板中)
    • 嵌套的 ref 会自动解包
  3. 性能优化

    • 依赖收集采用位运算标记,提升性能
    • 避免不必要的依赖收集
    • 更精确的触发更新,减少不必要的组件更新
  4. 编译时优化

    • 编译器能够静态分析模板,生成更优化的代码
    • PatchFlags 和 dynamicChildren 等优化手段

五、响应式 API 对比

API适用类型深层响应式返回值类型解包需求
reactive对象/数组代理对象
ref任意类型Ref 对象需要 .value
shallowRef任意类型Ref 对象需要 .value
readonly对象/数组只读代理对象
computed计算属性-Ref 对象需要 .value

六、总结

Vue3 的响应式系统通过 Proxy 和 Reflect 重构,解决了 Vue2 中响应式系统的诸多限制,同时引入了更精细的依赖收集和触发更新机制,配合编译时的优化,使得性能得到了显著提升。理解响应式系统的内部工作原理,有助于我们更好地使用 Vue3 开发应用,并在遇到问题时能够快速定位和解决。

ref 和 reactive 作为两种不同的响应式创建方式,各有适用场景:ref 更适合基本类型和需要保持引用的情况,而 reactive 则适合对象和数组等复杂数据结构。在实际开发中,应根据具体需求选择合适的 API。