常见的 React hooks 有哪些
前言
React Hook
是 React 16.8
推出的新特性。它可以让你在不编写 class
的情况下使用 state
以及其他的 React
特性。没有计划从 React
中移除 class
。
为什么要有 Hook?
官网 对于动机的解释。
在组件之间复用状态逻辑很难:
可能需要
render props
和高阶组件(HOC
),但是会形成会形成嵌套地狱,而hooks
支持从组件状态中提取状态逻辑,进行单独的测试和复用,很好的解决这个问题。复杂组件变得难以理解:
类组件中,可能需要在不同的生命周期中执行一些逻辑,如事件监听/销毁等需要在不同的生命周期中定义,
componentDidMount/componentWillUnMount
中执行,意味着会讲统一逻辑在不同生命周期中去拆分。但是在hooks
中可以直接在useEffect
中定义。难以理解的
class
:this 指向问题,类组件中太多对
this
的使用,如this.onCLick.bind(this)
等,容易忘记绑定事件处理器。class
不能很好的压缩导致热重载不稳定。
高阶组件 HOC
HOC
的原理其实很简单,它就是一个函数,且它接受一个组件作为参数,并返回一个新的组件,把复用的地方放在高阶组件中,你在使用的时候,只需要在告诫组件内部做不同逻辑处理。
但是容易导致的问题:HOC
的用处不单单是代码复用,还可以做权限控制、打印日志等。但它有也缺陷,例如 HOC
是在原组件上进行包裹或者嵌套,如果大量使用 HOC
,将会产生非常多的嵌套,这会让调试变得非常困难;
而且 HOC
可以劫持 props
,在不遵守约定的情况下可能造成冲突。
Hook 的设计目标
解决类组件遇到的问题,也就是上面官网的解释,为了解决这三个问题,hooks
应该具备的优点:
- 复用状态逻辑,支持自定义
hooks
- 无生命周期的困扰
- 无
Class
的复杂性,写法简单 - 对其
Class
组件已经具备的能力
内置 hook
类型 | Hook | 描述 |
---|---|---|
State Hook
|
useState ⭐️ | 状态:让函数组件具有维持状态的能力。 |
useReducer ⭐️ | 状态:useReducer 是 useState 的一个替代方案,用于在函数组件中管理更复杂的状态逻辑。 | |
Effect Hook
|
useEffect ⭐️ | 执行副作用:允许将组件与外部系统同步,比如数据获取、订阅、手动操作 DOM、定时器等。它通常会在组件渲染后执行。 |
useLayouEffect | 布局副作用:useLayoutEffect 是 useEffect 的一个变换版本,在浏览器重新绘制屏幕之前触发在浏览器重新绘制屏幕前执行。useLayoutEffect 可能会影响性能。 | |
useInsertionEffect | 【React 18 新增】在 React 对 DOM 进行更改之前触发,库可以在此处插入动态 CSS。 | |
Context Hook
|
useContext ⭐️ | 上下文:从祖先组件接收信息,而无需将其作为 props 传递。 |
Ref Hook
|
useRef ⭐️ | ref 允许组件 保存一些不用于渲染的信息,比如 DOM 节点或 timeout ID。 |
useImpreveHandle ⭐️ | 自定义从组件中暴露的 ref handle,如向上暴露子节点的函数和属性。 | |
性能 Hook
|
useMemo ⭐️ | 缓存:每次重新渲染时都能缓存计算的结果。 |
useCallback ⭐️ | 缓存:允许你在多次渲染中缓存函数。 | |
useTransition | 【React 18 新增】帮助你在不阻塞 UI 的情况下更新状态 | |
useDeferredValue | 【React 18 新增】允许你延迟更新 UI 的某些部分,以让其他部分先更新。 | |
其他 Hook
|
useDebugValue | 自定义在 React 开发者工具中显示的调试信息。 |
useId | 【React 18 新增】用于生成唯一的 ID 标识符,特别适合在无状态的函数组件中为 DOM 元素创建唯一的 ID。 | |
useSyncExternalStore | 【React 18 新增】主要用于订阅外部存储(例如 Redux 或其他全局状态管理库)并同步更新组件状态。这个 Hook 解决了在严格模式和并发渲染模式下状态不一致的问题,保证状态始终保持同步。 | |
useActionState | 可以根据某个表单动作的结果更新 state 的 Hook。 |
useState
useState
是一个 React Hook
,它允许你向组件添加一个 状态变量
。
1 | function Counter() { |
特点
- 如果
state
是一个对象,不可局部更新,只能全量替换,如通过展开运算符(...
)。 - 调用
setState
一直会创建新的对象和数组,因为内存地址要变。 useState
和setState
都接受函数,如setState(pre => pre + 1)
。- 自动批处理,且都是异步的。
自动批处理
在
18
以前,只有在React
事件处理函数(onChange
、onCLick
)中进行会批处理(异步)。默认情况下promise
,setTimeout
、DOM
原生处理函数(this.click
) 都不会进行批处理(同步,这些事件发生在 React 调度流程之外,不会触发批处理更新机制)。React 18
引入了并发渲染的支持,自动批处理。
同步还是异步
- 批处理与同步异步是两个不同的概念。
- 通常而言在
17
之前,有同步有异步分情况。不过18
之后,都是异步的了。 - 虽然说
setState
在某些情况下是异步的,但实际上它并不是真正意义上的异步,而只是批量更新的一种优化手段。
useReducer
useReducer
是 React
提供的一个 Hook
,用于在函数组件中管理更复杂的状态逻辑。
它是 useState
的一个替代方案,适用于那些有多个子值或者更新逻辑较为复杂的状态。通常情况下,useReducer
被用于管理需要根据不同的动作(actions
)来更新的状态,比如你会看到它在 Redux
这样的全局状态管理库中被广泛使用。
基本用法
1 | const [state, dispatch] = useReducer(reducer, initialState); |
useReducer
接受两个参数:reducer
函数:一个纯函数,定义了如何根据action
来更新state
。initialState
:初始状态值。
useReducer
返回两个值:state
:当前的状态值。dispatch
:一个函数,用于发送action
来更新状态。
state
是当前的状态。dispatch
是一个函数,用来派发action
来触发状态更新。
总结起来一句话:我们使用 dispatch
来触发 reducer
纯函数,用 reducer
纯函数中的逻辑修改 initialState
,得到一个新的变量,把这个变量赋值给 state
,最终返回。
1 | import { useReducer } from 'react'; |
看起来很像 redux
吧?
其实,useReducer
是 React
的一个 hook
,通常用于局部组件状态管理,而 Redux
通常用于跨组件或应用级别的状态管理。
使用场景
useReducer
是一个轻量级的状态管理工具,只适用于局部组件(单组件)状态管理,不支持跨组件共享状态。- 在需要管理大量数据的场景中,使用
useReducer
更加合适。
useEffect
useEffect
是一个 React Hook
,用于在函数组件中执行副作用操作(side effects
)。副作用指的是那些对外部世界有影响的操作,比如数据获取、订阅、手动操作 DOM
、定时器等。它通常会在组件渲染后执行。
1 | useEffect(setup, dependencies?) |
setup
:处理Effect
的函数。dependencies
:可选依赖项,它控制副作用的触发时机。useEffect
会在依赖项发生变化时执行副作用。
特点
useEffect
默认会在组件每次渲染后执行。- 如果同时存在多个
useEffect
,会按照出现次序执行。 - 可以返回一个清理函数,用于在组件卸载或依赖项更新时清理副作用。
1 | useEffect(() => { |
useContext
useContext
是一个 React Hook
,可以让你读取和订阅组件中的上下文 context
。它使得你能够在组件树中跨层级轻松共享数据,而不需要通过逐层传递 props
。
1 | const value = useContext(ThemeContext) |
基本用法
- 创建 Context: 使用 React.createContext 创建一个上下文对象。
- 提供 Context: 使用 Context.Provider 组件提供上下文的值,通常在组件树的顶层提供。
- 消费 Context: 在需要访问上下文值的组件中,使用 useContext 来获取该值。
1 | import React, { createContext, useContext, useState } from 'react'; |
useRef
Ref
提供了一种方式,允许我们访问 DOM
节点或在 render
方法中创建 React
元素。
useRef
返回一个具有单个 current
属性 的 ref
对象,并初始化为你提供的 初始值。在后续的渲染中,useRef
将返回相同的对象。你可以改变它的 current
属性来存储信息,并在之后读取它。
- 与
state
的区别?
React Hooks
的本质是闭包,闭包是存在内存中的,使用 useRef
,可以不通过内存来保存数据,使得这些数据在重渲染时不会被清除。
当改变 ref.current
属性时,React 不会重新渲染组件,但是 state
会触发渲染。
使用场景
- 缓存值: 使用
ref
缓存一个值或实例对象 - 操作
DOM
:使用ref
操作 DOM - 获取子组件实例
缓存值
比如创建一个定时器
1 | function handleStartClick() { |
在之后,从 ref
中读取 interval ID
便可以 清除定时器:
1 | function clear() { |
操作 DOM
使用 ref
操作 DOM
是非常常见的行为。React
内置了对它的支持。
1 | import { useRef } from 'react'; |
当 React
创建 DOM
节点并将其渲染到屏幕时,React
将会把 DOM
节点设置为 ref
对象的 current
属性。现在可以借助 ref
对象访问 <input>
的 DOM
节点,并访问节点的属性和事件。如获取 inputRef.current.focus()
;
当组件销毁时,React
会自动将 ref
对象的 current
属性设置为 null
。这是 React
的内置机制,确保在组件卸载或节点不再渲染时,引用不会保留对失效 DOM
元素的引用。
获取子组件实例
- 类组件:直接绑定
ref
,就能拿到整个子组件的实例对象。
1 | class Child extends Component {} |
- 函数组件:结合需要
forwardRef
+ useImperativeHandle。
1 | import { forwardRef, useImperativeHandle } from 'react'; |
注
:如果没有通过 forwardRef
包裹,将在控制台得倒错误提示。
useImpreveHandle
useImperativeHandle
是 React
中的一个 Hook
,它能让你自定义作为 ref
暴露出来的方法或属性。
1 | useImperativeHandle(ref, createHandle, dependencies?) |
ref
: 从forwardRef
渲染函数 中获得的第二个参数。createHandle
:该函数无需参数,它返回你想要暴露的ref
实例。该实例可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。- 可选的
dependencies
:依赖更新时将重新生成实例分配给ref
。
一个完整的例子:
1 | import { forwardRef, useRef, useImperativeHandle } from 'react'; |
默认情况下,组件不会将它们的 DOM
节点暴露给父组件。如果你想要 MyInput 的父组件能访问到 <input> DOM
节点,你必须选择使用 forwardRef
。
useMemo
useMemo
是一个 React Hook
,它在每次重新渲染的时候能够缓存计算的结果。
1 | const cachedValue = useMemo(calculateValue, dependencies) |
calculateValue
:要缓存计算值的函数。dependencies
:依赖项,依赖发生改变时重新缓存计算值。
跳过代价昂贵的重新计算
1 | import { useMemo } from 'react'; |
跳过组件的重新渲染
如果 List
的所有 props
都与上次渲染时相同,则 List
将跳过重新渲染。
1 | import { memo } from 'react'; |
useMemo 和 memo 的区别
memo()
是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的props
发生变化。useMemo()
是一个React Hook
,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算。
useCallback
useCallback
是一个允许你在多次渲染中缓存函数的 React Hook
。
1 | const cachedFn = useCallback(fn, dependencies) |
fn
:想要缓存的函数。dependencies
:依赖项,依赖发生改变时缓存函数。
它和 useMemo
出自一脉,useCallback( x => log(x), [m])
等价于 useMemo(() => x => log(x), [m])
。
跳过组件的重新渲染
1 | import { useCallback } from 'react'; |
useCallback 与 useMemo 有何区别
useCallback
其实是 useMemo
的另一种实现,如果 useMemo
是个值还好说,如果是返回函数的函数,如 useMmeo(()=>(x) => console.log(x))
不仅难用,而且难以理解,于是 React
团队就写了语法糖 —— useCallback
。
useMemo
缓存函数调用的结果。useCallback
缓存函数本身
自定义 Hook
自定义 Hook
是一种复用 React
逻辑的方式,允许你将组件中常见的逻辑提取到一个函数中,然后在不同的组件中重用它。自定义 Hook
以 use
开头,遵循 React
的 Hook
规则,可以像内置 Hook
一样在函数组件中使用。
特点:
Hook
的名称必须永远以use
开头。- 自定义
Hook
共享的是状态逻辑,而不是状态本身。
防抖 useDebounce
1 | import { useState, useEffect } from 'react'; |
节流 useThrottle
1 | import { useRef, useEffect } from 'react'; |
更多自定义 Hooks
见 自定义 Hook