
本文详解为何简单闭包无法复现 usestate 行为,并提供符合 react 更新机制的自定义 usecustomstate 实现方案,包含 useref + useeffect 的正确范式、可变状态同步原理及关键注意事项。
React 的 useState 不仅管理值,更核心的是触发组件重渲染。你原始代码的问题在于:value 是一个局部变量,setValue 仅修改了该变量,但未通知 React 触发更新——因此 UI 永远停留在初始值 3。
要真正模拟 useState,必须满足两个条件:
- 状态持久化:跨多次渲染保持最新值(不能每次调用 Hook 都重置);
- 触发重渲染:状态变更后,必须调用 setState 或等效机制(如 forceUpdate)驱动组件更新。
✅ 正确实现方式是借助 useRef 存储当前值 + useState(或 useReducer)驱动更新:
import React, { useState, useRef, useCallback } from 'react';
const useCustomState = (initialState) => {
// 使用 useRef 持久化最新值(不触发渲染)
const valueRef = useRef(initialState);
// 使用 useState 控制渲染(真正的“驱动器”)
const [, forceUpdate] = useState({});
const setValue = useCallback((newVal) => {
// 同步更新 ref 值(供后续读取)
valueRef.current = typeof newVal === 'function'
? newVal(valueRef.current)
: newVal;
// 触发重渲染(关键!)
forceUpdate({});
}, []);
// 返回当前 ref 值(始终是最新的)
return [valueRef.current, setValue];
};? 使用示例(与原生 useState 完全一致):
function App() {
const [count, setCount] = useCustomState(3);
return (
<>
{count}
>
);
}⚠️ 关键注意事项:
- ❌ 不要仅用普通变量(如 let value = ...)存储状态——它会在每次渲染时重新声明,丢失上一次值;
- ✅ useRef 是唯一能在不触发重渲染的前提下跨渲染保留值的机制;
- ✅ useState(哪怕只用于 forceUpdate)是触发 UI 同步的必要桥梁;
- ✅ 使用 useCallback 包裹 setValue,避免每次渲染都生成新函数,防止子组件不必要的重渲染;
- ? 支持函数式更新(setCount(prev => prev + 1)),通过 typeof newVal === 'function' 判断并执行;
? 延伸建议:
实际项目中,无需重复造轮子。useState 本身已高度优化且支持所有场景(对象、数组、函数更新等)。若需封装逻辑(如表单状态、防抖 setState),应基于 useState 构建「带业务语义的 custom Hook」(例如 useFormState, useDebouncedState),而非试图“重写” useState 内核。
掌握其原理是为了深入理解 React 的状态模型,而非替代它——这才是自定义 Hook 的正确实践哲学。










