核心概念

响应式系统的核心是建立数据副作用函数之间的关联关系,当数据变化时自动触发相关副作用函数。

系统架构

🔧 核心数据结构

let activeEffect = null;          // 当前激活的副作用函数
const depsMap = new Map();        // 依赖收集映射表
const effectStack = [];           // 副作用函数栈(解决嵌套问题)

// depsMap 结构示例:
// Map(3) {
//   'a' => Set(2) { [Function: environment], [Function: environment] },
//   'b' => Set(1) { [Function: environment] },
//   'c' => Set(1) { [Function: environment] }
// }

🎯 响应式数据

const data = { a: 1, b: 2, c: 3 };
const state = new Proxy(data, {
  get(target, key) {
    track(target, key);    // 📥 依赖收集
    return target[key];
  },
  set(target, key, value) {
    target[key] = value;
    trigger(target, key);  // 📤 派发更新
    return true;
  }
});

核心实现

1. 📥 依赖收集 (Track)

const track = (target, key) => {
  if (activeEffect) {
    let deps = depsMap.get(key);  // 获取属性对应的依赖集合
    if (!deps) {
      deps = new Set();           // 🆕 创建新的依赖集合
      depsMap.set(key, deps);
    }
    activeEffect.deps.push(deps); // 🔗 记录当前环境函数的所有依赖集合
    deps.add(activeEffect);       // ➕ 将环境函数加入依赖集合
  }
};

2. 📤 派发更新 (Trigger)

const trigger = (target, key) => {
  const deps = depsMap.get(key);  // 获取依赖集合
  if (deps) {
    // ☑️ 关键:创建快照避免无限循环
    const effectToRun = new Set(deps);
    effectToRun.forEach(effect => effect());
  }
};

3. 🎭 副作用函数 (Effect)

function effect(fn) {
  // 🎪 创建环境函数(形成闭包)
  const environment = () => {
    activeEffect = environment;    // 🎯 激活当前环境函数
    effectStack.push(environment); // ⬆️ 入栈
    
    clearDeps(environment);        // 🧹 清理旧依赖
    fn();                          // 🚀 执行副作用函数
    
    activeEffect = effectStack.pop(); // ⬇️ 出栈
  };
  
  environment.deps = [];           // 📝 初始化依赖记录
  environment();                   // 🎬 立即执行
}

4. 🧹 依赖清理 (clearDeps)

function clearDeps(environment) {
  const deps = environment.deps;   // 📦 获取所有依赖集合
  
  if (deps.length) {
    deps.forEach(dep => {
      dep.delete(environment);     // 🗑️ 从每个依赖集合中删除自己
      
      if (dep.size === 0) {        // 🎯 如果集合为空则完全清理
        for (let [key, value] of depsMap) {
          if (value === dep) {
            depsMap.delete(key);   // 🧽 删除空依赖集合
          }
        }
      }
    });
    deps.length = 0;               // 🔄 清空依赖记录
  }
}

系统工作原理

📊 依赖关系建立过程

初始状态: activeEffect = null, depsMap = {}

effect(() => { state.a }) 执行:
1. 🎪 创建 environment 环境函数
2. 🎯 activeEffect = environment
3. 📥 访问 state.a → 触发 track
4. ➕ 将 environment 加入 depsMap['a'] 集合
5. 🔗 environment.deps 记录 [depsMap['a']]

最终: depsMap = { 'a': Set([environment]) }

🔄 动态依赖更新

effect(() => {
  if (state.a === 1) {
    state.b;    // ✅ 条件成立时建立 b 依赖
  } else {
    state.c;    // ✅ 条件不成立时建立 c 依赖
  }
});

执行过程:

  • 🧹 每次执行前先清理旧依赖

  • 📥 根据条件动态建立新依赖

  • 🔄 确保依赖关系始终准确

设计演进历程

第1版:基础实现

let activeEffect = null;
const depsMap = new Map();

function track(target, key) {
  if (activeEffect) {
    let deps = depsMap.get(key);
    if (!deps) {
      deps = new Set();
      depsMap.set(key, deps);
    }
    deps.add(activeEffect);
  }
}

function trigger(target, key) {
  const deps = depsMap.get(key);
  if (deps) {
    deps.forEach(effect => effect());
  }
}

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

架构图示:

activeEffect 作用:

⚠️ 问题一:动态依赖失效

测试用例:


    state.b;  // 第一次:建立 b 依赖
  } else {
    state.c;  // 第二次:应该建立 c 依赖
  }
  console.log("执行了函数");
});

问题分析:

  • 第一次执行:建立 a → Set(fn)b → Set(fn) 依赖

  • 修改 state.a = 100 后重新执行

  • activeEffect = null,无法进入依赖收集

依赖关系变化:

// 第一次执行结果:
Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
Map(2) {
  'a' => Set(1) { [Function (anonymous)] },
  'b' => Set(1) { [Function (anonymous)] }
}
执行了函数

// 期望第二次执行结果:
Map(2) {
  'a' => Set(1) { [Function (anonymous)] },
  'c' => Set(1) { [Function (anonymous)] }
}

问题根源图示:

✅ 解决方案:环境函数包装

function effect(fn) {
  const environment = () => {
    activeEffect = environment;  // 🎯 关键:重新激活
    fn();
    activeEffect = null;
  };
  environment();
}

改进效果图示:

⚠️ 问题二:旧依赖残留

问题描述: 条件分支变化时,旧的依赖关系没有清理,导致依赖关系不准确。

✅ 解决方案:依赖清理机制

function cleanup(environment) {
  let deps = environment.deps;
  if (deps.length) {
    deps.forEach(dep => {
      dep.delete(environment);
      if (dep.size === 0) {
        for (let [key, value] of depsMap) {
          if (value === dep) {
            depsMap.delete(key);
          }
        }
      }
    });
    deps.length = 0;
  }
}

清理机制图示:

⚠️ 问题三:无限循环

产生场景:

effect(() => {
  if (state.a === 1) {
    state.b;
  } else {
    state.c;
  }
  console.log("执行了函数1");
});

effect(() => {
  console.log(state.a);
  console.log(state.c);
  console.log("执行了函数2");
});

state.a = 2;  // 🔄 触发无限循环

循环过程:

  1. 📤 state.a = 2 触发 trigger

  2. 🚀 执行第二个 effect 函数

  3. 📥 访问 state.a 重新收集依赖

  4. 🔄 再次触发 trigger,形成循环

✅ 解决方案:快照机制

function trigger(target, key) {
  const deps = depsMap.get(key);
  if (deps) {
    const effectsToRun = new Set(deps);  // ☑️ 创建快照
    effectsToRun.forEach(effect => effect());
  }
}

⚠️ 问题四:嵌套 Effect 异常

测试用例:

effect(() => {
  effect(() => {
    state.a;
    console.log("执行了函数2");
  });
  state.b;
  console.log("执行了函数1");
});

异常输出:

Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
执行了函数2
Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
执行了函数1  // ❌ 缺少 b 属性的依赖

问题根源图示:

✅ 解决方案:函数栈机制

function effect(fn) {
  const environment = () => {
    activeEffect = environment;
    effectStack.push(environment);  // ⬆️ 入栈
    clearDeps(environment);
    fn();
    effectStack.pop();              // ⬇️ 出栈
    activeEffect = effectStack[effectStack.length - 1]; // 恢复上一个
  };
  environment.deps = [];
  environment();
}

关键技术点

1. 🎯 环境函数设计

// ❌ 错误做法:直接使用原函数
activeEffect = fn;

// ✅ 正确做法:包装环境函数
const environment = () => {
  activeEffect = environment;  // 🎯 保持引用一致性
  // ... 其他逻辑
};

2. 🧹 依赖清理的重要性

问题场景:

effect(() => {
  if (state.a === 1) {
    state.b;  // 第一次:建立 b 依赖
  } else {
    state.c;  // 第二次:应该建立 c 依赖
  }
});

解决方案:

  • 🧹 每次执行前清理旧依赖

  • 📥 重新建立准确的新依赖关系

3. 🎪 嵌套 Effect 处理

effect(() => {
  effect(() => {
    state.a;
    console.log("内层函数");
  });
  state.b;
  console.log("外层函数");
});

栈机制保证:

  • ⬆️ 内层 effect 入栈时不破坏外层 activeEffect

  • ⬇️ 内层执行完毕正确恢复外层上下文

4. 🔄 避免无限循环

问题代码:

// ❌ 直接遍历会导致无限循环
deps.forEach(effect => effect());

解决方案:

// ✅ 创建快照避免动态修改影响
const effectToRun = new Set(deps);
effectToRun.forEach(effect => effect());

完整示例

// 使用示例
effect(() => {
  effect(() => {
    effect(() => {
      state.c;
      console.log("执行函数3");
    });
    state.a;
    console.log("执行函数2");
  });
  state.b;
  console.log("执行函数1");
});

完整代码

练习: 个人学习专用知识库~