从零手写一个状态管理库

举报
茉莉风铃 发表于 2026/10/18 08:36:41 2026/10/18
【摘要】 从零实现一套最小可用的状态管理:全局状态、订阅通知、派生值三块,说明这套实现的适用边界。

一、先明确它要解决什么问题

状态管理要解决的是跨组件共享和变更通知这两件事。

很多项目用状态管理库,实际只用到了它百分之十的功能:一个全局变量 + 一个订阅列表。理解了这一层,就没必要一上来引依赖。

二、核心就三块

1. 存和取

用一个普通对象存状态。约定 key 用 $ 开头存内部状态,这样和业务数据不会混在一起(业务可能会存一个叫 user 的东西)。

const globalState = { $count: 0, $name: '未命名' };

2. 订阅

谁关心这个 key,谁就把回调记下来。

const watchers = new Map();

function watch(key, callback) {
  if (!watchers.has(key)) watchers.set(key, new Set());
  watchers.get(key).add(callback);
  return () => watchers.get(key).delete(callback);
}

返回一个取消订阅的函数——这一点很重要,否则组件卸载后回调还在,之后会一直报错。Vue 的组合式函数基本都是这么设计的。

3. 通知

值真的变了才通知。这里是最容易写错的地方。

function set(key, value) {
  const old = globalState[key];
  if (old === value) return;      // 相等就不通知,避免无谓的渲染
  globalState[key] = value;
  const list = watchers.get(key);
  if (list) list.forEach((cb) => cb(value, old));
}

old === value 这个判断必须加。不加的话,每次设置成相同的值都会触发一遍所有回调,性能和正确性都有问题(尤其是对象类型,虽然对象的引用变了内容可能没变)。

三、派生值

有些值是从别的值算出来的,比如总价 = 单价 × 数量。这类值不该单独存,存了就会出现两边不一致。

function computed(getter, keys) {
  return watch(keys, () => { /* 依赖变了触发 */ });
}

// 用法
const total = computed(
  () => state.price * state.count,
  ['price', 'count']
);

更实用的做法是直接写成函数,每次读取时现算。少几行代码,也不会出现同步问题:

function getTotal() {
  return state.price * state.count;
}

在组件里用它就行。性能问题的答案是"慢的话再加 memo",不是一开始就上复杂机制。

四、和组件对接

在前端框架里,大致是这样接的:

function useStore() {
  const snapshot = ref({ ...globalState });

  function set(key, value) {
    storeSet(key, value);
    snapshot.value = { ...globalState };  // 触发视图更新
  }

  return { state: snapshot, set };
}

这里用「每次变更整体替换一次引用」的方式来触发更新,简单粗暴但可靠。真正精细的依赖收集(只更新用到的部分)实现起来复杂度高不少,收益在小项目里看不出来。

五、这套实现的边界

能用的地方: 主题、表单草稿、当前用户这类少量全局数据。

不够用的地方:

  • 异步状态。 接口请求要有 loading 和 error 三个状态,自己维护很容易乱。
  • 状态之间的依赖。 A 变了 B 要跟着变,依赖一多就变成了隐式耦合。
  • 调试。 状态分散在多个回调里,出问题时数据从哪来的很难追。
  • 时间旅行。 出错时想回到上一步看看,这种需求这套实现给不了。

六、什么时候该换回库

判断标准是状态之间的关系复杂度,不是项目大小:

  • 只有几个互不相关的全局字段 → 自己写够了
  • 状态之间有派生关系、有异步、有多个组件同时读写 → 上成熟库

我发现大多数项目一开始自己写,写到第三层嵌套就明白了为什么要用库。这个过程本身是有价值的,因为用库之后你才知道它帮你省掉了哪部分。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。