文件结构
主要源码在 packages/reactivity/src 目录下
主要模块
effect.ts
定义响应式副作用的核心逻辑,包括
watchEffect
、watch
的底层实现
核心方法
effect(fn, options?)
: 创建一个响应式副作用。track(target, type, key)
: 收集依赖。trigger(target, type, key, newValue?, oldValue?)
: 触发更新。
说明
effect.ts
是vue3实现响应式的核心模块reactive.ts
和ref.ts
都依赖effect.ts
。这是因为它们创建的响应式对象和 ref 都需要使用 effect(track | trriger) 来跟踪依赖和触发更新。computed.ts
也依赖effect.ts
,因为它需要创建一个 effect 来运行 getter 函数,并收集 getter 中访问的响应式数据的依赖。watch.ts
依赖effect.ts
来实现其响应式追踪。
track
和trigger
的实现依赖三层套娃-
最底层名为
deps
使用的是Set
,用来存储每个属性的effect()
可以理解为副作用,使用Set
可以去重 -
中间层名为
depsMap
,使用的是Map
,用来存储数据对象的每个属性的deps -
最上层为
targetMap
,使用的是WeakMap
,用来存储多个响应式对象。 响应式关系:javascripttargetMap (WeakMap) // effect.ts - 存储目标对象到 deps 的映射 │ └─ depsMap (Map) // 存储属性到依赖集合的映射 │ └─ dep (Set) // 存储具体依赖项
-
简易实现
- 实现
effect(fn)
, 忽略第二个参数options
js
// lazy load
let activeEffect = null;
function effect(fn){
activeEffect = fn;
activeEffect();
activeEffect = null;
}
- track(target,key)
js
const targetMap = new WeakMap();
const track = (target, key) => {
if (activeEffect) {
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 Set()))
}
dep.add(activeEffect)
}
}
- trigger(target,key)
js
const trigger = (target, key) => {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
reactive.ts
核心方法
reactive(target)
: 创建一个响应式代理对象。createReactiveObject(...)
: 创建响应式对象的内部实现。isReactive(value)
:判断一个对象是否是reactive对象shallowReactive(target)
: 创建一个浅层响应式对象(只有根级别的属性是响应式的)。
说明
reactive 基于Proxy
和 Reflect
实现,简单陈列以下这两个知识点,如下:
-
Proxy
- 代理对象:创建一个对象的代理,拦截并自定义对象的基本操作(如属性访问、赋值、函数调用等)
- 拦截操作 :可以监听 13 种对象操作(如
get
,set
,has
,deleteProperty
等) - 示例如下:
jsconst handler = { get (target,key){ return key in target ? target[key] : false }, set (target,key,value) { target[key] = parseFloat(value) } } const obj = {}; const objProxy = new Proxy(obj,handler); objProxy.a = '123'; console.log(objProxy.a) // 123; console.log(objProxy.b) // false;
-
Reflect
-
反射 API:提供一套操作对象的标准化方法(与 Proxy 方法一一对应)
-
替代隐式操作 :替代直接操作对象的隐式行为(如
obj[prop]
→Reflect.get(obj,prop)
) -
核心特性
js// 传统方式 obj.name = 'value'; // Reflect 方式 Reflect.set(obj, 'name', 'value'); // 返回布尔值表示是否成功 const success = Reflect.deleteProperty(obj, 'prop');
-
-
Proxy 和 Reflect 结合使用
- 最佳实践模式
jsconst handler = { get(target, prop, receiver) { // 自定义逻辑... return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { // 自定义逻辑... return Reflect.set(target, prop, value, receiver); } };
-
结合优势
- 保持默认行为 :通过
Reflect
调用原始操作,避免手动实现基础逻辑 - 保持 this 绑定 :
receiver
参数确保this
指向代理对象(避免原型链问题) - 统一接口 :
Reflect
方法与Proxy
陷阱一一对应,代码更规范 - 示例
js// 不使用 Reflect 的 this 问题 const parent = { a: 1 }; const child = new Proxy({}, { get(target, prop) { return target[prop]; // 无法访问父级属性 } }); Object.setPrototypeOf(child, parent); console.log(child.a); // undefined ❌ // 使用 Reflect 修复 const childCorrect = new Proxy({}, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); // ✅ } });
- 保持默认行为 :通过
-
应用场景
js// 数据验证代理 const validator = { set(target, prop, value) { if (prop === 'age' && !Number.isInteger(value)) { throw new TypeError('Age must be an integer'); } return Reflect.set(target, prop, value); } }; const person = new Proxy({}, validator); person.age = 30; // 正常 person.age = 'young'; // 抛出错误
-
原理对比
特性 Proxy Reflect 核心目的 拦截和自定义对象操作 提供操作对象的标准化方法 返回值 总是返回代理对象 返回操作结果(布尔值/实际值) 设计定位 对象的"中间件"层 替代隐式操作的显式 API 典型使用场景 数据绑定、验证、日志记录 与 Proxy 配合实现默认行为
简易实现
javascript
function reactive(obj) {
const handler = {
get(target,key,receiver) {
const result = Relect.get(target,key,receiver);
// 依赖收集
track(target,key);
return result
},
set(target,key,value,receiver) {
const oldValue = target[key];
if(oldValue !== value) {
// change
const result = Relect.set(target,key,value,receiver)
if(result) {
// 更新成功,执行副作用
trigger(target,value)
return value
}
// 更新失败
return oldValue
}
}
}
// 返回一个proxy
return new Proxy(obj,handler)
}
ref.ts
核心方法
ref(value)
: 创建一个 ref 对象。isRef(value)
:判断一个对象是否是ref对象unref(value)
: 如果参数是 ref,则返回内部值,否则返回参数本身。shallowRef(value)
: 创建一个浅层 ref(不会对嵌套对象进行深度响应式转换)。
说明
实现ref
依赖于JavaScript 计算属性
,又称 JavaScript 访问器(Getter 和 Setter)
,注意不是vue的 computed
。
js是怎么拿到普通对象(不包括
proxy
对象)上属性的?
- 如果对象自身有该属性(通过
Object.hasOwnProperty()
判断),则直接返回该属性的值。 - 如果对象自身没有该属性,则会沿着原型链(
__proto__
或Object.getPrototypeOf()
)向上查找。如果找到该属性,则返回其值;如果直到原型链顶端(null
)仍未找到,则返回undefined
。 - 如果对象的属性是一个访问器属性(即定义了
get
方法),则会调用该get
方法。- 如果对象自身没有getter
,但原型链上的某个对象定义了getter
,则会调用原型链上的getter
。
js
const obj = {
get a() {
console.log('访问属性 a');
return 1;
}
};
console.log(obj.a); // 触发 getter
为什么不直接使用reactive的value属性来定义ref,例如下面这样?
php
function ref(raw){
return reactive({value: raw})
}
ref
只应暴露一个value
属性,如果使用reactive
,就可以给他添加别的响应式属性,不符合我们的预期ref
内部有供isRef
,isShallow
去check的额外属性。
简易实现
javascript
function ref(raw) {
const r = {
// isRef 判断依据
__v_isRef: true,
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
if (raw !== newVal) {
raw = newVal
trigger(r, 'value')
}
},
}
return r
}
function isRef(target){
return !!target[__v_isRef]
}
computed.ts
核心方法
computed(getterOrOptions,debugOptions?,isSSR=false)
: 创建一个计算属性 ref。
说明
computed
内部实现也使用的是类似ref
js访问器- 第二个参数对于组件调试很有帮助
ts
export interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
简易实现
这里就实现一个只传入getter的computed
javascript
function computed(getter) {
const result = ref();
effect(() => (result.value = getter()))
return result
}
watch.ts
核心方法
watch(source, cb, options?)
: 监听一个或多个响应式数据源,并在数据变化时执行回调。watchEffect(effect,options?)
:立即运行一个函数,同时响应式的追踪其依赖,并在依赖更改时重新运行它
说明
简易实现
- watch
js
function watch(getter, callback) {
let oldValue, newValue
let firstRun = true
// 利用 effect 收集 getter 内部对数据的依赖,
// 数据变化时 effect 会重新运行,并调用 callback
effect(() => {
newValue = getter()
if (firstRun) {
// 第一次执行不触发回调
oldValue = newValue
firstRun = false
} else {
callback(oldValue, newValue)
oldValue = newValue
}
})
}
- watchEffect
javascript
function watchEffect(fn) {
// 利用 effect 收集 fn 内部对数据的依赖,
// 数据变化时 effect 会重新运行,并调用 fn
effect(fn)
}
完整示例代码
javascript
const ReactiveFlags = {
SKIP: '__v_skip',
IS_REACTIVE: '__v_isReactive',
IS_READONLY: '__v_isReadonly',
IS_SHALLOW: '__v_isShallow',
RAW: '__v_raw',
IS_REF: '__v_isRef',
}
let activeEffect = null
// the code we want to run
function effect(fn) {
activeEffect = fn
activeEffect()
activeEffect = null
}
const targetMap = new WeakMap()
const track = (target, key) => {
if (activeEffect) {
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 Set()))
}
dep.add(activeEffect)
}
}
const trigger = (target, key) => {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = value
if (oldValue != value) {
Reflect.set(target, key, value, receiver)
trigger(target, key)
}
return result
},
}
return new Proxy(target, handler)
}
function ref(raw) {
const r = {
__v_isRef: true,
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
if (raw !== newVal) {
raw = newVal
trigger(r, 'value')
}
},
}
return r
}
export function isRef(r) {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
function computed(fn) {
const result = ref()
effect(() => (result.value = fn()))
return result
}
function watch(getter, callback) {
let oldValue, newValue
let firstRun = true
// 利用 effect 收集 getter 内部对数据的依赖,
// 数据变化时 effect 会重新运行,并调用 callback
effect(() => {
newValue = getter()
if (firstRun) {
// 第一次执行不触发回调
oldValue = newValue
firstRun = false
} else {
callback(oldValue, newValue)
oldValue = newValue
}
})
}
function watchEffect(fn) {
// 利用 effect 收集 fn 内部对数据的依赖,
// 数据变化时 effect 会重新运行,并调用 fn
effect(fn)
}
export { effect, reactive, ref, computed, watch, watchEffect }