Menu

React Hooks 详解:用可运行示例讲解每个 Hook

hook 是名字以 use 开头的函数,让组件可以使用 state、context、ref 或 effect 等 React 功能。按用途分组了解每个内置 hook、所有 hook 都遵守的两条规则,以及如何编写自己的 hook。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

React hooks 是名字以 use 开头的函数,让函数组件可以使用 React 的功能:useState 记住一个值,useEffect 在渲染后运行代码,useRef 保存一个 DOM 节点,useContext 读取共享数据。你在组件顶部调用它们,React 会在多次渲染之间保留它们的值。

这里有四个 hook 协同工作。useState 保存文本,useRef 指向输入框,让按钮可以聚焦它,useMemo 在查询内容变化之前一直保留过滤后的列表,useEffect 在每次查询内容变化的渲染之后打印日志。输入 an 并观察 Console:每按一次键打印一行,而且是在列表更新之后。

为什么会有 hooks

在 React 16.8 之前,需要 state 或生命周期代码的组件必须写成类。同一个功能的逻辑被拆散在 componentDidMount、componentDidUpdate 和 componentWillUnmount 中,要在组件之间共享就得用 mixin(它从来没法和 class 语法一起用)、高阶组件或 render props,而这些方式都会给组件树套上额外的层级。

// The class way: one feature, three methods
class Clock extends React.Component {
    state = { now: new Date() };
    componentDidMount() {
        this.id = setInterval(() => this.setState({ now: new Date() }), 1000);
    }
    componentWillUnmount() {
        clearInterval(this.id);
    }
    render() {
        return <p>{this.state.now.toLocaleTimeString()}</p>;
    }
}

有了 hooks,同一个功能集中在一处:effect 启动定时器,它的清理函数停止定时器。

隐藏时钟后,Console 会打印 timer stopped:Clock 离开页面时 React 运行了清理函数。再次显示它,又会启动一个新的定时器。类组件在 React 19 中依然可用,但新代码都使用 hooks。

全部内置 hook

React 19.2 按照 hook 给组件提供的能力对它们分组。前两组你每天都会用到,其余的在遇到特定问题时再用。

Hook提供的能力页面
useState一个在多次渲染之间保留的值,以及一个会触发重新渲染的 setteruseState
useReducer由 reducer 函数和派发的 action 更新的 stateuseReducer
useContext父组件提供的值,无需传递 propsuseContext
useRef一个在多次渲染之间保留的可变容器,常用来保存 DOM 节点useRef
useImperativeHandle决定父组件通过你的 ref 能看到什么forwardRef
useEffect通常在绘制之后运行的代码:定时器、订阅、获取数据useEffect
useLayoutEffect在 DOM 更新之后、绘制之前运行的代码useLayoutEffect
useInsertionEffect在 layout effect 之前运行,供 CSS-in-JS 库使用
useEffectEvent一个供 effect 调用、总能看到最新 props 和 state 的函数useEffect
useMemo在多次渲染之间缓存计算结果useMemo
useCallback在多次渲染之间缓存函数useCallback
useTransition把更新标记为非紧急,并告诉你它是否仍在等待中useTransition
useDeferredValue一个值的副本,在繁重的渲染中会滞后更新useDeferredValue
useId一个在服务器和客户端上一致的唯一 iduseId
useSyncExternalStore订阅存在于 React 之外的 storeuseSyncExternalStore
useDebugValue在 React DevTools 中为自定义 hook 显示标签
use读取 promise 或 context,可以写在条件语句中use
useActionState由表单 action 更新的 state,附带等待中标志useActionState
useOptimistic在服务器响应之前先显示预期结果useOptimistic
useFormStatus外层表单的等待状态(来自 react-dom)

use、useActionState、useOptimistic 和 useFormStatus 随 React 19 推出,useEffectEvent 在 19.2 中进入稳定版。列表中的其他 hook 在 React 18 中也能使用。

state 会触发渲染,ref 不会

并不是每个 hook 都会触发渲染。useState 和 useReducer 会:改变它们的值,React 就会重新绘制组件。useRef 给你一个对象,你可以随意修改它的 current 字段,React 永远不会因此重新渲染。所以它适合存放定时器 id、上一次的值,或者页面上不显示的计数。

useReducer 保存购物车,useState 保存备注;两者变化时都会触发渲染,effect 每次渲染打印一行。effect 还修改了 commits.current,但这个修改本身从不触发渲染,所以日志不会无限循环。计数之所以在 effect 中更新而不是在组件主体中,是因为组件主体在渲染时不应该修改任何东西。

hook 的使用规则

每个 hook 都遵守两条规则:

  1. 只在组件的顶层调用 hook,不要在条件语句、循环或嵌套函数中调用。
  2. 只在函数组件或你自己的自定义 hook 中调用 hook。

React 并不是靠名字识别 hook 的,而是靠调用顺序,并且要求每次渲染时顺序都相同。放在 if 中的 hook 会让它后面的所有 hook 错位。hook 的使用规则页面展示了会出什么问题以及怎么修复。use 是唯一的例外:它可以在条件语句中调用。

你的第一个自定义 hook

当两个组件需要同样的有状态逻辑时,把它移到一个名字以 use 开头的函数中。这个函数可以调用任何其他 hook。每个调用它的组件都拥有自己的 state。

两个计数器运行的是同一个 hook,但点击其中一个不会影响另一个:自定义 hook 共享的是逻辑,不是 state。给 useCounter 加一个 decrement 函数,再加一个调用它的按钮,两个计数器会同时获得这个功能。更多模式(useToggle、useDebounce、useLocalStorage)见自定义 hook 页面。

该用哪个 hook

  • 值变化时页面也要跟着变:用 useState,如果许多事件都更新同一个 state,用 useReducer。
  • 需要和 React 之外的东西打交道(定时器、订阅、请求):用 useEffect。
  • 需要一个 DOM 节点,或者一个页面上不显示的值:用 useRef。
  • 组件树深处的许多组件需要同一个值:用 useContext。
  • 渲染变慢是因为某个计算,或者因为一个已记忆化的子组件重新渲染:用 useMemo 和 useCallback。React Compiler 可以替你加上这些记忆化,但手写依然有效,也依然很常见。
  • 输入有卡顿,因为背后的更新太重:用 useTransition 或 useDeferredValue。

常见问题

React 中的 hooks 是什么?

hooks 是名字以 use 开头的函数,让函数组件可以使用 React 的功能:useState 用于 state,useEffect 用于副作用,useContext 用于 context,useRef 用于在多次渲染之间保留值又不触发渲染,等等。

React 为什么要加入 hooks?

在 hooks 出现(React 16.8)之前,state 和生命周期方法只存在于类组件中,复用有状态的逻辑需要用 mixin、高阶组件或 render props。hooks 把这些逻辑放进普通函数中,任何函数组件都可以调用,还能封装成自定义 hook 共享。

React 有多少个 hook?

React 19.2 的 react 包中大约有二十个内置 hook,另外 react-dom 中还有 useFormStatus。大多数应用日常只用到其中几个:useState、useEffect、useRef、useContext、useMemo 和 useCallback。

类组件中可以使用 hooks 吗?

不可以。hooks 只能在函数组件和其他 hook 中使用。不过类组件仍然可以和函数组件一起渲染,类组件也可以渲染使用了 hooks 的函数组件。

什么是自定义 hook?

你自己写的、名字以 use 开头并调用其他 hook 的函数。它让多个组件复用同一段有状态的逻辑,而每个调用它的组件仍然拥有各自独立的 state。

Coddy 编程语言插图

和 Coddy 一起学编程

开始