常见的 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 不能很好的压缩导致热重载不稳定。
高阶组件 HOCHOC 的原理其实很简单,它就是一个函数,且它接受一个组件作为参数,并返回一个新的组件,把复用的地方放在高阶组件中,你在使用的时候,只需要在告诫组件内部做不同逻辑处理。
但是容易导致的问题: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。
useStateuseState 是一个 React Hook,它允许你向组件添加一个 状态变量。
12345678910function Counter() { const [count, setCount] = useState(0); return (
You clicked {count} times
特点
如果 state 是一个对象,不可局部更新,只能全量替换,如通过展开运算符(...)。
调用 setState 一直会创建新的对象和数组,因为内存地址要变。
useState 和 setState 都接受函数,如 setState(pre => pre + 1)。
自动批处理,且都是异步的。
自动批处理
在 18 以前,只有在 React 事件处理函数(onChange、onCLick)中进行会批处理(异步)。默认情况下 promise,setTimeout、DOM 原生处理函数(this.click) 都不会进行批处理(同步,这些事件发生在 React 调度流程之外,不会触发批处理更新机制)。
React 18 引入了并发渲染的支持,自动批处理。
同步还是异步
批处理与同步异步是两个不同的概念。
通常而言在 17 之前,有同步有异步分情况。不过 18 之后,都是异步的了。
虽然说 setState 在某些情况下是异步的,但实际上它并不是真正意义上的异步,而只是批量更新的一种优化手段。
useReduceruseReducer 是 React 提供的一个 Hook,用于在函数组件中管理更复杂的状态逻辑。
它是 useState 的一个替代方案,适用于那些有多个子值或者更新逻辑较为复杂的状态。通常情况下,useReducer 被用于管理需要根据不同的动作(actions)来更新的状态,比如你会看到它在 Redux 这样的全局状态管理库中被广泛使用。
基本用法1const [state, dispatch] = useReducer(reducer, initialState);
useReducer 接受两个参数:
reducer 函数:一个纯函数,定义了如何根据 action 来更新 state。
initialState:初始状态值。
useReducer 返回两个值:
state:当前的状态值。
dispatch:一个函数,用于发送 action 来更新状态。
state 是当前的状态。
dispatch 是一个函数,用来派发 action 来触发状态更新。
总结起来一句话:我们使用 dispatch 来触发 reducer 纯函数,用 reducer 纯函数中的逻辑修改 initialState,得到一个新的变量,把这个变量赋值给 state,最终返回。
1234567891011121314151617181920212223242526272829303132333435363738import { useReducer } from 'react';function reducer(state, action) { switch (action.type) { case 'incremented_age': { return { age: state.age + 1 }; } case 'decremented_age': { return { age: state.age - 1 }; } } throw Error('Unknown action.');}export default function Counter() { const [state, dispatch] = useReducer(reducer, { age: 42 }); return ( <>
Hello! You are {state.age}.
> );}看起来很像 redux 吧?
其实,useReducer 是 React 的一个 hook,通常用于局部组件状态管理,而 Redux 通常用于跨组件或应用级别的状态管理。
使用场景
useReducer 是一个轻量级的状态管理工具,只适用于局部组件(单组件)状态管理,不支持跨组件共享状态。
在需要管理大量数据的场景中,使用 useReducer 更加合适。
useEffectuseEffect 是一个 React Hook,用于在函数组件中执行副作用操作(side effects)。副作用指的是那些对外部世界有影响的操作,比如数据获取、订阅、手动操作 DOM、定时器等。它通常会在组件渲染后执行。
1useEffect(setup, dependencies?)
setup:处理 Effect 的函数。
dependencies:可选依赖项,它控制副作用的触发时机。useEffect 会在依赖项发生变化时执行副作用。
特点
useEffect 默认会在组件每次渲染后执行。
如果同时存在多个 useEffect,会按照出现次序执行。
可以返回一个清理函数,用于在组件卸载或依赖项更新时清理副作用。
12345678910useEffect(() => { const timer = setInterval(() => { console.log('Count:', count); }, 1000); // 返回清理函数 return () => { clearInterval(timer); // 清理定时器 };}, [count]); // 每次 count 变化时重新设置定时器
useContextuseContext 是一个 React Hook,可以让你读取和订阅组件中的上下文 context。它使得你能够在组件树中跨层级轻松共享数据,而不需要通过逐层传递 props。
1const value = useContext(ThemeContext)
基本用法
创建 Context: 使用 React.createContext 创建一个上下文对象。
提供 Context: 使用 Context.Provider 组件提供上下文的值,通常在组件树的顶层提供。
消费 Context: 在需要访问上下文值的组件中,使用 useContext 来获取该值。
123456789101112131415161718192021222324252627282930import React, { createContext, useContext, useState } from 'react';// 创建 Contextconst ThemeContext = createContext();// 提供 Contextfunction App() { const [color, setColor] = useState('#123456'); return ( // 使用 Provider 提供 Context 的值
Current color: {color}
useRefRef 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建 React 元素。
useRef 返回一个具有单个 current 属性 的 ref 对象,并初始化为你提供的 初始值。在后续的渲染中,useRef 将返回相同的对象。你可以改变它的 current 属性来存储信息,并在之后读取它。
与 state 的区别?
React Hooks 的本质是闭包,闭包是存在内存中的,使用 useRef,可以不通过内存来保存数据,使得这些数据在重渲染时不会被清除。
当改变 ref.current 属性时,React 不会重新渲染组件,但是 state 会触发渲染。
使用场景
缓存值: 使用 ref 缓存一个值或实例对象
操作 DOM:使用 ref 操作 DOM
获取子组件实例
缓存值比如创建一个定时器
123456function handleStartClick() { const intervalId = setInterval(() => { // ... }, 1000); intervalRef.current = intervalId;}
在之后,从 ref 中读取 interval ID 便可以 清除定时器:
1234function clear() { const intervalId = intervalRef.current; clearInterval(intervalId);}
操作 DOM使用 ref 操作 DOM 是非常常见的行为。React 内置了对它的支持。
123456import { useRef } from 'react';function MyComponent() { const inputRef = useRef(null); return ;}
当 React 创建 DOM 节点并将其渲染到屏幕时,React 将会把 DOM 节点设置为 ref 对象的 current 属性。现在可以借助 ref 对象访问 的 DOM 节点,并访问节点的属性和事件。如获取 inputRef.current.focus();
当组件销毁时,React 会自动将 ref 对象的 current 属性设置为 null。这是 React 的内置机制,确保在组件卸载或节点不再渲染时,引用不会保留对失效 DOM 元素的引用。
获取子组件实例
类组件:直接绑定 ref,就能拿到整个子组件的实例对象。
123456class Child extends Component {}const App = () => { const ref = useRef() return
函数组件:结合需要 forwardRef + useImperativeHandle。
12345678910import { forwardRef, useImperativeHandle } from 'react';const Child = (props, ref) => { useImperativeHandle(ref, () => { // 返回要绑定的实例对象 return {}; }, []);}const App = forwardRef(Child);
注:如果没有通过 forwardRef 包裹,将在控制台得倒错误提示。
useImpreveHandleuseImperativeHandle 是 React 中的一个 Hook,它能让你自定义作为 ref 暴露出来的方法或属性。
1useImperativeHandle(ref, createHandle, dependencies?)
ref: 从 forwardRef 渲染函数 中获得的第二个参数。
createHandle:该函数无需参数,它返回你想要暴露的 ref 实例。该实例可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。
可选的 dependencies:依赖更新时将重新生成实例分配给 ref。
一个完整的例子:
123456789101112131415161718import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => { return { focus() { inputRef.current.focus(); }, scrollIntoView() { inputRef.current.scrollIntoView(); }, }; }, []); return ;});
默认情况下,组件不会将它们的 DOM 节点暴露给父组件。如果你想要 MyInput 的父组件能访问到 DOM 节点,你必须选择使用 forwardRef。
useMemouseMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
1const cachedValue = useMemo(calculateValue, dependencies)
calculateValue:要缓存计算值的函数。
dependencies:依赖项,依赖发生改变时重新缓存计算值。
跳过代价昂贵的重新计算123456import { useMemo } from 'react';function TodoList({ todos, tab, theme }) { const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); // ...}
跳过组件的重新渲染如果 List 的所有 props 都与上次渲染时相同,则 List 将跳过重新渲染。
12345import { memo } from 'react';const List = memo(function List({ items }) { // ...});
useMemo 和 memo 的区别
memo() 是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的 props 发生变化。
useMemo() 是一个 React Hook,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算。
useCallbackuseCallback 是一个允许你在多次渲染中缓存函数的 React Hook。
12const cachedFn = useCallback(fn, dependencies)
fn:想要缓存的函数。
dependencies:依赖项,依赖发生改变时缓存函数。
它和 useMemo 出自一脉,useCallback( x => log(x), [m]) 等价于 useMemo(() => x => log(x), [m])。
跳过组件的重新渲染1234567891011import { useCallback } from 'react';function ProductPage({ productId, referrer, theme }) { const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails, }); }, [productId, referrer]); // ...}
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 共享的是状态逻辑,而不是状态本身。
防抖 useDebounce12345678910111213141516171819202122232425262728293031323334import { useState, useEffect } from 'react';const useDebounce = (stateValue, wait) => { const [val, setVal] = useState(stateValue) useEffect(() => { const timer = setTimeout(() => { setVal(stateValue) }, wait) return () => { clearTimeout(timer) } }, [stateValue, wait]) return val;}const MyComponent = () => { const [value, setValue] = useState DebouncedValue: {debouncedValue}
节流 useThrottle12345678910111213141516171819202122232425262728293031323334import { useRef, useEffect } from 'react';const useThrootle = (func, wait) => { const lastCallRef = useRef(0); const throttledFunction = (...args) => { const now = Date.now(); if (now - lastCallRef.current >= wait) { lastCallRef.current = now func(...args) } } return throttledFunction}// 使用const MyComponent = () => { const handleScroll = useThrottle(() => { console.log('Scroll event triggered!'); }, 1000); // 设置 1 秒的节流 useEffect(() => { window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, [handleScroll]); return (
Scroll to see throttled events in action!
更多自定义 Hooks 见 自定义 Hook