Hooks 按职责分几类:useState 和 useReducer 负责数据驱动更新,useEffect 和 useLayoutEffect 负责副作用,useMemo 和 useCallback 负责缓存,useRef 负责跨渲染保存引用。React 18 另外新增了 useSyncExternalStore、useTransition 和 useDeferredValue。
最后更新:2026-09-17 · 基于 覆盖到 React 18 新增的并发相关 Hook
React Hooks详解
本文整理自:https://juejin.cn/post/7118937685653192735,作者:我不是外星人
1. 概述
Hooks 是 React 16.8 的新增特性。并在React 18版本新增了一些功能。Hooks可以让函数组件也可以实现部分类组件的功能,比如state状态管理、部分React生命周期钩子、以及其他的React特性。
在Hooks出现以前,开发遇到的问题:
- 函数组件后期维护需要添加状态管理的功能
- 一些函数组件中不能处理接口请求、获取数据的逻辑处理,只能改成类组件
- 类组件随着开发功能越来越多,随着功能增强而变得越来越臃肿。在进行优化拆分时,只要有状态管理的就都需要使用类组件拆分
所以 Hooks 出现本质上原因是:
- 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取 ref ,也能做数据缓存
- 解决逻辑复用难的问题
- 放弃面向对象编程,拥抱函数式编程
关于自定义 Hooks:
自定义 hooks 是在 React Hooks 基础上的一个拓展,可以根据业务需求制定满足业务需要的组合 hooks ,更注重的是逻辑单元。通过业务场景不同,到底需要React Hooks 做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义 hooks 产生的初衷。
自定义 hooks 也可以说是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。
hooks功能概览和出现的版本:
2. hooks数据驱动更新
2.1 useState
useState 可以使函数组件像类组件一样拥有 state,函数组件通过 useState 可以让组件重新渲染,更新视图。
使用方法、参数说明:
1 | import { useState } from 'react'; |
- state:状态变量,目的提供给 UI ,作为渲染视图的数据源。
- setState:用于更新状态值的函数。当使用这个函数设置新的状态时,React 会根据新的状态重新渲染组件。
- initData:状态的初始值。分为三种情况:第一种是不传,则 state 的初始值为undefined。第二种情况是非函数,将作为 state 初始化的值。 第三种情况是函数,函数的返回值作为 useState 初始化的值。
useState例子:
1 | const DemoState = (props) => { |
useState 注意事项:
1、在函数组件一次执行上下文中,state 的值是固定不变的
1 | function Index(){ |
2、 如果两次传入相同的 state 值,那么组件就不会更新
1 | let account =5; |
3、设置的state值,在当前执行上下文中获取不到最新的 state, 只有再下一次组件 rerender 中才能获取到。
4、当调用 setstate 设置state值时,React 将跳过子组件的渲染及 effect 的执行。需要注意的是,React 可能仍需要在跳过渲染前渲染该组件。不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。
2.2 useReducer
useReducer 是 react-hooks 提供的能够在无状态组件中运行的类似redux的功能 api 。
使用方法、参数说明:
1 | import { useReducer } from 'react'; |
- state:状态变量,目的提供给 UI ,作为渲染视图的数据源。
- dispatch:用于更新状态值的函数。本质上跟 useState方法返回的 setState 方法一样。
- reducer:一个函数 reducer ,我们可以认为它就是一个 redux 中的 reducer , reducer的参数就是常规reducer里面的state和action, 返回改变后的state, 这里有一个需要注意的点就是:如果返回的 state 和之前的 state ,内存指向相同,那么组件将不会更新。
- initialState:状态的初始值。如果不传则 state 的初始值为undefined。注意不能使用函数返回值形式设置。
- processInitState:选填,处理初始值的方法,initialState 可以当这个函数的参数使用。
useReducer 例子:
1 | function Index({ dispatch, State }) { |
2.3 useSyncExternalStore(v18新增)
useSyncExternalStore 的诞生和 v18 的更新模式下外部数据的 tearing 有着十分紧密的关联。useSyncExternalStore 能够让 React 组件在 concurrent 模式下安全地有效地读取外接数据源,在组件渲染过程中能够检测到变化,并且在数据源发生变化的时候,能够调度更新。当读取到外部状态发生了变化,会触发一个强制更新,来保证结果的一致性。
使用方法、参数说明:
1 | import { useSyncExternalStore } from 'react'; |
- subscribe:为订阅函数,当数据改变的时候,会触发 subscribe,在 useSyncExternalStore 会通过带有记忆性的 getSnapshot 来判别数据是否发生变化,如果发生变化,那么会强制更新数据。
- getSnapshot:可以理解成一个带有记忆功能的选择器。当 store 变化的时候,会通过 getSnapshot 生成新的状态值,这个状态值可提供给组件作为数据源使用,getSnapshot 可以检查订阅的值是否改变,改变的话那么会触发更新。
- getServerSnapshot:用于 hydration 模式下的 getSnapshot。
useSyncExternalStore 例子:
1 | import { combineReducers , createStore } from 'redux' |
点击按钮,会触发 reducer ,然后会触发 store.subscribe 订阅函数,执行 getSnapshot 得到新的 number ,判断 number 是否发生变化,如果变化,触发更新。
2.4 useTransition(v18新增)
在 React v18 中,有一种并发模式(concurrent),concurrent模式允许将UI更新标记为高优先级的或者可中断的低优先级操作。而useTransition()方法可以将某些更新标记为可中断的和非紧急的-也就是所谓的transitions。这种新特性在大量的UI更新操作中尤其有效,比如过滤一个较大的列表。打个比方如下图当点击 tab 从 tab1 切换到 tab2 的时候,本质上产生了两个更新任务。
- 第一个就是 hover 状态由 tab1 变成 tab2。
- 第二个就是内容区域由 tab1 内容变换到 tab2 内容。
这两个任务,用户肯定希望 hover 状态的响应更迅速,而内容的响应有可能还需要请求数据等操作,所以更新状态并不是立马生效,通常还会有一些 loading 效果。所以第一个任务作为立即执行任务,而第二个任务就可以视为过渡任务。
useTransition使用方法、参数说明:
1 | import { useTransition } from 'react' |
- isPending:Boolean值,表示处于过渡状态的标志,指明这个
transition正在加载中(pending) - startTransition:设置过度的方法,这个方法接收一个函数作为参数,在这个参数函数里面执行一些地任务行为
useTransition 例子:
1 | /* 模拟数据 */ |
2.5 useDeferredValue(v18新增)
官方说明:https://zh-hans.react.dev/reference/react/useDeferredValue
useDeferredValue用来接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。由于React 18 将UI更新标记为高优先级的或者可中断的低优先级操作。所以我们可以在高优先级任务结束后,再得到新的状态,而这个新的状态就称之为 DeferredValue。
useDeferredValue使用方法、参数说明:
1 | import { useDeferredValue } from 'react'; |
- value:你想延迟的值,可以是任何类型。
- initialValue:可选值,组件初始渲染时使用的值。如果省略此选项,
useDeferredValue在初始渲染期间不会延迟,因为没有以前的版本可以渲染。 - deferrredValue:延迟的值
注意事项:
- 当更新发生在 Transition 内部时,
useDeferredValue总是返回新的value并且不会产生延迟渲染,因为该更新已经被延迟了。 - 传递给
useDeferredValue的值应该是原始值(如字符串和数字)或是在渲染之外创建的对象。如果你在渲染期间创建一个新对象并立即将其传递给useDeferredValue,它在每次渲染时都会不同,从而导致不必要的后台重新渲染。 useDeferredValue本身不会引起任何固定的延迟。一旦 React 完成原始的重新渲染,它会立即开始使用新的延迟值处理后台重新渲染。由事件(例如输入)引起的任何更新都会中断后台重新渲染,并被优先处理。- 由
useDeferredValue引起的后台重新渲染在提交到屏幕之前不会触发 Effect。如果后台重新渲染被暂停,Effect 将在数据加载后和 UI 更新后运行。
用法:
- 1、在新内容加载期间显示旧内容
- 2、延迟渲染 UI 的某些部分
useDeferredValue 例子:
1 | import React from "react" |
1 | import React, { memo, useState, useDeferredValue } from 'react'; |
3. 执行副作用
React hooks也提供了 api ,用于弥补函数组件没有生命周期的缺陷。其本质主要是运用了 hooks 里面的 useEffect , useLayoutEffect,还有 useInsertionEffect。其中最常用的就是 useEffect 。
3.1 useEffect
1 | import { useEffect } from 'react'; |
- setup:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。
- dependencies:可选参数,数组,
setup代码中引用的所有响应式值的列表。响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数。依赖项里面的值改变,执行上一次setup 返回的 清理(cleanup) 函数,和执行新的 effect 第一个参数 setup 。React 将使用Object.is来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行 Effect 函数。
1 | import React, { useEffect, useRef, useState } from "react" |
常见问题
useState 和 useReducer 该怎么选
状态简单、更新逻辑分散就用 useState;状态有多个字段且更新方式固定成几种动作,用 useReducer 把逻辑收拢到一个 reducer 里更清晰,也更容易测试。
useEffect 和 useLayoutEffect 有什么区别
useEffect 在浏览器绘制之后异步执行,不阻塞渲染;useLayoutEffect 在 DOM 变更后、绘制之前同步执行,会阻塞渲染。需要读取布局信息并同步改 DOM 以避免闪烁时才用 useLayoutEffect,其余场景一律用 useEffect。
useTransition 是做什么的
React 18 新增,用来把某些状态更新标记为低优先级。被标记的更新在有更紧急的交互(比如输入)时可以被打断和延后,避免大列表渲染卡住输入框的响应。







