实现Vue reactive响应式
手写实现Vue 3的reactive响应式系统,理解Proxy和依赖收集的原理
问题
Vue 3使用Proxy实现响应式系统,当数据变化时自动触发依赖更新。本题要求实现一个简化版的reactive函数,支持:
- 对象的响应式转换
- 依赖收集(track)
- 依赖触发(trigger)
- 嵌套对象的响应式处理
解答
// 存储当前正在执行的effect函数
let activeEffect = null;
// 存储依赖关系的WeakMap: target -> Map(key -> Set(effect))
const targetMap = new WeakMap();
/**
* 依赖收集函数
* @param {Object} target - 目标对象
* @param {String} key - 属性名
*/
function track(target, key) {
if (!activeEffect) return;
// 获取target对应的依赖Map
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 获取key对应的依赖Set
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
// 添加当前effect到依赖集合
dep.add(activeEffect);
}
/**
* 触发依赖更新
* @param {Object} target - 目标对象
* @param {String} key - 属性名
*/
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
// 执行所有依赖的effect函数
dep.forEach(effect => effect());
}
/**
* 创建响应式对象
* @param {Object} target - 原始对象
* @returns {Proxy} 响应式代理对象
*/
function reactive(target) {
// 如果不是对象,直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 收集依赖
track(target, key);
// 如果属性值是对象,递归转换为响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 值发生变化时触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
};
return new Proxy(target, handler);
}
/**
* 副作用函数
* @param {Function} fn - 需要执行的函数
*/
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行一次,收集依赖
activeEffect = null;
}
// 导出API
export { reactive, effect };
使用示例
// 示例1: 基本使用
const state = reactive({
count: 0,
message: 'Hello'
});
effect(() => {
console.log('count changed:', state.count);
});
state.count++; // 输出: count changed: 1
state.count++; // 输出: count changed: 2
// 示例2: 嵌套对象
const user = reactive({
name: 'Tom',
profile: {
age: 18,
address: {
city: 'Beijing'
}
}
});
effect(() => {
console.log('User info:', user.name, user.profile.age);
});
user.profile.age = 20; // 输出: User info: Tom 20
// 示例3: 多个effect
const data = reactive({ price: 10, quantity: 2 });
effect(() => {
console.log('Total:', data.price * data.quantity);
});
effect(() => {
console.log('Price:', data.price);
});
data.price = 20;
// 输出: Total: 40
// 输出: Price: 20
// 示例4: 模拟Vue组件更新
const state = reactive({
todos: [
{ id: 1, text: 'Learn Vue', done: false },
{ id: 2, text: 'Build App', done: false }
]
});
// 模拟渲染函数
effect(() => {
console.log('Rendering todos:', state.todos.length);
state.todos.forEach(todo => {
console.log(`- ${todo.text}: ${todo.done ? '✓' : '○'}`);
});
});
// 修改数据触发重新渲染
state.todos[0].done = true;
state.todos.push({ id: 3, text: 'Deploy', done: false });
关键点
-
Proxy代理: 使用Proxy拦截对象的get和set操作,实现响应式的机制
-
依赖收集(track): 在get拦截器中收集依赖,使用WeakMap -> Map -> Set的三层结构存储依赖关系
-
依赖触发(trigger): 在set拦截器中触发依赖更新,执行所有相关的effect函数
-
activeEffect: 全局变量记录当前执行的effect函数,用于建立数据与effect的关联
-
递归响应式: 对嵌套对象进行递归处理,确保深层属性也具有响应式能力
-
Reflect使用: 使用Reflect API确保正确的this指向和操作行为
-
数据结构设计: WeakMap避免内存泄漏,Map存储属性依赖,Set存储effect函数避免重复
-
立即执行: effect函数创建时立即执行一次,完成初始依赖收集
目录