vue3中的ref和reactive
第一节: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`,类型推导依赖泛型:```typescriptconst 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 的核心原理,但在生产环境中建议使用官方实现以获得完整功能和性能保障。