logo

从零开始:新手如何手写实现Reactive响应式系统

作者:半吊子全栈工匠2025.09.19 12:48浏览量:0

简介:本文为前端新手提供手写Reactive响应式系统的完整指南,通过原理拆解、代码实现和优化技巧,帮助开发者理解响应式核心机制并掌握实现方法。

一、理解Reactive的核心机制

响应式系统的本质是建立数据与视图的自动同步机制,其核心在于依赖追踪触发更新。当数据变化时,系统能自动通知所有依赖该数据的组件进行更新。这一机制通过三个关键角色实现:

  1. 依赖收集器(Dep)存储所有依赖当前数据的观察者
  2. 观察者(Watcher):执行更新逻辑的回调函数
  3. 响应式对象(ReactiveObject):通过getter/setter拦截数据访问和修改

以Vue 2.x的响应式实现为例,当访问obj.name时,会触发getter将当前Watcher存入Dep;当修改obj.name时,会触发setter通知Dep中的所有Watcher执行更新。这种设计模式被称为发布-订阅模式

二、基础实现:最小化Reactive系统

1. 创建依赖收集器

  1. class Dep {
  2. constructor() {
  3. this.subscribers = new Set(); // 使用Set避免重复
  4. }
  5. depend() {
  6. if (activeWatcher) {
  7. this.subscribers.add(activeWatcher);
  8. }
  9. }
  10. notify() {
  11. this.subscribers.forEach(watcher => watcher.update());
  12. }
  13. }

关键点:

  • 使用Set存储订阅者,避免重复订阅
  • activeWatcher作为全局变量,表示当前正在执行的Watcher
  • depend()方法在getter中被调用,收集依赖

2. 实现响应式对象

  1. function reactive(obj) {
  2. const observed = {};
  3. for (const key in obj) {
  4. const dep = new Dep();
  5. let value = obj[key];
  6. observed[key] = {
  7. get() {
  8. dep.depend(); // 收集依赖
  9. return value;
  10. },
  11. set(newValue) {
  12. if (value !== newValue) {
  13. value = newValue;
  14. dep.notify(); // 触发更新
  15. }
  16. }
  17. };
  18. }
  19. return new Proxy(observed, {
  20. get: function(target, prop) {
  21. return target[prop].get();
  22. },
  23. set: function(target, prop, value) {
  24. target[prop].set(value);
  25. return true;
  26. }
  27. });
  28. }

实现要点:

  • 使用Proxy拦截所有属性访问
  • 每个属性维护独立的Dep实例
  • 在setter中比较新旧值,避免无效更新
  • 通过闭包保存原始值和Dep实例

3. 创建Watcher类

  1. let activeWatcher = null;
  2. class Watcher {
  3. constructor(updateFn) {
  4. this.updateFn = updateFn;
  5. this.run();
  6. }
  7. run() {
  8. activeWatcher = this;
  9. this.updateFn();
  10. activeWatcher = null;
  11. }
  12. update() {
  13. this.run();
  14. }
  15. }

Watcher的工作流程:

  1. 创建时立即执行updateFn
  2. 执行期间设置activeWatcher为当前实例
  3. 依赖收集通过dep.depend()自动完成
  4. 数据变化时调用update()重新执行updateFn

三、进阶优化:处理嵌套对象和数组

1. 递归响应式转换

  1. function deepReactive(obj) {
  2. if (typeof obj !== 'object' || obj === null) {
  3. return obj;
  4. }
  5. if (Array.isArray(obj)) {
  6. return obj.map(item => deepReactive(item));
  7. }
  8. return reactive(
  9. Object.keys(obj).reduce((result, key) => {
  10. result[key] = deepReactive(obj[key]);
  11. return result;
  12. }, {})
  13. );
  14. }

2. 数组方法劫持

  1. const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
  2. arrayMethods.forEach(method => {
  3. const original = Array.prototype[method];
  4. Array.prototype[method] = function(...args) {
  5. const result = original.apply(this, args);
  6. const observed = this.__ob__; // 假设数组已被标记为响应式
  7. observed.dep.notify(); // 手动触发更新
  8. return result;
  9. };
  10. });

实现要点:

  • 保留原始数组方法
  • 在修改数组后手动触发更新
  • 需要标记响应式数组(如通过__ob__属性)

四、完整示例:计数器应用

  1. // 1. 创建响应式数据
  2. const state = deepReactive({
  3. count: 0,
  4. nested: {
  5. value: 'hello'
  6. }
  7. });
  8. // 2. 创建Watcher更新视图
  9. new Watcher(() => {
  10. document.getElementById('count').textContent = state.count;
  11. document.getElementById('nested').textContent = state.nested.value;
  12. });
  13. // 3. 修改数据触发更新
  14. document.getElementById('increment').addEventListener('click', () => {
  15. state.count++;
  16. });
  17. document.getElementById('update-nested').addEventListener('click', () => {
  18. state.nested.value += '!';
  19. });

五、常见问题与解决方案

  1. 异步更新队列

    1. class Queue {
    2. constructor() {
    3. this.queue = [];
    4. this.pending = false;
    5. }
    6. add(watcher) {
    7. if (!this.queue.includes(watcher)) {
    8. this.queue.push(watcher);
    9. }
    10. if (!this.pending) {
    11. this.pending = true;
    12. Promise.resolve().then(() => this.flush());
    13. }
    14. }
    15. flush() {
    16. while (this.queue.length) {
    17. this.queue.shift().update();
    18. }
    19. this.pending = false;
    20. }
    21. }

    使用微任务队列合并更新,避免频繁重渲染

  2. 计算属性实现

    1. class Computed {
    2. constructor(getter) {
    3. this.dirty = true;
    4. this.value = undefined;
    5. this.getter = getter;
    6. this.deps = new Set();
    7. new Watcher(() => {
    8. if (this.dirty) {
    9. this.value = this.getter();
    10. this.dirty = false;
    11. }
    12. });
    13. }
    14. depend() {
    15. if (this.deps.size) {
    16. // 实现依赖收集逻辑
    17. }
    18. }
    19. evaluate() {
    20. this.dirty = true;
    21. return this.value;
    22. }
    23. }

六、性能优化技巧

  1. 懒执行:只在需要时计算派生数据
  2. 批量更新:使用requestIdleCallback合并低优先级更新
  3. 依赖精简:通过shouldTrack标志控制依赖收集
  4. 内存管理:提供$dispose()方法清理不再需要的Watcher

七、测试验证方法

  1. 单元测试

    1. test('should trigger update when property changes', () => {
    2. const obj = reactive({ count: 0 });
    3. let updated = false;
    4. new Watcher(() => {
    5. updated = true;
    6. });
    7. obj.count = 1;
    8. expect(updated).toBe(true);
    9. });
  2. 性能基准测试

    1. function benchmark() {
    2. const obj = reactive(Array(1000).fill(0).reduce((acc, _) => {
    3. acc[`key${Math.random()}`] = Math.random();
    4. return acc;
    5. }, {}));
    6. const start = performance.now();
    7. for (let i = 0; i < 1000; i++) {
    8. obj[`key${Math.random()}`] = Math.random();
    9. }
    10. console.log(`Update time: ${performance.now() - start}ms`);
    11. }

通过以上实现,新手开发者可以逐步构建自己的响应式系统。建议从最小实现开始,逐步添加数组支持、异步更新队列等高级功能。理解这些原理后,阅读Vue/React等框架的源码将变得更加容易。

相关文章推荐

发表评论