第201集:Vue 3响应式系统源码分析
概述
Vue 3的响应式系统是其核心特性之一,采用了ES6的Proxy API来实现,相比Vue 2的Object.defineProperty具有更强大的功能和更好的性能。本集将深入分析Vue 3响应式系统的源码实现,包括核心概念、关键类和函数、以及响应式系统的工作原理。
核心概念
1. 响应式基础
- 响应式数据:当数据变化时,依赖该数据的组件会自动更新
- 依赖收集:跟踪哪些组件依赖于哪些数据
- 触发更新:当数据变化时,通知所有依赖该数据的组件更新
2. 关键类型
- Ref:用于包装基本类型数据的响应式对象
- Reactive:用于包装对象类型数据的响应式代理
- Computed:用于计算属性的响应式对象
- Watch:用于监听数据变化的响应式机制
响应式系统架构
Vue 3的响应式系统主要包含以下几个核心模块:
- 响应式核心:
reactive.ts- 实现reactive、ref、computed等核心API - 依赖收集:
effect.ts- 实现依赖收集和触发更新 - 响应式工具:
baseHandlers.ts- 实现Proxy的各种处理器 - 响应式辅助:
ref.ts- 实现Ref类型的响应式 - 计算属性:
computed.ts- 实现Computed类型的响应式
核心源码分析
1. Reactive API实现
// reactive.ts 核心实现
import { mutableHandlers } from './baseHandlers'
import { isObject } from '@vue/shared'
// 存储响应式代理的WeakMap
const reactiveMap = new WeakMap<any, any>()
// reactive函数:创建响应式代理
export function reactive(target: object) {
// 如果target不是对象,直接返回
if (!isObject(target)) {
return target
}
// 如果target已经是响应式代理,直接返回
if (target.__v_isReactive) {
return target
}
// 检查是否已有对应的响应式代理
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建响应式代理
const proxy = new Proxy(target, mutableHandlers)
// 缓存代理对象
reactiveMap.set(target, proxy)
// 标记为响应式对象
proxy.__v_isReactive = true
return proxy
}2. Proxy处理器实现
// baseHandlers.ts 核心实现
import { track, trigger } from './effect'
import { isObject } from '@vue/shared'
// mutableHandlers:可变对象的处理器
export const mutableHandlers = {
// 获取属性值时触发
get(target: object, key: string | symbol, receiver: object) {
// 处理特殊情况
if (key === '__v_isReactive') {
return true
}
// 追踪依赖
track(target, 'get', key)
// 获取原始值
const res = Reflect.get(target, key, receiver)
// 如果获取的是对象,递归创建响应式
if (isObject(res)) {
return reactive(res)
}
return res
},
// 设置属性值时触发
set(target: object, key: string | symbol, value: any, receiver: object) {
// 获取旧值
const oldValue = (target as any)[key]
// 设置新值
const result = Reflect.set(target, key, value, receiver)
// 如果值发生变化,触发更新
if (oldValue !== value) {
trigger(target, 'set', key, value, oldValue)
}
return result
},
// 删除属性时触发
deleteProperty(target: object, key: string | symbol) {
// 检查属性是否存在
const hadKey = hasOwn(target, key)
// 删除属性
const result = Reflect.deleteProperty(target, key)
// 如果属性存在且删除成功,触发更新
if (hadKey && result) {
trigger(target, 'delete', key)
}
return result
},
// 检查属性是否存在时触发
has(target: object, key: string | symbol) {
const result = Reflect.has(target, key)
// 追踪依赖
track(target, 'has', key)
return result
},
// 获取属性描述符时触发
getOwnPropertyDescriptor(target: object, key: string | symbol) {
return Reflect.getOwnPropertyDescriptor(target, key)
}
}3. 依赖收集与触发更新
// effect.ts 核心实现
// 存储当前活跃的副作用函数
let activeEffect: ReactiveEffect | undefined
// 依赖映射:target -> key -> effects
const targetMap = new WeakMap<any, Map<string | symbol, Set<ReactiveEffect>>>()
// 副作用类
class ReactiveEffect {
constructor(
public fn: () => any,
public scheduler?: EffectScheduler
) {}
// 运行副作用函数
run() {
// 保存当前副作用函数
activeEffect = this
try {
// 执行副作用函数,触发依赖收集
return this.fn()
} finally {
// 清除当前副作用函数
activeEffect = undefined
}
}
}
// 追踪依赖
export function track(target: object, type: TrackOpTypes, key: string | symbol) {
// 如果没有活跃的副作用函数,直接返回
if (!activeEffect) {
return
}
// 获取target对应的依赖映射
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取key对应的副作用集合
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 如果副作用函数不在集合中,添加进去
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
}
}
// 触发更新
export function trigger(target: object, type: TriggerOpTypes, key?: string | symbol, newValue?: any, oldValue?: any) {
// 获取target对应的依赖映射
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
// 收集需要触发的副作用函数
const effects = new Set<ReactiveEffect>()
// 根据操作类型,获取对应的副作用函数
if (key !== undefined) {
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effects.add(effect))
}
}
// 触发所有副作用函数
effects.forEach(effect => {
if (effect.scheduler) {
// 如果有调度器,使用调度器触发
effect.scheduler()
} else {
// 否则直接运行副作用函数
effect.run()
}
})
}
// watchEffect API
export function watchEffect(fn: () => any) {
const effect = new ReactiveEffect(fn)
effect.run()
return () => effect.stop()
}4. Ref API实现
// ref.ts 核心实现
import { track, trigger } from './effect'
import { isObject } from '@vue/shared'
import { reactive } from './reactive'
// Ref类型接口
interface Ref<T = any> {
value: T
}
// RefImpl类:实现Ref类型
class RefImpl<T> implements Ref<T> {
private _value: T
private _rawValue: T
constructor(value: T) {
// 保存原始值
this._rawValue = value
// 如果是对象,创建响应式代理
this._value = isObject(value) ? reactive(value) : value
}
// getter:获取值时追踪依赖
get value() {
track(this, 'get', 'value')
return this._value
}
// setter:设置值时触发更新
set value(newValue: T) {
if (newValue !== this._rawValue) {
// 更新原始值
this._rawValue = newValue
// 如果是对象,创建响应式代理
this._value = isObject(newValue) ? reactive(newValue) : newValue
// 触发更新
trigger(this, 'set', 'value', newValue)
}
}
}
// ref API
export function ref<T>(value: T): Ref<T> {
return new RefImpl(value)
}5. Computed API实现
// computed.ts 核心实现
import { ReactiveEffect } from './effect'
// ComputedRef类型接口
interface ComputedRef<T = any> {
value: T
}
// ComputedRefImpl类:实现Computed类型
class ComputedRefImpl<T> implements ComputedRef<T> {
private _value: T
private _dirty = true
private _effect: ReactiveEffect<T>
constructor(getter: () => T) {
// 创建副作用函数
this._effect = new ReactiveEffect(getter, () => {
// 调度器:当依赖变化时,标记为脏
if (!this._dirty) {
this._dirty = true
// 触发更新
trigger(this, 'set', 'value')
}
})
}
// getter:获取计算值
get value() {
// 如果是脏的,重新计算
if (this._dirty) {
this._value = this._effect.run()
this._dirty = false
}
// 追踪依赖
track(this, 'get', 'value')
return this._value
}
}
// computed API
export function computed<T>(getter: () => T): ComputedRef<T> {
return new ComputedRefImpl(getter)
}响应式系统工作流程
1. 依赖收集流程
- 当组件渲染时,会创建一个副作用函数
- 副作用函数执行时,会访问组件依赖的响应式数据
- 访问响应式数据时,会触发Proxy的get处理器
- get处理器调用track函数,将当前副作用函数添加到依赖集合中
- 依赖集合存储在targetMap中,格式为:target -> key -> effects
2. 触发更新流程
- 当响应式数据发生变化时,会触发Proxy的set处理器
- set处理器调用trigger函数,获取该数据的所有依赖副作用函数
- 遍历副作用函数集合,执行每个副作用函数
- 副作用函数重新执行,导致组件重新渲染
响应式系统优化
Vue 3的响应式系统相比Vue 2有以下优化:
- 使用Proxy API:相比Object.defineProperty,Proxy可以监听更多的对象操作,包括新增属性、删除属性、数组索引变化等
- 懒依赖收集:只有当响应式数据被实际访问时,才会进行依赖收集
- 更细粒度的更新:可以精确到对象的某个属性,而不是整个对象
- 支持Set和Map:可以监听Set和Map类型数据的变化
- 更好的性能:Proxy的性能比Object.defineProperty更好,尤其是在大型对象上
实际应用场景
1. 组件中的响应式数据
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
// 使用ref创建基本类型的响应式数据
const count = ref(0)
// 使用computed创建计算属性
const title = computed(() => `Count: ${count.value}`)
// 点击事件处理函数
const increment = () => {
count.value++
}
</script>2. 复杂对象的响应式
import { reactive } from 'vue'
// 创建复杂对象的响应式代理
const user = reactive({
name: 'John',
age: 30,
address: {
city: 'New York',
zip: '10001'
},
hobbies: ['reading', 'coding']
})
// 直接修改嵌套属性,会触发响应式更新
user.address.city = 'London'
// 添加新属性,会触发响应式更新
user.email = 'john@example.com'
// 删除属性,会触发响应式更新
delete user.age调试响应式系统
Vue 3提供了一些调试工具来帮助开发者调试响应式系统:
- isReactive:检查对象是否是响应式代理
- isRef:检查对象是否是Ref对象
- isComputed:检查对象是否是Computed对象
- toRaw:获取响应式代理的原始对象
- markRaw:标记对象为非响应式
import { reactive, isReactive, toRaw, markRaw } from 'vue'
const obj = { name: 'John' }
const reactiveObj = reactive(obj)
console.log(isReactive(reactiveObj)) // true
console.log(isReactive(obj)) // false
console.log(toRaw(reactiveObj) === obj) // true
const nonReactiveObj = markRaw({ name: 'Jane' })
const reactiveNonReactiveObj = reactive(nonReactiveObj)
console.log(reactiveNonReactiveObj === nonReactiveObj) // true总结
Vue 3的响应式系统是基于ES6 Proxy API实现的,具有强大的功能和良好的性能。通过深入分析其源码,我们可以了解到:
- 响应式系统的核心是依赖收集和触发更新
- Reactive API使用Proxy创建响应式代理
- Ref API用于包装基本类型数据的响应式
- Computed API用于实现计算属性的响应式
- Watch API用于监听数据变化
理解Vue 3响应式系统的源码实现,有助于我们更好地使用Vue 3进行开发,并在遇到问题时能够快速定位和解决。