当前位置: 首页 > news >正文

手写React状态hook


在日常开发中,我们经常用到 React 的状态管理 Hook:useStateuseReducer
但你有没有想过:这些 Hook 内部是怎么实现的?为什么调用 setState 之后组件会重新渲染?

今天我们就来从零手写 useState 和 useReducer,理解它们的原理。


一、React Hook 的本质

在 React 中,每个函数组件调用时都会按照 调用顺序 来记录 Hook。
比如下面的例子:

function Counter() {const [count, setCount] = useState(0);const [name, setName] = useState("张三");return (<div><p>{count}</p><p>{name}</p></div>);
}

React 内部会维护一个 数组(或链表) 来存储这些 Hook 对应的状态:

hooks = [{ state: 0 },        // count{ state: "张三" }     // name
]
  • 每次组件渲染时,useState 都会返回这个位置对应的状态。
  • 调用 setState 时,会更新状态并触发组件重新渲染。

所以:Hook 的核心 = 存储状态 + 按顺序取出


二、手写 useState

我们先写一个最简版的 React Hook 运行环境:

let hookStates = []; // 存放所有 hook 的状态
let hookIndex = 0;   // 当前执行到第几个 hookfunction useState(initialValue) {const currentIndex = hookIndex; // 记录当前 hook 的位置// 第一次渲染:存储初始值if (hookStates[currentIndex] === undefined) {hookStates[currentIndex] = initialValue;}// 获取当前状态const state = hookStates[currentIndex];// 更新函数const setState = (newValue) => {hookStates[currentIndex] =typeof newValue === "function"? newValue(hookStates[currentIndex]): newValue;render(); // 触发重新渲染};hookIndex++; // 下一个 hook 位置return [state, setState];
}

这里我们做了几件事:

  1. hookStates 保存所有状态。
  2. hookIndex 保证按顺序存取。
  3. setState 更新状态后调用 render,模拟 React 重新渲染。

三、测试 useState

写一个小 demo:

function Counter() {const [count, setCount] = useState(0);const [name, setName] = useState("张三");console.log("render:", { count, name });return {add: () => setCount(count + 1),rename: () => setName("李四"),};
}// 模拟渲染
function render() {hookIndex = 0; // 每次渲染重置app = Counter();
}let app;
render();// 测试
app.add();    // count 从 0 → 1
app.rename(); // name 从 "张三" → "李四"

运行过程:

render: { count: 0, name: '张三' }
render: { count: 1, name: '张三' }
render: { count: 1, name: '李四' }

✅ 我们的 useState 成功模拟了 React 的状态管理!


四、手写 useReducer

useReducer 的本质就是 useState 的加强版:

  • useState 直接存值
  • useReducerreducer 函数 来更新值

我们来实现它:

function useReducer(reducer, initialValue) {const [state, setState] = useState(initialValue);function dispatch(action) {const newState = reducer(state, action);setState(newState);}return [state, dispatch];
}

是不是很眼熟?
其实 React 源码里 useState 就是 useReducer 的语法糖:

function useState(initialValue) {return useReducer((state, action) => action, initialValue);
}

五、测试 useReducer

来写一个计数器:

function reducer(state, action) {switch (action.type) {case "add":return state + 1;case "sub":return state - 1;default:return state;}
}function Counter2() {const [count, dispatch] = useReducer(reducer, 0);console.log("render:", { count });return {add: () => dispatch({ type: "add" }),sub: () => dispatch({ type: "sub" }),};
}// 模拟渲染
function render() {hookIndex = 0;app = Counter2();
}let app;
render();app.add(); // count: 0 → 1
app.add(); // count: 1 → 2
app.sub(); // count: 2 → 1

输出:

render: { count: 0 }
render: { count: 1 }
render: { count: 2 }
render: { count: 1 }

完美模拟 ✅


六、总结

  • useState:存储值,返回 [state, setState]
  • useReducer:存储值 + reducer 逻辑,返回 [state, dispatch]
  • 关系useState = useReducer 的简化版。

什么时候用哪个?

  • 逻辑简单(计数器、表单输入) → 用 useState
  • 逻辑复杂(多个状态、复杂操作) → 用 useReducer

结语

通过手写,我们发现 React Hook 并没有什么黑魔法:
它只是 按照调用顺序存储状态,并在更新时触发重新渲染。

理解了原理,再写业务代码时就更清晰:
为什么 Hook 不能写在 if/for 里?为什么每次渲染必须顺序一致?
—— 因为 React 就是用数组来按顺序存储的


文章转载自:

http://scoKI7jy.sqyjh.cn
http://zWfJWHTx.sqyjh.cn
http://joUOQys5.sqyjh.cn
http://7k4YbWMl.sqyjh.cn
http://EiIuD8jA.sqyjh.cn
http://ckcuHL8M.sqyjh.cn
http://sYqXCbA8.sqyjh.cn
http://A3Brssm3.sqyjh.cn
http://7Obk2eeA.sqyjh.cn
http://GfYdM4J9.sqyjh.cn
http://QU3ClCrf.sqyjh.cn
http://93NXvKMC.sqyjh.cn
http://W26GN3yE.sqyjh.cn
http://VUHPctEG.sqyjh.cn
http://JL2GcVOF.sqyjh.cn
http://bduWQldn.sqyjh.cn
http://EEClB339.sqyjh.cn
http://lwrt1cuh.sqyjh.cn
http://QYyB9nJh.sqyjh.cn
http://y2wK6IUl.sqyjh.cn
http://2tCvWAH7.sqyjh.cn
http://upxeF8dI.sqyjh.cn
http://ICWrHtQy.sqyjh.cn
http://XTB6dIJ3.sqyjh.cn
http://O07JJZbB.sqyjh.cn
http://x2IaV9zA.sqyjh.cn
http://kShIRPp7.sqyjh.cn
http://u2VZdw3P.sqyjh.cn
http://dkqJJAhh.sqyjh.cn
http://91cIuFpy.sqyjh.cn
http://www.dtcms.com/a/368541.html

相关文章:

  • scrypt 密钥派生算法(RFC7914)技术解析及源码示例
  • 案例分享|企微智能会话风控系统:为尚丰盈铝业筑牢沟通安全防线
  • Docker部署Drawnix开源白板工具
  • linux缺页中断频繁怎么定位
  • 代码随想录70期day3
  • AI驱动开发:颠覆传统编程新范式
  • 第三方web测评机构:【WEB安全测试中HTTP方法(GET/POST/PUT)的安全风险检测】
  • PAT 1096 Consecutive Factors
  • 53.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--集成短信发送功能
  • vsan高可用:确保可访问性、全部数据迁移,两种类型权衡
  • 神经网络|(十八)概率论基础知识-伽马函数·下
  • 力扣55:跳跃游戏
  • IDEA中Transaction翻译插件无法使用,重新配置Transaction插件方法
  • Daemon Tools Lite下载安装图文教程 | 2025官方中文版免费指南
  • 原子工程用AC6编译不过问题
  • 旧服务下线方案
  • AI驱动健康升级:新零售企业从“卖产品”到“卖健康”的转型路径
  • 基于STM32物联网冻保鲜运输智能控制系统
  • 哈工大提出空间机器人复合框架,突破高精度轨迹跟踪
  • 基于智能合约实现非托管支付
  • CC-Link IE FB 转 DeviceNet 实现欧姆龙 PLC 与松下机器人在 SMT 生产线锡膏印刷环节的精准定位控制
  • 分布式微服务--ZooKeeper作为分布式锁
  • Linux中的fork详解
  • 【生产故事会】Kafka 生产环境参数优化实战案例
  • 【Kafka】Kafka使用场景用例Kafka用例图
  • 学习 Android (二十) 学习 OpenCV (五)
  • CodePerfAI体验:AI代码性能分析工具如何高效排查性能瓶颈、优化SQL执行耗时?
  • 【leetcode】46. 全排列
  • GD32入门到实战34--ARM启动流程
  • 针对nvm不能导致npm和node生效的解决办法