React useState的工作原理是什么?如何用纯JS实现可正确更新状态的useState功能
原代码失效的核心原因
你最初写的逻辑存在两个本质问题:
- 值快照问题:JS中基础类型是按值传递的,你在第一次调用
useStatePureJs时,把当时等于null的内部状态直接放进了返回数组,后续setState修改的是闭包内部的state变量,和你外层解构拿到的那个初始值没有任何绑定关系,自然永远读不到更新后的值。 - 缺少更新感知通道:哪怕状态能存能改,没有设计通知机制的话,外部完全不知道状态什么时候发生了变化,更达不到React useState“状态变更驱动视图更新”的核心能力。
不依赖监听器的最小可运行实现
如果只是需要正确读写状态,根本不需要监听器机制,只要解决值快照的问题即可:把直接返回状态值,改成返回一个能读取闭包内最新状态的访问函数。
代码示例:
const createBasicState = (initialValue) => { // 闭包内存储真实状态,外部无法直接篡改 let _state = initialValue; // 暴露获取最新状态的方法 const getState = () => _state; // 更新状态的方法 const setState = (newValue) => { _state = newValue; }; return [getState, setState]; }; // 测试运行 const [count, setCount] = createBasicState(null); console.log(count()); // 初始值:null setCount(5); console.log(count()); // 输出最新值:5
类React能力需要监听器机制
如果要实现和React useState一致的“状态变更自动触发更新”的能力,就必须依赖发布订阅模式的监听器机制:
- 内部维护一个监听器集合,用来存所有状态变更后需要执行的回调(比如重新渲染UI、执行副作用函数)
- 调用
setState更新完内部状态后,自动遍历执行所有注册的监听器
完整实现示例:
const useStateLike = (initialValue) => { let _state = initialValue; // 用Set存储所有注册的监听器,天然支持去重 const listeners = new Set(); const getState = () => _state; const setState = (newValue) => { // 第一步更新内部状态 _state = newValue; // 第二步触发所有注册的变更回调 listeners.forEach(listener => listener()); }; // 提供订阅方法,让外部可以注册状态变更后的回调 const subscribe = (listener) => { listeners.add(listener); // 返回取消订阅的方法,避免内存泄漏 return () => listeners.delete(listener); }; return [getState, setState, subscribe]; }; // 测试运行 const [count, setCount, subscribe] = useStateLike(0); // 注册回调:状态变了就打印日志、更新页面 subscribe(() => { console.log('状态已更新,最新值:', count()); document.getElementById('root').textContent = count(); }); setCount(5); // 自动触发回调,控制台打印5,页面内容同步更新
常见认知误区
- 如果状态是引用类型(比如对象),哪怕直接返回状态值,也能通过引用读到最新值,但这种方式没法在状态变更时主动通知外部,本质只是个全局变量,达不到useState的核心作用。
- 也可以借助
Proxy实现对状态修改的拦截,自动触发回调,本质还是监听器机制的落地形式,没有跳出发布订阅的核心逻辑。
内容的提问来源于stack exchange,提问作者WebArtisan
相关产品推荐
相关产品推荐

