编码母题 / P1 / 前端与专项架构 / 02

最小响应式状态管理器

先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。

重要程度岗位专项难度困难预计完成50–70 分钟

难度按 LeetCode 简单/中等/困难三档估计;工程题并非 LeetCode 官方题目。用时指独立编码并检查主要边界的参考时间,因经验而异。

题目是什么

实现最小 reactive/effect/computed:读取收集依赖,写入触发更新;条件分支切换时清理旧依赖,同步多次写入在微任务内去重。

输入输出示例

effect 从读取 a 切换为读取 b 后,继续写 a 不应再触发它。

练习起手式与验证

复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。

/*
 * 题目:最小响应式状态管理器
 * 要求:实现最小 reactive/effect/computed:读取收集依赖,写入触发更新;
 * 条件分支切换时清理旧依赖,同步多次写入在微任务内去重。
 * 示例:effect 从读取 a 切换为读取 b 后,继续写 a 不应再触发它。
 */
const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
function flushQueue() {}
function cleanup(runner) {}
function effect(fn, options = {}) {}
function track(target, key) {}
function trigger(target, key) {}
function reactive(target) {}
function computed(getter) {}

(async () => {
  const state = reactive({ ok: true, a: 1, b: 2 });
  let observed;
  let runs = 0;
  effect(() => {
    runs += 1;
    observed = state.ok ? state.a : state.b;
  });
  state.a = 3;
  state.a = 4;
  await Promise.resolve();
  console.log(observed); // 预期:4
  console.log(runs); // 预期:2
  state.ok = false;
  await Promise.resolve();
  state.a = 9;
  await Promise.resolve();
  console.log(observed); // 预期:2
})().catch(console.error);

解题思路

展开思路、不变量与常见边界

关键切入点:依赖图双向记录 target/key 与 effect;computed 懒计算并在依赖变化时标脏。

核心模型:WeakMap(target→Map(key→Set(effect)))。activeEffect 表示当前消费者; 每个 effect 反向保存 deps 便于清理。不变量:依赖边双向一致;写相同值不触发; 调度队列中同一 runner 最多一个。此练习不覆盖数组长度、Map/Set 和 effectScope。

参考代码与验证

“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。

const targetMap = new WeakMap();
let activeEffect = null;
const queue = new Set();
let flushing = false;
// 关键机制:依赖图双向记录 target/key 与 effect;computed 懒计算并在依赖变化时标脏。
function flushQueue() {
  if (flushing) return;
  flushing = true;
  queueMicrotask(() => {
    try {
      for (const job of [...queue]) {
        queue.delete(job);
        job();
      }
    } finally {
      flushing = false;
      if (queue.size) flushQueue();
    }
  });
}
function cleanup(runner) {
  for (const dep of runner.deps) dep.delete(runner);
  runner.deps.length = 0;
}
function effect(fn, options = {}) {
  const runner = () => {
    cleanup(runner);
    const previous = activeEffect;
    activeEffect = runner;
    try {
      return fn();
    } finally {
      activeEffect = previous;
    }
  };
  runner.deps = [];
  runner.scheduler = options.scheduler;
  if (!options.lazy) runner();
  return runner;
}
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()));
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}
function trigger(target, key) {
  const effects = new Set(targetMap.get(target)?.get(key) || []);
  for (const runner of effects) {
    if (runner.scheduler) runner.scheduler();
    else {
      queue.add(runner);
      flushQueue();
    }
  }
}
function reactive(target) {
  return new Proxy(target, {
    get(object, key, receiver) {
      const value = Reflect.get(object, key, receiver);
      track(object, key);
      return value && typeof value === "object" ? reactive(value) : value;
    },
    set(object, key, value, receiver) {
      const old = object[key];
      const changed = !Object.is(old, value);
      const result = Reflect.set(object, key, value, receiver);
      if (changed) trigger(object, key);
      return result;
    },
  });
}
function computed(getter) {
  let dirty = true;
  let value;
  const holder = {};
  const runner = effect(getter, {
    lazy: true,
    scheduler: () => {
      if (!dirty) {
        dirty = true;
        trigger(holder, "value");
      }
    },
  });
  return {
    get value() {
      track(holder, "value");
      if (dirty) {
        value = runner();
        dirty = false;
      }
      return value;
    },
  };
}

// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
  const state = reactive({ ok: true, a: 1, b: 2 });
  let observed;
  let runs = 0;
  effect(() => {
    runs += 1;
    observed = state.ok ? state.a : state.b;
  });
  state.a = 3;
  state.a = 4;
  await Promise.resolve();
  console.log(observed); // 预期:4
  console.log(runs); // 预期:2
  state.ok = false;
  await Promise.resolve();
  state.a = 9;
  await Promise.resolve();
  console.log(observed); // 预期:2
  const doubled = computed(() => state.b * 2);
  console.log(doubled.value); // 预期:4
  state.b = 5;
  console.log(doubled.value); // 预期:10


})().catch(console.error);

复杂度分析

时间复杂度:追踪 O(1);触发 O(e)。单个依赖登记均摊常数;更新需遍历该属性的 e 个 effect。

空间复杂度:O(d)。依赖图存储 d 条属性与 effect 关联,队列至多容纳活跃 effect。

下载原始 JS 文件