第一节:ref和reactive的区别
在 Vue3 中,ref
和 reactive
是两种创建响应式数据的核心 API,它们的主要区别体现在数据结构、响应式原理和使用方式上。以下是详细对比:
1. 数据结构与响应式原理
ref
- 本质:创建一个包装对象(
RefImpl
),通过.value
访问内部值。 - 适用场景:基本类型(如
number
、string
)、单个值的响应式处理。 - 响应式原理:通过
Object.defineProperty()
将.value
转换为 getter/setter。
const count = ref(0);
console.log(count.value); // 0
count.value = 1; // 修改值时触发响应式更新
reactive
- 本质:创建一个深层响应式对象,对对象的所有嵌套属性都进行代理。
- 适用场景:复杂对象、数组等引用类型的响应式处理。
- 响应式原理:基于 ES6 Proxy 实现,拦截对象的属性访问和修改。
const state = reactive({
count: 0,
user: { name: 'Alice' }
});
console.log(state.count); // 无需 .value
state.count = 1; // 直接修改属性触发响应式更新
2. 语法与使用方式
ref
- 声明:使用
ref(初始值)
,类型通过泛型指定。 - 模板引用:在模板中自动解包,无需
.value
。 - 组件中使用:
<script setup lang="ts"> const count = ref<number>(0); const increment = () => { count.value++; // 必须使用 .value 修改值 }; </script> <template> <button @click="increment">{{ count }}</button> <!-- 模板中无需 .value --> </template>
reactive
- 声明:使用
reactive(对象字面量)
,类型可通过接口或类型注解明确。 - 模板引用:直接访问属性,无需
.value
。 - 组件中使用:
<script setup lang="ts">
interface State {
count: number;
message: string;
}
const state = reactive<State>({
count: 0,
message: 'Hello'
});
const increment = () => {
state.count++; // 直接修改属性
};
</script>
<template>
<button @click="increment">{{ state.count }}</button>
</template>
### 3. **类型系统差异**
#### `ref`
- 类型为 `Ref<T>`,访问值时需通过 `.value`,类型推导依赖泛型:
```typescript
const count = ref(0); // Ref<number>
const message = ref<string>('hello'); // 显式指定类型
reactive
- 类型为原始对象的代理类型,保留对象结构:
const state = reactive({ count: 0 }); // 类型为 { count: number }
- 注意:
reactive
无法直接处理基本类型,必须包装在对象中:// 错误:无法直接让基本类型响应式 const num = reactive(1); // 报错 // 正确:包装在对象中 const state = reactive({ num: 1 });
4. 解构与响应式丢失
ref
- 通过
.value
保持响应式:const count = ref(0); const { value } = count; // value 不是响应式的
reactive
- 直接解构会导致响应式丢失,需使用
toRefs
:const state = reactive({ count: 0 }); const { count } = state; // count 不是响应式的 // 使用 toRefs 保持响应式 const { count } = toRefs(state); // count 是 Ref<number>
5. 适用场景对比
场景 | ref |
reactive |
---|---|---|
基本类型响应式 | ✅ const count = ref(0) |
❌ 需包装在对象中 |
对象/数组响应式 | ✅ const obj = ref({}) |
✅ const obj = reactive({}) |
避免响应式丢失 | ✅ 直接传递 .value |
❌ 需使用 toRefs 解构 |
函数参数传递 | ✅ 可直接传递 Ref 实例 |
❌ 需确保整个对象被代理 |
与第三方库集成 | ✅ 可通过 .value 暴露原始值 |
❌ Proxy 对象可能导致兼容性问题 |
6. 选择建议
使用
ref
场景:- 管理基本类型(如数字、字符串)。
- 在组合式函数中返回单个响应式值。
- 需要与 TypeScript 泛型紧密配合时(如表单验证)。
使用
reactive
场景:- 管理复杂对象或数组。
- 避免过多
.value
语法,使代码更简洁。 - 需要深层响应式监听时(如嵌套对象的变化)。
总结
特性 | ref |
reactive |
---|---|---|
响应式原理 | Getter/Setter (Object.defineProperty) | Proxy |
数据类型 | 任意类型(包装为 Ref 对象) |
仅对象/数组(需为引用类型) |
访问方式 | 模板中自动解包,JS 中需 .value |
直接访问属性 |
类型系统 | Ref<T> |
原始对象类型的代理 |
解构响应式 | 保持响应式(.value ) |
需使用 toRefs 保持响应式 |
在实际开发中,两者通常结合使用:用 reactive
管理复杂状态对象,用 ref
处理简单值或作为函数返回值。
第二节:使用原生的javascript实现ref功能
下面我将使用原生JavaScript实现一个类似Vue3中ref
的功能。ref
的核心是创建一个响应式对象,通过.value
属性访问和修改值,并在值变化时触发依赖更新。
实现思路
- 使用ES6的
Proxy
来拦截对象属性的访问和修改 - 创建一个包含
value
属性的对象,并对其进行代理 - 实现依赖收集和触发更新的机制
- 添加类型定义以支持TypeScript
原生JavaScript实现
// ref 实现
function ref(initialValue) {
// 创建一个包含value属性的对象
const refObject = {
_value: initialValue,
get value() {
// 收集依赖
track(refObject, 'value');
return this._value;
},
set value(newValue) {
this._value = newValue;
// 触发更新
trigger(refObject, 'value');
}
};
// 返回一个Proxy,使其更加灵活
return new Proxy(refObject, {
get(target, key) {
return target[key];
},
set(target, key, value) {
target[key] = value;
if (key === 'value') {
trigger(target, key);
}
return true;
}
});
}
// 存储依赖关系的WeakMap
// targetMap: WeakMap<target, depsMap>
// depsMap: Map<key, Set<effect>>
const targetMap = new WeakMap();
// 收集依赖
function track(target, key) {
if (!activeEffect) 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 = new Set()));
}
// 将当前活跃的副作用添加到依赖集合中
dep.add(activeEffect);
// 同时将依赖集合添加到副作用的依赖列表中
activeEffect.deps.push(dep);
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 创建要执行的副作用集合的副本
const effects = new Set();
const computedRunners = new Set();
// 添加属性的依赖副作用
if (depsMap.has(key)) {
depsMap.get(key).forEach(effect => {
if (effect.computed) {
computedRunners.add(effect);
} else {
effects.add(effect);
}
});
}
// 先执行计算属性副作用
computedRunners.forEach(computed => computed());
// 再执行普通副作用
effects.forEach(effect => effect());
}
// 当前活跃的副作用
let activeEffect = null;
// 创建副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
try {
activeEffect = effectFn;
// 清除之前的依赖
cleanup(effectFn);
// 执行副作用函数并获取返回值
return fn();
} finally {
activeEffect = null;
}
};
// 存储该副作用的依赖集合列表
effectFn.deps = [];
// 标记是否为计算属性
effectFn.computed = options.computed || false;
// 如果不是计算属性,则立即执行
if (!options.lazy) {
effectFn();
}
return effectFn;
}
// 清除副作用的依赖
function cleanup(effectFn) {
effectFn.deps.forEach(dep => {
dep.delete(effectFn);
});
effectFn.deps.length = 0;
}
// 实现计算属性
function computed(getter) {
let value;
let dirty = true;
// 创建一个计算属性的副作用
const runner = effect(getter, {
lazy: true,
computed: true
});
return {
get value() {
if (dirty) {
value = runner();
dirty = false;
}
// 收集依赖,以便在依赖变化时重新计算
track(this, 'value');
return value;
},
set value(newValue) {
console.warn('Computed value is readonly');
}
};
}
// 示例用法
const count = ref(0);
// 创建一个计算属性
const doubleCount = computed(() => count.value * 2);
// 创建副作用监听count变化
effect(() => {
console.log('Count changed:', count.value);
});
// 修改count值
count.value = 1; // 输出: Count changed: 1
count.value = 2; // 输出: Count changed: 2
// 访问计算属性
console.log(doubleCount.value); // 输出: 4
代码解释
ref函数:创建一个包含
value
属性的对象,并使用Proxy
代理它。当访问或修改value
时,会触发track
或trigger
函数。依赖收集系统:使用
WeakMap
存储对象及其依赖关系,当读取响应式对象的属性时收集依赖,当修改属性时触发更新。副作用函数:
effect
函数创建一个响应式副作用,当依赖的响应式数据变化时会重新执行。计算属性:
computed
函数基于effect
实现,具有缓存机制,只有在依赖变化时才会重新计算。
这个实现模拟了Vue3中ref
的核心功能,包括响应式追踪、依赖收集和计算属性。在实际应用中,Vue3的实现更加复杂和健壮,还包括生命周期集成、错误处理和性能优化等方面的考虑。
原生 JavaScript 实现的 ref 功能与 Vue3 中的 ref 有哪些区别?
原生 JavaScript 实现的 ref
功能与 Vue3 中的 ref
在核心原理上相似,但在实现细节、功能完整性和性能优化方面存在显著差异。以下是主要区别:
1. 底层实现差异
原生实现
- 依赖收集:使用
WeakMap
+Map
+Set
手动管理依赖关系。 - 响应式触发:通过
Object.defineProperty
或Proxy
拦截属性访问。 - 示例:
const refObject = { _value: initialValue, get value() { track(this, 'value'); return this._value; }, set value(newValue) { this._value = newValue; trigger(this, 'value'); } };
Vue3 实现
- 依赖收集:基于
ReactiveEffect
类和全局副作用栈,支持嵌套副作用。 - 响应式触发:通过
Proxy
实现深层响应式,支持数组和 Map/Set 等特殊对象。 - 优化:
- 使用
RefImpl
和ComputedRefImpl
类实现更高效的内存管理。 - 针对不同类型(基本类型、对象、数组)做了特殊处理。
- 使用
2. 功能完整性
特性 | 原生实现 | Vue3 实现 |
---|---|---|
基础响应式 | ✅ 支持 .value 读写 |
✅ 支持 .value 读写 |
计算属性 | ✅ 基础实现 | ✅ 完整支持,含缓存和懒计算 |
浅层响应式(shallowRef) | ❌ 未实现 | ✅ 支持 |
只读引用(readonly) | ❌ 未实现 | ✅ 支持 |
自定义响应式(customRef) | ❌ 未实现 | ✅ 支持 |
与组件生命周期集成 | ❌ 需手动管理 | ✅ 自动绑定和清理副作用 |
类型系统 | 基础类型支持 | 完整 TypeScript 集成 |
3. 性能优化
原生实现
- 依赖收集:每次访问属性时都需遍历依赖列表。
- 触发更新:直接触发所有依赖,无批量更新机制。
- 内存管理:依赖关系可能无法被垃圾回收(如 WeakMap 使用不当)。
Vue3 实现
- 批量更新:使用微任务队列(
queueJob
)合并多次更新,避免重复渲染。 - 细粒度依赖:通过
TrackOpTypes
和TriggerOpTypes
区分不同操作类型。 - 优化渲染:
- 基于
shouldTrack
条件判断是否收集依赖。 - 使用
ReactiveFlags
标记特殊对象,避免重复代理。
- 基于
4. 与 Vue 生态的集成
原生实现
- 模板集成:需手动绑定 DOM 更新逻辑。
- 生命周期:无自动清理机制,需手动管理副作用。
- Vue 特性:不支持
watch
、provide/inject
等。
Vue3 实现
- 模板自动解包:在
<template>
中无需.value
。 - 生命周期钩子:自动绑定
setup()
和组件实例。 - Vue 特性:
- 与
watch
、watchEffect
无缝协作。 - 支持
toRef
、toRefs
等工具函数。
- 与
5. 边界情况处理
场景 | 原生实现 | Vue3 实现 |
---|---|---|
循环引用 | ❌ 可能导致栈溢出 | ✅ 通过 targetMap 避免 |
非响应式对象 | ❌ 直接返回原始值 | ✅ 通过 toRaw() 显式转换 |
响应式丢失 | ❌ 需手动处理 | ✅ 提供 toRefs() 保持响应式 |
数组变异方法 | ❌ 需手动实现 | ✅ 自动处理(如 push 、splice ) |
6. 错误处理与调试
原生实现
- 错误捕获:需手动添加
try...catch
。 - 调试工具:无内置支持,需手动打印日志。
Vue3 实现
- 错误处理:
- 通过
app.config.errorHandler
全局捕获副作用错误。 - 计算属性错误会缓存并在访问时抛出。
- 通过
- 调试工具:
- Vue DevTools 支持响应式数据可视化。
onTrack
和onTrigger
钩子用于调试依赖关系。
总结
维度 | 原生实现 | Vue3 实现 |
---|---|---|
实现复杂度 | 较低(基础功能) | 极高(完整生态) |
性能 | 基础优化 | 高度优化(批量更新、缓存等) |
功能完整性 | 核心功能 | 完整特性集 |
与框架集成 | 无 | 深度集成(模板、生命周期等) |
调试工具 | 手动调试 | 官方 DevTools 支持 |
原生实现适合学习响应式原理,但在生产环境中,建议使用 Vue3 官方实现以获得完整功能和性能保障。
除了 Proxy,还有哪些方法可以实现类似 ref 的功能?
在 JavaScript 中,除了使用 Proxy 实现响应式外,还可以通过以下几种方式实现类似 Vue3 ref
的功能。每种方法各有优缺点,下面我将逐一介绍并给出示例代码。
1. Object.defineProperty()
这是 Vue2 中使用的核心响应式机制,通过 getter/setter 拦截属性访问。
实现原理:
- 为对象的属性定义 getter 和 setter
- 在 getter 中收集依赖,在 setter 中触发更新
示例代码:
javascript
function ref(initialValue) {
const refObject = {
_value: initialValue,
};
Object.defineProperty(refObject, 'value', {
get() {
track(refObject, 'value');
return this._value;
},
set(newValue) {
this._value = newValue;
trigger(refObject, 'value');
},
});
return refObject;
}
// 依赖收集和触发更新的实现与之前相同
优缺点:
- 优点:兼容性好(支持 IE9+)
- 缺点:
- 无法检测对象属性的添加或删除
- 对数组的支持有限(需手动处理变异方法)
- 只能劫持对象的属性,无法劫持整个对象
2. 发布-订阅模式(Event Emitter)
通过自定义事件系统实现响应式,属性变化时发布事件,依赖方订阅事件。
实现原理:
- 创建一个事件中心,管理事件的发布和订阅
- ref 对象的 setter 触发事件,getter 可用于依赖收集
示例代码:
javascript
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
}
off(event, callback) {
if (!this.events.has(event)) return;
const callbacks = this.events.get(event);
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
}
emit(event, ...args) {
if (!this.events.has(event)) return;
this.events.get(event).forEach(callback => callback(...args));
}
}
function ref(initialValue) {
const emitter = new EventEmitter();
let _value = initialValue;
return {
get value() {
// 收集依赖逻辑
return _value;
},
set value(newValue) {
_value = newValue;
emitter.emit('change', newValue);
},
onChange(callback) {
emitter.on('change', callback);
},
};
}
// 使用示例
const count = ref(0);
count.onChange((newValue) => {
console.log('Count changed to:', newValue);
});
count.value = 1; // 输出: Count changed to: 1
优缺点:
- 优点:
- 实现简单直观
- 可扩展性强,可自定义各种事件
- 缺点:
- 手动管理事件订阅和取消订阅
- 缺乏自动依赖收集,需显式调用
onChange
3. ES6 类 + 访问器属性
使用 ES6 类的 getter/setter 实现响应式包装。
实现原理:
- 创建一个类,将值存储在私有属性中
- 通过 public getter/setter 控制值的读写
示例代码:
javascript
class Ref {
constructor(initialValue) {
this._value = initialValue;
this.deps = new Set();
}
get value() {
// 收集依赖
if (activeEffect) {
this.deps.add(activeEffect);
activeEffect.deps.push(this.deps);
}
return this._value;
}
set value(newValue) {
this._value = newValue;
// 触发更新
this.deps.forEach(effect => effect());
}
}
function ref(initialValue) {
return new Ref(initialValue);
}
// 副作用函数
let activeEffect = null;
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn;
fn();
activeEffect = null;
};
effectFn();
return effectFn;
}
// 使用示例
const count = ref(0);
effect(() => {
console.log('Count is:', count.value);
});
count.value = 1; // 输出: Count is: 1
优缺点:
- 优点:
- 面向对象设计,结构清晰
- 可通过继承扩展功能
- 缺点:
- 无法拦截对象内部属性的变化(只能拦截顶层 value)
- 相比 Proxy,需要为每个 ref 创建一个类实例
4. RxJS(响应式编程库)
利用 RxJS 的 Observable 实现数据流管理。
实现原理:
- 使用 BehaviorSubject 存储值并发布变更
- 通过订阅流来响应值的变化
示例代码:
javascript
import { BehaviorSubject } from 'rxjs';
function ref(initialValue) {
const subject = new BehaviorSubject(initialValue);
return {
get value() {
return subject.getValue();
},
set value(newValue) {
subject.next(newValue);
},
subscribe(callback) {
return subject.subscribe(callback);
}
};
}
// 使用示例
const count = ref(0);
const subscription = count.subscribe((newValue) => {
console.log('Count changed to:', newValue);
});
count.value = 1; // 输出: Count changed to: 1
subscription.unsubscribe(); // 取消订阅
优缺点:
- 优点:
- 强大的异步数据流处理能力
- 丰富的操作符(map、filter、merge 等)
- 缺点:
- 学习曲线陡峭
- 引入额外的依赖库(RxJS)
5. Symbol + 全局注册表
使用 Symbol 作为唯一标识符,结合全局注册表管理响应式对象。
实现原理:
- 使用 Symbol 作为每个 ref 的唯一标识
- 通过全局注册表存储 ref 的值和依赖关系
示例代码:
javascript
const REF_KEY = Symbol('ref');
const refRegistry = new Map();
function ref(initialValue) {
const refId = Symbol('refId');
refRegistry.set(refId, {
value: initialValue,
deps: new Set(),
});
return {
get value() {
track(refId);
return refRegistry.get(refId).value;
},
set value(newValue) {
refRegistry.get(refId).value = newValue;
trigger(refId);
},
};
}
// 依赖收集和触发更新的实现
function track(refId) {
if (activeEffect) {
refRegistry.get(refId).deps.add(activeEffect);
activeEffect.deps.push(refRegistry.get(refId).deps);
}
}
function trigger(refId) {
refRegistry.get(refId).deps.forEach(effect => effect());
}
// 副作用函数
let activeEffect = null;
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn;
fn();
activeEffect = null;
};
effectFn();
return effectFn;
}
优缺点:
- 优点:
- 避免直接在对象上添加属性,保持对象纯净
- 更好的内存管理(通过 WeakMap 可实现)
- 缺点:
- 全局注册表可能导致命名冲突
- 调试困难,难以追踪依赖关系
总结对比
方法 | 优点 | 缺点 |
---|---|---|
Proxy | 深层响应式、拦截能力强 | 兼容性差(IE 不支持) |
Object.defineProperty | 兼容性好 | 只能劫持属性,无法劫持对象 |
发布-订阅模式 | 实现简单、灵活 | 需手动管理事件订阅 |
ES6 类 | 面向对象设计、结构清晰 | 只能拦截顶层属性 |
RxJS | 强大的异步处理能力 | 学习曲线陡峭、引入额外依赖 |
Symbol+注册表 | 保持对象纯净、避免属性冲突 | 调试困难、全局状态管理复杂 |
在实际应用中,Vue3 选择 Proxy 是因为它提供了更强大的元编程能力,可以拦截对象的各种操作,实现深层响应式。而其他方法各有适用场景,例如需要兼容旧浏览器时可考虑 Object.defineProperty
,处理复杂异步数据流时 RxJS 是不错的选择。
如何使用原生 JavaScript 实现 reactive 功能?
下面我将使用原生 JavaScript 实现一个类似 Vue3 中 reactive
的功能。reactive
的核心是创建一个深层响应式对象,对对象的所有嵌套属性都进行代理,当属性值变化时触发依赖更新。
实现思路
使用 ES6 Proxy:拦截对象的属性访问和修改操作
递归代理嵌套对象:确保深层属性也具有响应式能力
依赖收集与触发:使用 WeakMap 存储对象与依赖的映射关系
特殊处理
:
- 避免重复代理同一对象
- 处理数组的变异方法(如 push、splice)
- 支持不可变对象(readonly)
原生 JavaScript 实现
javascript
// 存储依赖关系的 WeakMap
// targetMap: WeakMap<target, depsMap>
// depsMap: Map<key, Set<effect>>
const targetMap = new WeakMap();
// 当前活跃的副作用函数
let activeEffect = null;
// 创建响应式对象
function reactive(target) {
// 如果不是对象类型,直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 如果已经是代理对象,直接返回
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// 创建代理
return createProxy(target);
}
// 创建代理
function createProxy(target) {
// 检查是否已存在代理
const existingProxy = getTargetProxy(target);
if (existingProxy) {
return existingProxy;
}
// 创建新代理
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 处理特殊标志
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
// 获取属性值
const value = Reflect.get(target, key, receiver);
// 收集依赖
track(target, key);
// 如果值是对象,递归创建响应式代理
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, key, newValue, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, newValue, receiver);
// 只有当值真正发生变化时才触发更新
if (oldValue !== newValue && (oldValue === oldValue || newValue === newValue)) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);
// 如果属性存在且被成功删除,触发更新
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
// 存储代理与原始对象的映射
storeTargetProxy(target, proxy);
return proxy;
}
// 依赖收集
function track(target, key) {
if (!activeEffect) 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 = new Set()));
}
// 将当前活跃的副作用添加到依赖集合中
dep.add(activeEffect);
// 同时将依赖集合添加到副作用的依赖列表中
activeEffect.deps.push(dep);
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 创建要执行的副作用集合的副本
const effects = new Set();
// 添加属性的依赖副作用
if (depsMap.has(key)) {
depsMap.get(key).forEach(effect => {
effects.add(effect);
});
}
// 执行所有副作用
effects.forEach(effect => effect());
}
// 创建副作用函数
function effect(fn) {
const effectFn = () => {
try {
// 清除之前的依赖
cleanup(effectFn);
// 设置当前活跃的副作用
activeEffect = effectFn;
// 执行副作用函数
return fn();
} finally {
// 重置当前活跃的副作用
activeEffect = null;
}
};
// 存储该副作用的依赖集合列表
effectFn.deps = [];
// 立即执行副作用以收集依赖
effectFn();
return effectFn;
}
// 清除副作用的依赖
function cleanup(effectFn) {
effectFn.deps.forEach(dep => {
dep.delete(effectFn);
});
effectFn.deps.length = 0;
}
// 特殊标志
const ReactiveFlags = {
IS_REACTIVE: Symbol('is_reactive')
};
// 存储原始对象与代理的映射(简化版)
const proxyMap = new WeakMap();
function getTargetProxy(target) {
return proxyMap.get(target);
}
function storeTargetProxy(target, proxy) {
proxyMap.set(target, proxy);
}
// 示例用法
const state = reactive({
count: 0,
user: {
name: 'Alice',
age: 30
}
});
// 创建副作用监听 count 变化
effect(() => {
console.log('Count changed:', state.count);
});
// 修改 count 值
state.count = 1; // 输出: Count changed: 1
state.count = 2; // 输出: Count changed: 2
// 嵌套对象也具有响应式能力
effect(() => {
console.log('User name changed:', state.user.name);
});
state.user.name = 'Bob'; // 输出: User name changed: Bob
核心功能解析
- Proxy 拦截器:
get
:收集依赖并递归代理嵌套对象set
:比较新旧值,仅在值变化时触发更新deleteProperty
:处理属性删除时的依赖更新
- 依赖收集系统:
- 使用
WeakMap<target, Map<key, Set<effect>>>
存储依赖关系 - 每个属性对应一个依赖集合(Set)
- 使用
- 副作用管理:
effect
函数创建响应式副作用activeEffect
全局变量跟踪当前执行的副作用
- 循环引用处理:
- 使用
proxyMap
避免重复代理同一对象 - 通过
ReactiveFlags.IS_REACTIVE
标识已代理对象
- 使用
进阶功能实现(可选)
1. 数组变异方法处理
javascript
// 拦截数组的变异方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
// 先执行原始方法
const result = original.apply(this, args);
// 通知变更
const ob = this.__ob__;
// push、unshift、splice 可能会添加新元素,需要对新元素进行响应式处理
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted);
// 触发更新
ob.dep.notify();
return result;
};
});
2. 计算属性实现
javascript
function computed(getter) {
let value;
let dirty = true;
const runner = effect(getter, {
lazy: true,
scheduler: () => {
if (!dirty) {
dirty = true;
// 触发依赖于这个计算属性的副作用
trigger(computedRef, 'value');
}
}
});
const computedRef = {
[ReactiveFlags.IS_REACTIVE]: true,
get value() {
if (dirty) {
value = runner();
dirty = false;
}
track(computedRef, 'value');
return value;
},
set value() {
console.warn('Computed value is readonly');
}
};
return computedRef;
}
`### 与 Vue3 reactive 的差异
1. **功能完整性**:
- 缺少 Vue3 的 `shallowReactive`、`readonly`、`toRaw` 等工具函数
- 未处理特殊对象(如 Map、Set、Date 等)
2. **性能优化**:
- 没有批量更新机制(Vue3 使用微任务队列合并更新)
- 未实现细粒度的依赖优化
3. **生态集成**:
- 没有与 Vue 组件生命周期的集成
- 缺少 DevTools 支持
这个实现展示了 Vue3 reactive 的核心原理,但在生产环境中建议使用官方实现以获得完整功能和性能保障。