【react深入】useRef与useState
今天看到两个API感觉蛮有趣useRef与useState
官方描述不再赘述,个人理解:useRef可以操作这个Dom,使用元素上的各种方法,但是useState相当于一个值存在组件中,可以改变值,但是ref只能通过current改变值。这点引起了我的好奇,useRef到底如何实现的呢?
一、前置知识:Hook 底层基础
1. 核心依赖
- Fiber 架构:每个函数组件对应一个 Fiber 节点,用于存储组件状态、DOM 信息和 Hook 链表。
- Hook 链表:组件的所有 Hook(useState/useRef 等)以单向链表形式存储在 fiber.memoizedState 中,是 Hooks 持久化的核心。
2. 关键数据结构
|
// 简化 Hook 节点结构 type Hook = { memoizedState: any; // 存储 Hook 核心数据(useRef 存容器,useState 存状态+队列) next: Hook | null; // 指向下一个 Hook 节点(形成链表) tag: HookTag; // Hook 类型标记(区分 useState/useRef) }; // 全局追踪变量(React 内部使用) let currentlyRenderingFiber = null; // 当前渲染的 Fiber 节点 let workInProgressHook = null; // 当前遍历的 Hook 节点 |
3. 核心原则
- Hook 调用顺序不可变:链表按 Hook 调用顺序排列,必须在组件顶层调用(不能在条件 / 循环中),否则会导致链表错乱。
二、useRef 源码解析
1. 功能定位
创建持久化引用容器,用于存储 DOM 引用或非渲染依赖数据,修改不触发组件重渲染。
2. 核心源码(简化版)
|
function useRef(initialValue) { // 1. 获取/创建当前 Hook 节点 const hook = updateWorkInProgressHook(HookTag.useRef); // 2. 首次渲染初始化引用容器 if (hook.memoizedState === undefined) { hook.memoizedState = { current: initialValue }; } // 3. 返回容器(每次渲染返回同一个对象) return hook.memoizedState; } |
3. 关键原理
- 持久化特性:引用容器存储在 hook.memoizedState 中,组件重新渲染时直接复用该对象(引用不变)。
- 不触发重渲染:修改 current 属性仅改变对象内部值,未改变 memoizedState 的引用,React 不会触发重渲染。
- 初始化逻辑:仅首次渲染时创建容器,后续渲染直接返回已存在的容器。
4. 适用场景
- 存储 DOM 元素引用(如 ref={inputRef})。
- 存储不需要触发渲染的持久化数据(如定时器 ID、第三方库实例)。
三、useState 源码解析
1. 功能定位
创建组件状态(state)和状态更新函数(setState),修改状态会触发组件重渲染,支持批量更新和函数式更新。
2. 核心源码(简化版)
(1)主函数
|
function useState(initialState) { // 1. 获取/创建当前 Hook 节点 const hook = updateWorkInProgressHook(HookTag.useState); // 2. 首次渲染初始化:状态 + 更新队列(循环链表) if (hook.memoizedState === undefined) { const initialValue = typeof initialState === "function" ? initialState() : initialState; hook.memoizedState = { state: initialValue, queue: { pending: null } // 存储待执行的更新任务 }; } // 3. 解构状态和更新队列 const { state, queue } = hook.memoizedState; // 4. 创建 setState 函数 const setState = (newState) => { // 创建更新任务 const update = { action: newState, next: null }; // 加入循环链表队列 if (queue.pending === null) update.next = update; else { update.next = queue.pending.next; queue.pending.next = update; } queue.pending = update; // 触发组件重新渲染 scheduleUpdateOnFiber(currentlyRenderingFiber); }; return [state, setState]; } |
(2)更新队列处理(组件重渲染时)
|
function processHookUpdateQueue(hook) { const { queue, state: oldState } = hook.memoizedState; let newState = oldState; if (queue.pending !== null) { queue.pending = null; let currentUpdate = queue.pending.next; // 遍历所有更新任务,计算新状态 do { const action = currentUpdate.action; newState = typeof action === "function" ? action(newState) : action; currentUpdate = currentUpdate.next; } while (currentUpdate !== queue.pending); // 更新状态 hook.memoizedState.state = newState; } return newState; } |
3. 关键原理
(1)状态存储
- memoizedState 存储对象:{ state: 当前状态, queue: 更新队列 }。
- 更新队列是循环链表,用于批量存储多次 setState 调用。
(2)setState 工作流程
- 调用 setState → 创建更新任务 → 加入循环链表。
- 触发组件重渲染 → 遍历更新队列 → 计算新状态(处理函数式更新)。
- 用新状态替换旧状态 → 组件用新状态重新渲染。
(3)核心特性
- 批量更新:多次 setState 合并为一次渲染(React 18 默认所有场景支持)。
- 函数式更新:setState(prev => prev + 1),解决状态依赖旧值的问题。
- 懒初始化:useState(() => 复杂计算),仅首次渲染执行初始化函数。
4. 适用场景
- 存储组件渲染依赖的状态(如表单值、弹窗显示状态、列表数据)。
- 需要通过状态变化触发 UI 更新的场景。
四、useRef 与 useState 核心区别
|
对比维度 |
useRef |
useState |
|
存储内容 |
引用容器 { current: ... } |
状态值 + 循环更新队列 |
|
修改触发重渲染 |
不触发(仅改 current 内部值) |
触发(调用 setState 加入队列) |
|
更新方式 |
直接修改 current(同步) |
批量更新 / 函数式更新(异步调度) |
|
引用稳定性 |
每次渲染返回同一个容器对象 |
状态值可能变化,setState 引用不变 |
|
初始化支持 |
仅直接传初始值(无函数式) |
支持直接传值 / 函数式懒初始化 |
|
核心用途 |
存储 DOM 引用、非渲染数据 |
存储渲染依赖的组件状态 |
五、常见易错点与注意事项
1. Hook 调用规则
- ❌ 错误:在 if/for 等条件 / 循环中调用 Hook。
- ✅ 正确:在组件顶层、函数组件或自定义 Hook 顶层调用。
- 原因:Hook 链表依赖调用顺序,顺序错乱会导致状态复用失败。
2. useState 常见问题
- 状态更新异步:setState 是异步调度,不能立即获取新状态(需用 useEffect 监听)。
- 状态依赖旧值:连续更新依赖前一次状态时,必须用函数式更新(如 setCount(prev => prev + 1))。
- 批量更新失效:需要强制同步更新时,用 ReactDOM.flushSync(() => setState(newVal))。
3. useRef 常见问题
- current 赋值时机:存储 DOM 时,需在组件挂载后访问 current(可在 useEffect 中获取)。
- 避免存储渲染依赖数据:若数据变化需要触发 UI 更新,应使用 useState 而非 useRef。
六、总结
- 底层共性:useRef 和 useState 均基于 Fiber 节点的 Hook 链表实现,依赖调用顺序保证状态持久化。
- 核心差异:useRef 是 “无渲染副作用的持久化容器”,useState 是 “带更新队列的状态管理工具”。
- 使用原则:根据是否需要触发 UI 更新选择 —— 需要更新 UI 用 useState,仅需持久化数据用 useRef。
(注:文档部分内容可能由 AI 生成)
更多推荐

所有评论(0)