vue3源码分析(三)—— 响应式系统(reactivity)
系列文章目录提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章 Python 机器学习入门之pandas的使用文章目录系列文章目录前言一、reactivity模块有哪些API二、reactive模块1. 核心方法 - reactive(target: object)2.创建响应式对象 - createReactiveObject3.baseHandlers(Object
系列文章目录
- 目录分析
- 初始化流程
- 响应式系统
- shared工具函数
文章目录
前言
无论在vue2或者vue3中,实现一个vue的响应式系统(改变数据,自动渲染组件),大致分为两部分:
- 定义响应式数据
- 依赖处理:收集和派发
packages/reactivity
文件夹下的src中就是实现响应式逻辑的源码
一、定义响应式数据
- vue2中,只需要在组件的
data
中定义数据,这些数据自动就会变成响应式的数据。原理是在组件初始化时,遍历data中的属性,依次利用Object.defineProperty
中的getter setter进行拦截操作。 - 而从vue3的setup API可知,我们需要调用 reactive 方法,手动定义一个响应式数据。
如以下代码示例:
<template>
<p>{{obj.count}}</p>
</template>
import {reactive} from 'vue'
const obj = reactive({count: 0})
obj.count++ // obj.count --> 1
那 reactive
方法背后的实现原理是什么呢???下面通过分析源码看下~~
1、reactive(target)
reactive方法主要作用:接收一个普通对象target然后返回该普通对象的响应式代理
定位到[runtime-core/reactivity/reactive.ts]源码:
export function reactive(target: object) {
// 如果target是一个只读的响应式数据,则直接返回。因为已经是响应式了
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 创建一个响应式对象
return createReactiveObject(
target, // 传入的响应式对象
false, // 是否只读
mutableHandlers, // proxy handle
mutableCollectionHandlers, // 集合数据(Map,WeakMap,Set,WeakSet)的 proxy handle
reactiveMap // 全局缓存的一个map结构,存储所有的proxy
)
}
reactive
作为定义响应式数据的入口API
,将一个target普通对象包装成响应式的对象,里面核心实现方法是:createReactiveObject
(如下)
2、createReactiveObject
方法主要作用:创建一个响应式数据对象
/**
* 创建响应式对象
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any> // 一个缓存weakmap,key是 target,value是响应式对象
) {
// 判断一:若不是对象,则直接返回,特别注意null也直接返回, reactive(null) => null
if (!isObject(target)) {
return target
}
// 判断二:若已经是proxy对象,并且。。。
if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 判断三:从proxyMap数据中拿到key为target的响应式对象,若存在,则直接返回已创建过的响应式对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 判断四:类型不是object array map set weakmap weakset 都在白名单之外,不创建代理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 通过所有判断,用有效的tagert创建new Proxy代理对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 存入缓存map 中
proxyMap.set(target, proxy)
return proxy
}
2.1 入参
target
传入的要被创建响应式的对象,同reactive(见上)的targetisReadonly
是否只读 ,通过reactive创建的为false
非只读(见上)baseHandlers
普通对象(Object,Array)的proxy handler(get,set,deleteProperty,has, ownKeys)。通过reactive创建的为mutableHandlers
(见[baseHandlers.ts]),为之后new Proxy(target, handlers)
传入
// proxy handlers
export const mutableHandlers: ProxyHandler<object> = {
get, // 拦截对象属性的读取,指向了方法 createGetter, 创建 get 劫持
set, // 拦截对象属性的设置
deleteProperty, // 拦截delete proxy[propKey]操作
has, // 拦截propKey in proxy操作
ownKeys // 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
}
-
collectionHandlers
!!!集合数据(Map,WeakMap,Set,WeakSet)的proxy handler 。通过reactive创建的为mutableCollectionHandlers
(见[collectionHandlers.ts]),为之后new Proxy(target, handlers)
传入 -
proxyMap
存储所有的proxy的WeakMap(Map与WeakMap区别可自行百度)结构数据,key是 target对象, value是响应式对象。reactive
方法传入的是reactiveMap
,
// reactiveMap全局对象,存储所有的proxy对象
export const reactiveMap = new WeakMap<Target, any>()
2.2 响应式创建过程
首先,判断入参target是否为有效参数
isObject
判断target是否为为对象,若不是对象,直接返回target- 若target已经是proxy对象,直接返回target
existingProxy = proxyMap.get(target)
若从全局proxyMap对象中能拿到该对象的代理对象,则直接返回拿到的代理对象- 检查target的类型,若不是object array map set weakmap weakset 中的,则直接返回target
接着,创建proxy对象
- 通过
new Proxy(target, handlers)
为target创建代理 - 并且根据target类型,选择合适的
handers:TargetType.COLLECTION ? collectionHandlers : baseHandlers
。 Map
,Set
,WeakMap
,WeakSet
集合(TargetType.COLLECTION
)类型,在proxy中使用的是collectionHandlers
Object
,Array
在proxy中使用的是baseHandlers
,下面具体看一下
最后,全局存储target对应的Proxy对象: proxyMap.set(target, proxy)
,并返回:return proxy
2.3 proxy handler
从上可知,reactive方法创建响应式对象时,传入的baseHandlers具体为mutableHandlers
,
mutableHandlers是从packages/src/reactivity/baseHandlers.ts
文件引入而来,baseHandlers.ts
文件定义了所有不同的baseHandlers,对应不同的数据处理
import {
mutableHandlers,
readonlyHandlers,
...
} from './baseHandlers'
以baseHandlers.ts
中的 mutableHandlers
为例:new Proxy(target, mutableHandlers)
export const mutableHandlers: ProxyHandler<object> = {
get, // 拦截对象属性的读取,指向了方法 createGetter, 创建 get 劫持
set, // 拦截对象属性的设置
deleteProperty, // 拦截delete proxy[propKey]操作
has, // 拦截propKey in proxy操作
ownKeys // 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
}
通过以上分析可知,vue3将一个普通对象变为响应式对象,核心是通过new Proxy(ES6语法)
创建代理,拦截set,get,deleteProperty,has,ownKeys
操作监听。
2.4 get 做了什么?
访问对象属性会触发get函数
const get = createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// ....
// 当获取target自身属性时,receiver --》 reactiveMap.get(target)拿到代理对象
if (key === ReactiveFlags.RAW && receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
// 数组操作
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 关键代码
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 非数组操作:关键代码-利用 Reflect 反射来获取原始值
const res = Reflect.get(target, key, receiver)
// 关键代码2:调用track,收集依赖。若只读,不会变更,无需追踪
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
//返回target属性值
return res
}
}
以上可知,get函数大致做了2件事:
track
依赖收集(类似于vue2的getter中的dep.depend()
)。把组件渲染期间 依赖的property记录下来,之后触发依赖项setter
时,会通知更新,使得组件重新渲染- get最终就是通过
Reflect.get
要拿到代理对象属性值res
思考:为什么用 Reflect.get,而不是直接 target[key] 返回呢?
Reflect.get(target, key, receiver)
- target (取值的目标对象)、 key (获取的值的键值)、receiver(如果target对象中指定了getter,receiver则为getter调用时的this值)。
- 返回值:属性的值
var target = {
foo: 1,
get baz() {
return this.foo;
},
};
const observed = reactive(target)
此时,如果用target[key]
取值,那么 this.foo 中的 this 就指向的是 target,而不是 observed,此时 this.foo 就不能收集到 foo 的依赖了,如果 observed.foo = 20 改变了 foo 的值,那么是无法触发依赖回调的,所以需要利用 Reflect.get 中的第三个参数receiver
将 getter 里的 this 指向代理对象observed。
2.5 set 做了什么?
设置对象属性会触发set函数
const set = createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
oldValue = toRaw(oldValue)
// 非数组类型,若旧值是ref类型,新值不是ref类型,那么直接复制给oldValue.value。ref数据在set value时就已经trigger依赖了,所以直接return
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
}
// 关键代码,设置值
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
// 对象上没有这个key,trigger add
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 对象上有这个key且value发生了变化,trigger set
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
以上可知,set函数大致做了2件事:
- set就是通过
Reflect.set
要设置代理对象属性值 trigger
派发更新,使得组件重新渲染。
二、依赖收集与派发更新
1、依赖收集——track
track方法在 reactivity模块中effect.ts
模块有定义,如下:
export function track(target: object, type: TrackOpTypes, key: unknown) {
// activeEffect不存在,直接return
if (!shouldTrack || activeEffect === undefined) {
return
}
// targetMap 依赖管理中心,用于收集依赖和触发依赖
let depsMap = targetMap.get(target)
if (!depsMap) {
// target 在 targetMap 对应的值是 depsMap targetMap(key:target, value:depsMap(key:key, value:dep(activeEffect)))
// set结构防止重复
targetMap.set(target, (depsMap = new Map()))
}
//此时经过上面的判断,depsMap 必定有值了。然后尝试在 depsMap 中获取 key
let dep = depsMap.get(key)
if (!dep) { // 判断有无当前key对应的dep,没有则创建
//如果没有获取到dep,说明 target.key 并没有被追踪,此时就在 depsMap 中塞一个值
depsMap.set(key, (dep = new Set())) // 执行了这句后,targetMap.get(target) 的值也会相应的改变
}
// 这个 activeEffect 就是在 effect执行的时候的那个 activeEffect
if (!dep.has(activeEffect)) {
dep.add(activeEffect) // 将effect放到dep里面
activeEffect.deps.push(dep) // 双向存储
}
}
- 以上涉及到了2个关键对象:
activeEffect
、targetMap
。最终将activeEffect收集到targetMap全局Map结构中,同时将dep放到activeEffect下的deps数组。 - 在vue2中,做依赖收集时,收集的是
watcher
,而vue3已经没有了watcher概念,取而代之的是effect
(副作用函数)。 targetMap
是一个全局WeakMap对象,作为一个依赖收集容器,用于存储target[key]相应的dep依赖。targetMap.get(target)
获取target对应的depsMap
,depsMap内部又是一个Map,key为target中的属性,depsMap.get(key)
则为Set结构存储的target[key]对应的dep
,dep中则是存储了所有依赖的effects。- 方便在修改数据触发set时,找到对应的effect依赖并执行。
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

2、activeEffect
activeEffect同样在 reactivity模块中effect.ts
模块有定义,表示正在运行中的effect
:
let activeEffect: ReactiveEffect | undefined // 当前正在运行的effect
3、effect
effect 作为 reactive 的核心,主要负责收集依赖,更新依赖。
3.1 effect如何被定义
看下源码方法定义:
export function effect<T = any>(
fn: () => T, // 接收一个回调函数
options: ReactiveEffectOptions = EMPTY_OBJ // 配置{lazy:false,...}
): ReactiveEffect<T> {
if (isEffect(fn)) { // 若fn已经是一个effect对象
fn = fn.raw // effect对象中:effect.raw = fn
}
// 创建`effect`,执行createReactiveEffect后得到的是一个 function 即 reactiveEffect
const effect = createReactiveEffect(fn, options)
// 非lazy时,立即执行effect()。computed effect为lazy模块
if (!options.lazy) {
effect()
}
return effect
}
isEffect(fn)
判定fn是否已经是一个effect,若是fn.raw就是原始的回调函数,fn = fn.raw (effect.raw = fn)createReactiveEffect(fn, options)
返回一个effect函数options.lazy
若为false,非惰性更新,则立即执行一次。computed effect为true
下面看看effect如何被创建的:
function createReactiveEffect<T = any>(
fn: () => T, // 回调函数
options: ReactiveEffectOptions // 传入的配置
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// 没有激活,说明调用了effect stop函数
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect) // 清除effect依赖
try {
enableTracking() // 重新进行依赖收集
effectStack.push(effect) // 将当前effect压入effectStack栈
activeEffect = effect // 当前正在运行的effect
return fn() // 执行回调
} finally { // 最后将effect出栈,恢复之前的状态
effectStack.pop()
resetTracking() // 重置依赖
activeEffect = effectStack[effectStack.length - 1] // 重置activeEffect
}
}
} as ReactiveEffect
effect.id = uid++ // 自增ID,唯一标识
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true // 用于标识方法是否经历过effect,是否是effect
effect.active = true // effect是否激活,默认false。调用stop函数之后,修改为false
effect.raw = fn // 传入的fn
effect.deps = [] // 持有当前effect的dep数组,在track时收集dep,dep就是追踪列表中对应的key,即 targetMap.get(target).get(key)
effect.options = options
return effect
}
- 生成一个挂载了一系列属性:
id,allowRecurse,_isEffect,active,raw,deps, options
的reactiveEffect方法。 - effectStack是一个作为全局变量的effect栈,类似于vue2中存储watcher的栈:
const effectStack: ReactiveEffect[] = []
- 在执行effect()时,若effectStack没有当前effect,首先
清空effect.deps数组中的依赖
,每次effect 运行时都会重新收集依赖。(在上面track那里,曾给deps数组塞值:activeEffect.deps.push(dep)) effectStack.push(effect)
,将当前effect压入effectStack栈activeEffect = effect
, 给activeEffect赋值,表示当前正在运行的effect,activeEffect 主要为了在收集依赖的时候使用- 执行回调
fn()
,最后将effect出栈,恢复之前的状态,重置activeEffect
3.2 effect都在什么时候创建呢?
1、mountComponent
2、computed
3、watcher、watchEffect
3、依赖更新派发——trigger
依赖收集完毕,接下来target的属性值修改会触发trigger,拿到相应的依赖并执行effect
export function trigger(
target: object,
type: TriggerOpTypes, // set | add | delete | clear
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target) //targetMap上面讲到,是全局的依赖收集器
if (!depsMap) { /* targetMap中没有该值,说明没有收集该effect,无需追踪*/
return
}
const effects = new Set<ReactiveEffect>()
/* 将合规的effect添加进effects set集合中*/
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) { // 若是clear
depsMap.forEach(add) // 触发对象所有的effect
} else if (key === 'length' && isArray(target)) { // 若数组的length发生变化
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else { // schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
...
// 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
// 关键代码,所有的effects会执行内部run方法
effects.forEach(run)
}
- 首先校验一下target有没有被收集依赖,若没有收集依赖,则return
- 根据不同的操作clear add delete set,将合规的effect加入到effects set集合中
- 遍历effects set集合,执行effect函数
以上,整个响应式系统已经形成了一个基本的闭环。
三、reactivity模块其他API
上一篇大致也介绍了vue源码各目录存放的API,那响应式模块都在runtime-core/reactive目录中。
打开packages/reactivity/src/index.ts
,可看到导出的API
主要是四个部分:
ref
响应式的关键入口,作用同reactivereactive
响应式的关键入口 (本文重点讲述)computed
同vue2 的computed选项effect
作用同vue2的watcher(Vue3中已经没有了watcher概念,由effect取而代之)
export {
ref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
Ref,
ToRefs,
UnwrapRef,
ShallowUnwrapRef,
RefUnwrapBailTypes
} from './ref'
export {
reactive, // --> 创建响应式对象的入口方法
readonly,
isReactive,
isReadonly,
isProxy,
shallowReactive,
shallowReadonly,
markRaw,
toRaw,
ReactiveFlags,
DeepReadonly,
UnwrapNestedRefs
} from './reactive'
export {
computed,
ComputedRef,
WritableComputedRef,
WritableComputedOptions,
ComputedGetter,
ComputedSetter
} from './computed'
export {
effect,
stop,
trigger,
track,
enableTracking,
pauseTracking,
resetTracking,
ITERATE_KEY,
ReactiveEffect,
ReactiveEffectOptions,
DebuggerEvent
} from './effect'
export { TrackOpTypes, TriggerOpTypes } from './operations'
总结
vue2响应式原理:
- 在 Vue2 里内部通过 Object.defineProperty API 劫持数据的变化,深度遍历 data 函数里的对象,给对象里每一个属性设置 getter、setter。
- 触发 getter 会通过 Dep 类做依赖收集操作,收集当前 Dep.target, 也就是 watcher。
- 触发 setter,执行 dep.notify 通知收集到的各类 watcher 更新,如 computed watcher、user watcher 、渲染 watcher。
Vue3响应式原理:
- Vue3 用 ES6的Proxy 重构了响应式,new Proxy(target, handler)
- Proxy 的 get handle 里 执行track() 用来跟踪收集依赖(收集 activeEffect,也就是 effect )
- Proxy 的 set handle 里执行 trigger() 用来触发响应(执行收集的 effect)
- effect 副作用函数 代替了 watcher

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)