从零开始:新手如何手写实现Reactive响应式系统
2025.09.19 12:48浏览量:0简介:本文为前端新手提供手写Reactive响应式系统的完整指南,通过原理拆解、代码实现和优化技巧,帮助开发者理解响应式核心机制并掌握实现方法。
一、理解Reactive的核心机制
响应式系统的本质是建立数据与视图的自动同步机制,其核心在于依赖追踪和触发更新。当数据变化时,系统能自动通知所有依赖该数据的组件进行更新。这一机制通过三个关键角色实现:
- 依赖收集器(Dep):存储所有依赖当前数据的观察者
- 观察者(Watcher):执行更新逻辑的回调函数
- 响应式对象(ReactiveObject):通过getter/setter拦截数据访问和修改
以Vue 2.x的响应式实现为例,当访问obj.name
时,会触发getter将当前Watcher存入Dep;当修改obj.name
时,会触发setter通知Dep中的所有Watcher执行更新。这种设计模式被称为发布-订阅模式。
二、基础实现:最小化Reactive系统
1. 创建依赖收集器
class Dep {
constructor() {
this.subscribers = new Set(); // 使用Set避免重复
}
depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}
notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}
关键点:
- 使用
Set
存储订阅者,避免重复订阅 activeWatcher
作为全局变量,表示当前正在执行的Watcherdepend()
方法在getter中被调用,收集依赖
2. 实现响应式对象
function reactive(obj) {
const observed = {};
for (const key in obj) {
const dep = new Dep();
let value = obj[key];
observed[key] = {
get() {
dep.depend(); // 收集依赖
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
dep.notify(); // 触发更新
}
}
};
}
return new Proxy(observed, {
get: function(target, prop) {
return target[prop].get();
},
set: function(target, prop, value) {
target[prop].set(value);
return true;
}
});
}
实现要点:
- 使用
Proxy
拦截所有属性访问 - 每个属性维护独立的
Dep
实例 - 在setter中比较新旧值,避免无效更新
- 通过闭包保存原始值和
Dep
实例
3. 创建Watcher类
let activeWatcher = null;
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn;
this.run();
}
run() {
activeWatcher = this;
this.updateFn();
activeWatcher = null;
}
update() {
this.run();
}
}
Watcher的工作流程:
- 创建时立即执行
updateFn
- 执行期间设置
activeWatcher
为当前实例 - 依赖收集通过
dep.depend()
自动完成 - 数据变化时调用
update()
重新执行updateFn
三、进阶优化:处理嵌套对象和数组
1. 递归响应式转换
function deepReactive(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepReactive(item));
}
return reactive(
Object.keys(obj).reduce((result, key) => {
result[key] = deepReactive(obj[key]);
return result;
}, {})
);
}
2. 数组方法劫持
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
arrayMethods.forEach(method => {
const original = Array.prototype[method];
Array.prototype[method] = function(...args) {
const result = original.apply(this, args);
const observed = this.__ob__; // 假设数组已被标记为响应式
observed.dep.notify(); // 手动触发更新
return result;
};
});
实现要点:
- 保留原始数组方法
- 在修改数组后手动触发更新
- 需要标记响应式数组(如通过
__ob__
属性)
四、完整示例:计数器应用
// 1. 创建响应式数据
const state = deepReactive({
count: 0,
nested: {
value: 'hello'
}
});
// 2. 创建Watcher更新视图
new Watcher(() => {
document.getElementById('count').textContent = state.count;
document.getElementById('nested').textContent = state.nested.value;
});
// 3. 修改数据触发更新
document.getElementById('increment').addEventListener('click', () => {
state.count++;
});
document.getElementById('update-nested').addEventListener('click', () => {
state.nested.value += '!';
});
五、常见问题与解决方案
异步更新队列:
class Queue {
constructor() {
this.queue = [];
this.pending = false;
}
add(watcher) {
if (!this.queue.includes(watcher)) {
this.queue.push(watcher);
}
if (!this.pending) {
this.pending = true;
Promise.resolve().then(() => this.flush());
}
}
flush() {
while (this.queue.length) {
this.queue.shift().update();
}
this.pending = false;
}
}
使用微任务队列合并更新,避免频繁重渲染
计算属性实现:
class Computed {
constructor(getter) {
this.dirty = true;
this.value = undefined;
this.getter = getter;
this.deps = new Set();
new Watcher(() => {
if (this.dirty) {
this.value = this.getter();
this.dirty = false;
}
});
}
depend() {
if (this.deps.size) {
// 实现依赖收集逻辑
}
}
evaluate() {
this.dirty = true;
return this.value;
}
}
六、性能优化技巧
- 懒执行:只在需要时计算派生数据
- 批量更新:使用
requestIdleCallback
合并低优先级更新 - 依赖精简:通过
shouldTrack
标志控制依赖收集 - 内存管理:提供
$dispose()
方法清理不再需要的Watcher
七、测试验证方法
单元测试:
test('should trigger update when property changes', () => {
const obj = reactive({ count: 0 });
let updated = false;
new Watcher(() => {
updated = true;
});
obj.count = 1;
expect(updated).toBe(true);
});
性能基准测试:
function benchmark() {
const obj = reactive(Array(1000).fill(0).reduce((acc, _) => {
acc[`key${Math.random()}`] = Math.random();
return acc;
}, {}));
const start = performance.now();
for (let i = 0; i < 1000; i++) {
obj[`key${Math.random()}`] = Math.random();
}
console.log(`Update time: ${performance.now() - start}ms`);
}
通过以上实现,新手开发者可以逐步构建自己的响应式系统。建议从最小实现开始,逐步添加数组支持、异步更新队列等高级功能。理解这些原理后,阅读Vue/React等框架的源码将变得更加容易。
发表评论
登录后可评论,请前往 登录 或 注册