钩子✅
React 内置了哪些 hooks ?
答案
React 19.1 版本下目前支持的 Hooks 列表如下:
Hook | 说明 | 常见场景示例 |
---|---|---|
useState | 组件状态管理 | 计数器、表单输入 |
useEffect | 副作用处理 | 数据请求、订阅、清理 |
useContext | 跨组件共享数据 | 主题、用户信息 |
useReducer | 复杂状态逻辑管理 | 多状态、依赖前一状态 |
useCallback | 缓存函数引用 | 性能优化、子组件传参 |
useMemo | 缓存计算结果 | 计算属性、性能优化 |
useRef | 获取/存储可变引用 | DOM、定时器、缓存值 |
useLayoutEffect | DOM更新后同步副作用 | 测量布局、动画 |
useImperativeHandle | 暴露ref自定义实例值 | 转发ref、封装组件 |
useDebugValue | 自定义hook调试信息 | Hook开发 |
useId | 生成唯一ID | 表单、无障碍 |
useDeferredValue | 延迟值更新 | 输入防抖、性能优化 |
useTransition | 标记并发更新 | 低优先级UI切换 |
useSyncExternalStore | 订阅外部存储 | 状态库、全局数据 |
useInsertionEffect | 样式插入前副作用 | CSS-in-JS库 |
useOptimistic | 乐观更新 | 表单提交、列表更新 |
useActionState | 处理异步操作状态,react 19 新增 | 异步数据加载、提交表单 |
useFormStatus | 表单状态管理,只针对 react-dom 环境 | 表单提交状态、验证 |
代码示例
常见误区
- 误用 useEffect 导致死循环:依赖项数组未正确设置。
- useRef 变化不会触发组件更新。
- useMemo/useCallback 过度使用反而影响性能。
大部分业务场景只需掌握 useState、useEffect、useContext、useRef,其他 hooks 可按需查阅官方文档。
延伸阅读
- React 官方 Hooks 文档
- React Hooks FAQ 简要说明常见错误
- 深入理解 React Hooks 进阶原理解析
说一下 useState 的使用?
答案
useState 用在函数组件和自定义 Hook 中,用于声明和管理组件状态。它返回一个包含当前状态值和更新函数的数组。 useState 函数如下
type Dispatch<A> = A => void;
type BasicStateAction<S> = (S => S) | S;
function useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]
- initialState:初始状态值
- 可以是任意类型,也可以是一个返回初始状态的函数。
- 也可是函数返回一个初始值,函数只会在组件首次渲染时调用一次。
- 返回值是一个数组,第一个元素是当前状态值,第二个元素是更新状态的函数。
- 第一个元素是当前状态值未传入 initialState 时默认为 undefined。
- 更新函数
- 支持直接传入新状态值
- 也支持传入一个函数来基于当前状态计算新状态。
示例如下
function Counter () { const [count, setCount] = useState(0) return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Add</button> </div> ) }
使用中需要注意
- 遵守 Hooks 规则
- 只能在函数组件或自定义 Hook 中调用 useState。
- 不能在条件语句、循环或嵌套函数中调用 useState。
- 对于初始状态值不要传入复杂的函数,这样会导致每次组件渲染时都重新计算初始状态。
- 更新状态后,不要直接消费 state 的值,需要通过更新函数获取最新状态。
- 对于非原始更新,注意要重新赋值避免引用类型未修改导致的问题
延伸阅读
- hooks-state 旧版文档对 useState 的介绍
- useState 新版文档对 useState 的介绍
说一下 useRef?
答案
- 不触发视图更新的信息,可以使用 useRef 来存储。
- 典型使用场景
- 保留 dom 引用
- 记录 timer 等句柄信息
function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> <button onClick={handleStop}> Stop </button> </> ); }
- 注意事项
延伸阅读
- Referencing values with refs 官方文档说明 reference 的使用
- Refs and the DOM 官方文档详细讲解 Refs
useReducer 的作用,和 useState 有什么区别?
答案
useReducer
是 React 提供的状态管理 Hook,适用于复杂的状态逻辑管理。它与 useState
的主要区别在于状态管理方式和适用场景。
基本语法
const [state, dispatch] = useReducer(reducer, initialState)
reducer
: 纯函数,接收当前状态和动作,返回新状态initialState
: 初始状态值state
: 当前状态dispatch
: 分发动作的函数
与 useState 的核心区别
特性 | useState | useReducer |
---|---|---|
适用场景 | 简单状态管理 | 复杂状态逻辑 |
状态结构 | 单一值或简单对象 | 复杂对象、多个相关状态 |
更新方式 | 直接设置新值 | 通过动作(action)描述变化 |
状态依赖 | 适合独立状态 | 适合依赖前一状态的更新 |
逻辑复用 | 较难复用 | reducer逻辑易于复用和测试 |
典型使用场景对比
useState 适合:
// 简单计数器
function SimpleCounter () {
const [count, setCount] = useState(0)
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
)
}
useReducer 适合:
// 复杂状态管理
type State = { count: number; error: string | null; loading: boolean }
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setError'; error: string }
function reducer (state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1, error: null }
case 'decrement':
if (state.count <= 0) {
return { ...state, error: '计数不能小于0' }
}
return { ...state, count: state.count - 1, error: null }
case 'reset':
return { count: 0, error: null, loading: false }
case 'setError':
return { ...state, error: action.error }
default:
return state
}
}
function ComplexCounter () {
const [state, dispatch] = useReducer(reducer, {
count: 0,
error: null,
loading: false
})
return (
<div>
<p>计数: {state.count}</p>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
<button onClick={() => dispatch({ type: 'increment' })}>增加</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
)
}
useReducer 的优势
- 状态逻辑集中: 所有状态变更逻辑都在 reducer 中
- 可预测性: 通过动作描述状态变化,便于调试
- 逻辑复用: reducer 函数可以独立测试和复用
- 复杂状态: 适合管理包含多个字段的状态对象
实际应用场景
1. 表单状态管理
function useFormReducer (initialValues) {
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value }
case 'SET_ERROR':
return { ...state, errors: { ...state.errors, [action.field]: action.error } }
case 'RESET':
return initialValues
default:
return state
}
},
{ ...initialValues, errors: {} }
)
return [state, dispatch]
}
2. 购物车管理
function cartReducer (state, action) {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.item] }
case 'REMOVE_ITEM':
return { ...state, items: state.items.filter(item => item.id !== action.id) }
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.id ? { ...item, quantity: action.quantity } : item
)
}
default:
return state
}
}
选择建议
- 使用 useState: 状态简单、独立、更新逻辑简单
- 使用 useReducer: 状态复杂、有多个相关字段、更新逻辑复杂、需要基于前一状态计算新状态
常见误区
- 过度使用 useReducer: 简单状态也用 useReducer 增加复杂度
- reducer 不纯: 在 reducer 中执行副作用操作
- action 设计不当: action 类型不明确或包含过多逻辑
在简单场景下优先使用 useState
,当状态逻辑变得复杂、难以管理时再考虑重构为 useReducer
。
延伸阅读
- useReducer 官方文档
- useState 官方文档
- Extracting State Logic into a Reducer 状态逻辑提取指南
说一下 useEffect?
答案
useEffect 用来绑定副作用函数,如数据获取、订阅事件、DOM 操作等。useEffect 函数定义如下
type EffectCallback = () => (void | (() => void));
function useEffect(effect: EffectCallback, deps?: Array<any> | void | null): void
- effect:副作用函数,执行时机为组件渲染后。
- 可以返回一个清理函数,在组件卸载或依赖项变化时调用。
- deps:依赖项数组,控制副作用函数的执行时机。
- 如果传入空数组
[]
,副作用函数只在组件首次渲染时执行。 - 如果不传入 deps,则每次组件渲染都会执行副作用函数。
- 如果传入依赖项数组,副作用函数会在依赖项变化时执行。
- 注意不要动态改变依赖项 react 会抛出警告
- 如果传入空数组
使用中要注意
- 确保副作用函数中消费的变量在 deps 中都有申明
- 副作用函数获取的 state 为快照,如果要获取最新 state,采用回调模式消费数据,或者使用 useRef 存储最新值。特别是在回调涉及异步时间是需要尤为注意
- 正确设置清理函数避免内存泄漏问题
延伸阅读
- hooks effect 旧版文档对 effects 说明
- Timing of effects 旧版文档对 useEffect 执行时机的讲解
- Synchronizing with Effects 新版文档讲解如何使用 useEffect
- You Might Not Need an Effect 新版文档讲解 useEffect 的注意事项
- useEffect 新版文档对 useEffect 的详细说明
- A Complete Guide to useEffect React 核心开发 dan abramov 对 useEffect 的详细讲解
useLayoutEffect 和 useEffect 有什么区别?
答案
useLayoutEffect
和 useEffect
的主要区别在于执行时机和同步性。
核心区别表格
特性 | useLayoutEffect | useEffect |
---|---|---|
执行时机 | DOM 更新后,浏览器绘制前 | 浏览器绘制后,异步执行 |
同步性 | 同步执行,阻塞渲染 | 异步执行,不阻塞渲染 |
适用场景 | 需要立即测量 DOM、避免视觉闪烁 | 数据获取、订阅、清理等副作用 |
性能影响 | 可能阻塞浏览器绘制 | 不影响浏览器绘制性能 |
执行时机详细说明
-
useLayoutEffect 在 React 渲染周期中的位置:
- DOM 更新完成 → useLayoutEffect 执行 → 浏览器绘制
- 同步执行,会阻塞浏览器的绘制过程
- 适合需要立即获取最新 DOM 布局信息的场景
-
useEffect 在 React 渲染周期中的位置:
- DOM 更新完成 → 浏览器绘制 → useEffect 异步执行
- 不阻塞浏览器的绘制过程
- 更适合处理副作用操作
典型应用场景
useLayoutEffect 适用场景:
- 测量 DOM 元素尺寸或位置,例如记录滚动位置、获取元素宽高等
- 避免视觉闪烁的 DOM 操作
- 需要在浏览器绘制前同步更新的场景
useEffect 适用场景:
- 数据获取(API 调用)
- 设置订阅或事件监听
- 清理资源
- 不需要阻塞渲染的副作用
延伸阅读
- useLayoutEffect 官方文档详细说明
useCallback 和 useMemo 有什么区别,如何使用?
答案
属性 | useCallback | useMemo |
---|---|---|
作用 | 缓存函数 | 缓存计算结果 |
返回值 | 函数 | 任意类型 |
典型场景 | 事件处理、子组件 props | 复杂计算、依赖对象 |
依赖变化 | 重新生成函数 | 重新计算结果 |
代码示例
- useCallback
- useMemo
一个典型的误区是认为 useCallback 和 useMemo 能阻止组件重新渲染,实际上 React 的理念是如果组件状态发生了变化整个子树都会重新刷新。而 useCallback 和 useMemo 只是一个缓存函数,消费的场景取决于你是否需要利用缓存。正确的避免重新渲染的方式是使用 React.memo、componentShouldUpdate、React.PureComponent
等技术,同时结合 useCallback 和 useMemo 来优化性能。进一步理解阅读 When should you NOT use React memo? 在 Issue 中 React 核心开发 Mark Erikson 做了详细的阐述
延伸阅读
useContext 的作用是什么 ?
答案
useContext 实现在函数组件中消费 Context 的值。
注意事项
- Context 分割: 将不同功能的数据分到不同 Context 中,避免一个值变化导致整个组件树重新渲染
- 使用 useMemo 缓存 Context 值: 防止对象/数组引用变化导致的不必要渲染
- React.memo 包裹消费组件 避免不必要的渲染
延伸阅读
useReducer 和 useState 有什么区别?
答案
对比维度 | useState | useReducer |
---|---|---|
概念 | 用于管理单一状态值 | 用于管理包含多字段或复杂逻辑的状态 |
使用方式 | const [state, setState] = useState(init?) | const [state, dispatch] = useReducer(reducer, initialArg, init?) |
更新方式 | 直接传入新值或函数:setState(next) | 派发 action:dispatch({ type, payload }) |
适用场景 | 简单状态、更新逻辑直接 | 复杂状态、多个字段、更新逻辑集中处理 |
参看示例对于复杂的状态处理可以利用 useReducer 将状态逻辑从组件中抽离出来放在组件外,更具体的逻辑可参考 Extracting State Logic into a Reducer 官方文档说明
useInsertionEffect 有什么用?
答案
useInsertionEffect
是 React 18 引入的一个特殊 Hook,专门用于在 DOM 变更后、所有其他 effect 执行前插入样式。它主要为 CSS-in-JS 库设计,解决在渲染中注入样式的性能问题。
基本语法
useInsertionEffect(setup, deps?)
setup
: 处理 effect 的函数,可以返回清理函数deps
: 可选的依赖项数组
useInsertionEffect
是高度专业化的 Hook,主要用于 CSS-in-JS 库开发。普通应用开发应优先使用 useEffect
或 useLayoutEffect
。
与其他 Effect Hook 的执行顺序
Hook | 执行时机(React 渲染阶段) | 相对顺序 | 主要用途(推荐场景) |
---|---|---|---|
useInsertionEffect | DOM 变更后,绘制前,早于所有其他 effect(同步) | 最早(高优先级) | 注入样式(CSS-in-JS) 避免 FOUC、布局抖动 |
useLayoutEffect | DOM 变更后,绘制前(同步),紧随 useInsertionEffect | 中间 | DOM 测量与读取强制同步布局动画初始值设定 |
useEffect | 浏览器绘制后(异步,排在任务队列中) | 最晚(非阻塞) | 异步任务(如:fetch、订阅)非视觉副作用(日志、事件绑定) |
延伸阅读
- useInsertionEffect 官方文档
- Add useInsertionEffect github 对 useInsertionEffect 的 PR 说明
Library Upgrade Guide: <style> (most CSS-in-JS libs)
对 useInsertionEffect 的使用讨论
useActionState 和 useFormStatus 有什么区别?
答案
useActionState
和 useFormStatus
是 React 19 中引入的两个新 Hook,都用于处理表单相关的状态管理。
核心区别对比
特性 | useActionState | useFormStatus |
---|---|---|
用途 | 处理异步操作状态 | 获取表单提交状态 |
适用范围 | 通用异步操作 | 仅针对表单提交 |
环境限制 | React 核心 | react-dom 环境 |
状态管理 | 管理完整的异步操作生命周期 | 只读的表单状态信息 |
返回值 | [state, action,pending] | { pending, data, method, action } |
- useActionState
- useFormStatus
延伸阅读
- useActionState 官方文档
- useFormStatus 官方文档
- React 19 新特性 官方博客
useDeferredValue 和 useTransition 的区别?
答案
useDeferredValue
和 useTransition
都是 React 18 引入的并发特性 Hook,用于优化用户体验,核心区别如下
核心区别对比
特性 | useDeferredValue | useTransition |
---|---|---|
作用对象 | 延迟值的更新 | 延迟状态更新操作 |
使用方式 | 包装值 | 包装更新函数 |
返回值 | 延迟的值 | [isPending, startTransition] |
控制方式 | 被动延迟 | 主动标记 |
适用场景 | 依赖外部数据 | 频繁刷新的非核心状态,注意不在用户输入等中使用,详见 updating-an-input-in-a-transition-doesnt-work |
原理差异 | 创建值的延迟版本,React 会在高优先级更新完成后再更新延迟值 | 将状态更新标记为可中断的低优先级任务,允许异步 action 结束后再触发更新 |
- useTransition
- useDeferredValue
startTransition 的本质就是延迟的更新函数,效果类似 debounce。伪代码如下
let isInsideTransition = false
function startTransition (scope) {
isInsideTransition = true
scope()
isInsideTransition = false
}
function setState () {
if (isInsideTransition) {
// 对于处于 transiton 中的更新,React 会将其标记为低优先级,延迟调度
} else {
// 对于非 transition 中的更新,React 会立即调度
}
}
所以这也是为什么不建议在 startTransition 中使用 setTimeout、setInterval 等异步函数,因为会导致调度的标记丢失,可以通过 setTransition
包裹传入的回调解决此问题,详见 React doesn’t treat my state update as a Transition
useDeferredValue 会将依赖延迟值的组件渲染优先级降低,相当于提供了认为控制子树的渲染优先级,可以参考 subtree priorities in Concurrent React 进一步了解。
延伸阅读
- useDeferredValue 官方文档
- useTransition 官方文档
- React 18 并发特性详解 深入理解并发渲染
useImperativeHandle 的作用?
答案
useImperativeHandle
是 React 提供的 Hook,用于自定义通过 ref 暴露给父组件的实例值。react19 可以直接结合 useRef
使用,react18 及以前需要结合 forwardRef
使用。
- React19
- React18
通过 useImperativeHandle
来限制父组件对子组件实例的访问,避免暴露不必要的内部方法和状态。除了直接操作 DOM 元素外,尽可能通过属性和事件来控制子组件的行为
延伸阅读
- useImperativeHandle 官方文档
- 父组件调用子组件方法 相关讨论
useSyncExternalStore 的作用?
答案
useSyncExternalStore
是 React 18 引入的 Hook,用于安全地订阅外部数据源,用来实现脱离 React 内部状态流,基于外部状态实现组件的自定义更新。
基本语法
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
subscribe
: 订阅函数,接收监听器并返回取消订阅函数,注意回调函数注册和回收在框架内部实现。getSnapshot
: 获取当前状态快照的函数getServerSnapshot
: 可选,用于 SSR 的服务端快照函数snapshot
: 返回当前状态快照
useSyncExternalStore 实现了外部状态和组件刷新的绑定,典型场景包括全局 DOM 事件或者外部状态管理库(如 Redux、MobX)的订阅。一般不直接使用 useSyncExternalStore, 而是通过封装的自定义 Hook 来使用。
延伸阅读
- useSyncExternalStore 官方文档
- 外部状态管理最佳实践 社区讨论
useOptimistic 的作用?
答案
useOptimistic
是 React 19 引入的一个 Hook,用于实现乐观更新(Optimistic Updates)。它允许在异步操作开始前通过一个预设的值来更新 UI, 当异步操作完成后,在基于实际结果更新状态。
基本语法
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn)
state
: 当前的实际状态,在异步操作完成后,optimisticState 的值会与之同步updateFn(currentState, optimisticValue)
: 函数,用于生成乐观更新的状态,第一个参数为当前 state 的状态,第二个值为 addOptimistic 传入的值optimisticState
: 乐观更新后的状态addOptimistic(optimisticValue)
: 触发乐观更新的函数
乐观更新适合高频交互和网络延迟明显的场景,但要确保异步操作的可靠性和错误处理。
延伸阅读
- useOptimistic 官方文档
- React 19 新特性 官方博客
useId 的作用?
答案
useId
是 React 18 引入的一个 Hook,用于生成在客户端和服务器端都一致的唯一标识符。
基本语法
const id = useId(): string
核心作用
功能 | 说明 | 使用场景 |
---|---|---|
无障碍性支持 | 为屏幕阅读器等辅助技术提供关联 | aria-labelledby 、aria-describedby |
表单 label 和 input 匹配 | 生成唯一 ID,避免冲突 | htmlFor 、id |
组件间唯一标识 | 确保组件在 SSR 和 CSR 中 ID 一致 | 复杂组件、动态生成的元素 |
延伸阅读
- useId 官方文档
- React 18 新特性 官方博客
- 无障碍设计指南 Web 可访问性最佳实践
useDebugValue 的作用?
答案
useDebugValue
是 React 提供的一个调试专用 Hook,主要用于在 React 开发者工具(React DevTools)中为自定义 Hook 显示调试信息。
基本语法
useDebugValue(value: any, format?: (value: any) => any): void
value
: 要在开发者工具中显示的值format
: 可选的格式化函数,用于自定义显示格式
延伸阅读
- useDebugValue 官方文档
- React DevTools React 开发者工具使用指南