React 的常用 Hooks (including React 18 后的 Hooks)

React 的常用 Hooks (including React 18 后的 Hooks)

React Hooks 自 16.8 版本引入以来,彻底改变了函数组件的能力,使其能够管理状态、处理副作用和访问 React 特性。本文将详细介绍 React 中常用的 Hooks,包括基础必备 Hooks、性能优化 Hooks、上下文相关 Hooks,以及 React 18 之后推出的新特性 Hooks,帮助开发者掌握它们的使用场景和实现方式。

useState:函数组件的状态管理基础

作用

useState 是 React 中最基础的 Hook,用于在函数组件中添加状态(state)。它允许组件在渲染之间保存数据,并在数据变化时触发重新渲染。

使用场景

任何需要在组件中维护内部状态的场景,例如:表单输入值、开关状态(如模态框显示/隐藏)、计数器数值等简单状态管理。

代码示例

javascript

复制代码

import { useState } from 'react'

function Counter() {

// 声明一个状态变量 count,初始值为 0,setCount 是更新 count 的函数

const [count, setCount] = useState(0)

return (

当前计数: {count}

)

}

export default Counter

代码解释:useState 接收初始状态作为参数(这里是 0),返回一个数组,第一个元素是当前状态值(count),第二个元素是更新状态的函数(setCount)。调用 setCount 时,React 会重新渲染组件,并使用新的 count 值。

useEffect:副作用处理与生命周期模拟

作用

useEffect 用于处理组件中的"副作用"操作,例如数据获取、订阅事件、DOM 操作等。它可以模拟类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。

使用场景

组件挂载后执行一次性操作(如初始化数据获取)

依赖项变化时执行操作(如根据 props 变化重新获取数据)

清理副作用(如取消订阅、清除定时器)

代码示例

基础用法:模拟 componentDidMount(仅挂载时执行)

javascript

复制代码

import { useState, useEffect } from 'react'

function UserProfile({ userId }) {

const [user, setUser] = useState(null)

// 仅在组件挂载时执行一次(依赖数组为空)

useEffect(() => {

console.log('组件挂载完成')

// 获取用户数据

fetch(`https://api.example.com/users/${userId}`)

.then(res => res.json())

.then(data => setUser(data))

}, []) // 空依赖数组:仅在组件挂载时执行

if (!user) return

加载中...

return (

{user.name}

邮箱: {user.email}

)

}

带依赖项:模拟 componentDidUpdate(依赖变化时执行)

javascript

复制代码

// 接上面的 UserProfile 组件,修改 useEffect 依赖项

useEffect(() => {

console.log(`userId 变化为 ${userId},重新获取数据`)

fetch(`https://api.example.com/users/${userId}`)

.then(res => res.json())

.then(data => setUser(data))

}, [userId]) // 依赖数组包含 userId:userId 变化时执行

清理副作用:模拟 componentWillUnmount

javascript

复制代码

function TimerComponent() {

const [time, setTime] = useState(0)

useEffect(() => {

// 组件挂载时启动定时器

const timer = setInterval(() => {

setTime(prevTime => prevTime + 1)

}, 1000)

// 返回清理函数:组件卸载或依赖变化前执行

return () => {

console.log('清理定时器')

clearInterval(timer)

}

}, []) // 空依赖:仅挂载时启动,卸载时清理

return

已运行: {time} 秒

}

useRef:DOM 引用与持久化值存储

作用

useRef 主要有两个用途:1. 获取 DOM 元素的引用;2. 在组件渲染之间持久化存储一个值(该值变化不会触发组件重新渲染)。

使用场景

直接操作 DOM 元素(如聚焦输入框、获取元素尺寸)

存储不需要触发渲染的持久化数据(如定时器 ID、上一次渲染的状态值)

代码示例

用途 1:获取 DOM 元素引用

javascript

复制代码

import { useRef, useEffect } from 'react'

function InputFocus() {

// 创建一个 ref 对象

const inputRef = useRef(null)

useEffect(() => {

// 组件挂载后,让输入框自动聚焦

inputRef.current.focus()

}, [])

return (

ref={inputRef} // 将 ref 绑定到 DOM 元素

type="text"

placeholder="自动聚焦的输入框"

/>

)

}

用途 2:持久化存储值(不触发渲染)

javascript

复制代码

import { useRef, useState } from 'react'

function CounterWithPrev() {

const [count, setCount] = useState(0)

// 存储上一次的 count 值

const prevCountRef = useRef(null)

const handleIncrement = () => {

prevCountRef.current = count // 更新 ref 的 current 值(不会触发渲染)

setCount(count + 1)

}

return (

当前计数: {count}

上一次计数: {prevCountRef.current ?? '未记录'}

)

}

useReducer:复杂状态逻辑的管理

作用

useReducer 是 useState 的替代方案,用于管理包含多个子值的复杂状态逻辑,或当状态转换逻辑复杂且需要复用、预测时。它基于 Redux 的思想,通过"动作(action)"来描述状态变化,并使用" reducer 函数"来处理状态转换。

使用场景

状态逻辑复杂(如包含多个子状态,且状态更新依赖于前一个状态)

多个组件需要共享状态更新逻辑

需要预测和测试状态变化(reducer 是纯函数,输入相同则输出相同)

代码示例

计数器示例(基础用法)

javascript

复制代码

import { useReducer } from 'react'

// 定义 reducer 函数:接收当前状态和动作,返回新状态

function countReducer(state, action) {

switch (action.type) {

case 'INCREMENT':

return { ...state, count: state.count + 1 }

case 'DECREMENT':

return { ...state, count: state.count - 1 }

case 'RESET':

return { ...state, count: 0 }

default:

throw new Error(`未知动作类型: ${action.type}`)

}

}

function CounterWithReducer() {

// 初始化状态和 dispatch 函数

const [state, dispatch] = useReducer(countReducer, { count: 0 })

return (

当前计数: {state.count}

)

}

表单状态管理(复杂状态示例)

javascript

复制代码

import { useReducer } from 'react'

function formReducer(state, action) {

switch (action.type) {

case 'UPDATE_FIELD':

return {

...state,

[action.field]: action.value

}

case 'RESET_FORM':

return action.initialState

default:

return state

}

}

function LoginForm() {

const initialState = {

username: '',

password: ''

}

const [formState, dispatch] = useReducer(formReducer, initialState)

const handleSubmit = (e) => {

e.preventDefault()

console.log('提交表单:', formState)

}

return (

type="text"

value={formState.username}

onChange={(e) => dispatch({

type: 'UPDATE_FIELD',

field: 'username',

value: e.target.value

})}

/>

type="password"

value={formState.password}

onChange={(e) => dispatch({

type: 'UPDATE_FIELD',

field: 'password',

value: e.target.value

})}

/>

type="button"

onClick={() => dispatch({ type: 'RESET_FORM', initialState })}

>

重置

)

}

useContext:跨组件状态共享

作用

useContext 用于在函数组件中访问 React 的上下文(Context),实现跨层级组件间的数据共享,避免通过 props 逐层传递数据("prop drilling"问题)。

使用场景

多个组件需要访问同一数据(如用户信息、主题设置、语言偏好)

组件层级较深,通过 props 传递数据繁琐

非父子关系组件间的数据共享

代码示例

步骤 1:创建 Context

javascript

复制代码

// ThemeContext.js

import { createContext } from 'react'

// 创建上下文,可提供默认值

const ThemeContext = createContext('light')

export default ThemeContext

步骤 2:在父组件中提供 Context 值

javascript

复制代码

// App.js

import { useState } from 'react'

import ThemeContext from './ThemeContext'

import ThemedButton from './ThemedButton'

function App() {

const [theme, setTheme] = useState('light')

const toggleTheme = () => {

setTheme(prev => prev === 'light' ? 'dark' : 'light')

}

return (

// 使用 Provider 包裹需要访问 Context 的组件树

padding: '20px',

backgroundColor: theme === 'light' ? '#fff' : '#333',

color: theme === 'light' ? '#333' : '#fff'

}}>

当前主题: {theme}

)

}

步骤 3:在子组件中使用 useContext 访问 Context

csharp

复制代码

// ThemedButton.js

import { useContext } from 'react'

import ThemeContext from './ThemeContext'

function ThemedButton() {

// 使用 useContext 获取 ThemeContext 的值

const theme = useContext(ThemeContext)

return (

)

}

export default ThemedButton

useMemo:计算结果的缓存与性能优化

作用

useMemo 用于缓存"昂贵计算"的结果,避免在每次组件渲染时重复执行这些计算,从而优化性能。它接收一个计算函数和依赖数组,只有当依赖项发生变化时,才会重新执行计算函数并更新缓存结果。

使用场景

执行昂贵的计算操作(如大数据排序、复杂数据转换)

避免在每次渲染时创建新的对象/数组(导致子组件不必要的重渲染)

代码示例

基础用法:缓存昂贵计算结果

javascript

复制代码

import { useState, useMemo } from 'react'

function ExpensiveCalculation() {

const [numbers] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

const [multiplier, setMultiplier] = useState(1)

// 昂贵的计算:将数组中所有数字乘以 multiplier 并求和

const calculateTotal = () => {

console.log('执行计算...') // 用于观察计算是否执行

return numbers.reduce((sum, num) => sum + num * multiplier, 0)

}

// 使用 useMemo 缓存计算结果,仅当 multiplier 变化时重新计算

const total = useMemo(calculateTotal, [multiplier])

return (

乘数: {multiplier}

计算结果: {total}

)

}

进阶用法:避免创建新对象导致子组件重渲染

javascript

复制代码

import { useState, useMemo } from 'react'

// 子组件:接收 user 对象作为 props

const UserCard = ({ user }) => {

console.log(`UserCard 渲染: ${user.name}`) // 观察是否重渲染

return (

{user.name}

年龄: {user.age}

)

}

// 父组件

function UserProfile() {

const [name, setName] = useState('张三')

const [age, setAge] = useState(25)

const [count, setCount] = useState(0)

// 使用 useMemo:仅当 name 或 age 变化时才创建新对象

const user = useMemo(() => ({ name, age }), [name, age])

return (

type="text"

value={name}

onChange={(e) => setName(e.target.value)}

placeholder="修改姓名"

/>

无关计数: {count}

)

}

解释:当使用 useMemo 后,只有 name 或 age 变化时,user 对象才会更新,避免了因 count 变化导致的 UserCard 不必要重渲染。

useCallback:函数引用的缓存与性能优化

作用

useCallback 用于缓存函数的引用,避免在每次组件渲染时创建新的函数实例。它通常与 React.memo 配合使用,防止因函数 props 变化导致子组件不必要的重渲染。

使用场景

将函数作为 props 传递给子组件,且子组件使用 React.memo 优化

函数作为 useEffect 的依赖项,避免因函数引用变化导致副作用重复执行

代码示例

基础用法:避免子组件因函数 props 变化重渲染

javascript

复制代码

import { useState, useCallback, memo } from 'react'

// 使用 memo 包装子组件,仅当 props 浅变化时才重渲染

const ActionButton = memo(({ onClick, label }) => {

console.log(`ActionButton "${label}" 渲染`)

return

})

function ParentComponent() {

const [count, setCount] = useState(0)

// 使用 useCallback 缓存函数引用,仅当依赖变化时才创建新函数

const handleIncrement = useCallback(() => {

setCount(c => c + 1)

}, []) // 空依赖:函数引用永久不变

return (

计数: {count}

setCount(0)} label="重置" />

)

}

进阶用法:作为 useEffect 依赖项

javascript

复制代码

import { useState, useCallback, useEffect } from 'react'

function DataFetcher({ userId }) {

const [data, setData] = useState(null)

// 使用 useCallback 缓存 fetchData 函数

const fetchData = useCallback(async () => {

const response = await fetch(`https://api.example.com/users/${userId}`)

const result = await response.json()

setData(result)

}, [userId]) // 仅当 userId 变化时,函数引用才更新

// 依赖 fetchData 函数,但由于 useCallback 缓存,仅在 userId 变化时执行

useEffect(() => {

fetchData()

}, [fetchData])

if (!data) return

加载中...

return

用户名: {data.name}

}

useLayoutEffect:DOM 更新后的同步操作

作用

useLayoutEffect 与 useEffect 功能类似,但执行时机不同:它在 DOM 更新后同步执行 (阻塞浏览器绘制),而 useEffect 在 DOM 更新后异步执行(不阻塞绘制)。

使用场景

需要读取 DOM 布局并立即执行操作(如测量元素尺寸后调整位置)

避免因异步执行导致的视觉闪烁(如模态框定位计算)

代码示例

javascript

复制代码

import { useRef, useLayoutEffect, useState } from 'react'

function Tooltip() {

const [position, setPosition] = useState({ top: 0, left: 0 })

const targetRef = useRef(null)

const tooltipRef = useRef(null)

useLayoutEffect(() => {

if (!targetRef.current || !tooltipRef.current) return

// 读取 DOM 布局信息(同步执行)

const targetRect = targetRef.current.getBoundingClientRect()

const tooltipRect = tooltipRef.current.getBoundingClientRect()

// 计算 tooltip 位置(避免溢出视口)

const top = targetRect.bottom + window.scrollY + 5

const left = targetRect.left + window.scrollX - (tooltipRect.width - targetRect.width) / 2

setPosition({ top, left })

}, []) // 组件挂载后计算一次位置

return (

ref={tooltipRef}

style={{

position: 'absolute',

top: position.top,

left: position.left,

background: '#333',

color: 'white',

padding: '4px 8px',

borderRadius: '4px'

}}

>

这是提示内容

)

}

React 18 新增 Hooks

useId:唯一 ID 生成器

作用

useId 用于生成跨服务端和客户端的 唯一且稳定的 ID ,解决 SSR(服务端渲染)中的" hydration 不匹配"问题。它生成的 ID 以 : 开头,确保全局唯一性。

使用场景

为表单元素生成 id 和 htmlFor 属性(关联 label 和 input)

为无障碍(a11y)属性生成唯一标识符(如 aria-labelledby)

避免手动生成 ID 导致的 SSR 不匹配问题

代码示例

javascript

复制代码

import { useId } from 'react'

function FormInput() {

// 生成唯一 ID

const inputId = useId()

// 可基于基础 ID 生成关联 ID

const errorId = `${inputId}-error`

return (

id={inputId}

type="text"

aria-describedby={errorId} // 关联错误提示

/>

用户名不能为空

)

}

useTransition:非阻塞状态更新

作用

useTransition 允许将某些状态更新标记为"非紧急",优先保证 UI 响应性。React 会优先处理紧急更新(如输入框输入),延迟处理非紧急更新(如大型列表过滤),避免页面卡顿。

使用场景

大型列表过滤或排序(数据量大时避免阻塞 UI)

复杂状态计算(不影响用户即时交互的操作)

表单提交前的预验证(不阻塞用户输入)

代码示例

javascript

复制代码

import { useState, useTransition } from 'react'

function SearchList({ items }) {

const [query, setQuery] = useState('')

const [filteredItems, setFilteredItems] = useState(items)

// isPending: 标记过渡是否进行中;startTransition: 包装非紧急更新

const [isPending, startTransition] = useTransition()

const handleChange = (e) => {

// 紧急更新:立即更新输入框值(用户能感知的交互)

setQuery(e.target.value)

// 非紧急更新:标记为过渡,React 会在空闲时执行

startTransition(() => {

// 过滤大型列表(可能耗时)

const result = items.filter(item =>

item.name.toLowerCase().includes(query.toLowerCase())

)

setFilteredItems(result)

})

}

return (

type="text"

value={query}

onChange={handleChange}

placeholder="搜索..."

/>

{isPending ? (

加载中...

) : (

    {filteredItems.map(item => (

  • {item.name}
  • ))}

)}

)

}

useDeferredValue:延迟更新低优先级值

作用

useDeferredValue 与 useTransition 类似,用于延迟更新"低优先级"的值,但它直接作用于值而非更新函数。当原值变化时,React 会先使用旧值渲染,待空闲后再更新为新值。

使用场景

显示大型列表的过滤结果(保持输入框响应性)

延迟更新非关键 UI 区域(如侧边栏统计数据)

代码示例

javascript

复制代码

import { useState, useDeferredValue } from 'react'

function ProductList({ products }) {

const [searchTerm, setSearchTerm] = useState('')

// 延迟更新 searchTerm 的值(低优先级)

const deferredSearchTerm = useDeferredValue(searchTerm)

// 基于延迟值过滤列表(仅在 deferredSearchTerm 更新时执行)

const filteredProducts = products.filter(product =>

product.name.toLowerCase().includes(deferredSearchTerm.toLowerCase())

)

return (

type="text"

value={searchTerm}

onChange={(e) => setSearchTerm(e.target.value)}

placeholder="搜索商品..."

/>

{filteredProducts.map(product => (

{product.name}

))}

)

}

useSyncExternalStore:外部数据订阅

作用

useSyncExternalStore 用于订阅外部数据源(如 Redux、 Zustand 等状态管理库,或浏览器 API 如 localStorage),确保在 React 并发渲染模式下数据的一致性和可预测性。

使用场景

订阅外部状态管理库(替代 useEffect 手动订阅)

监听浏览器 API 变化(如 localStorage、sessionStorage)

确保并发模式下的数据安全访问

代码示例

javascript

复制代码

import { useSyncExternalStore } from 'react'

// 模拟外部数据源(如 Redux store)

const store = {

state: { count: 0 },

listeners: [],

subscribe(listener) {

this.listeners.push(listener)

return () => {

this.listeners = this.listeners.filter(l => l !== listener)

}

},

getState() {

return this.state

},

increment() {

this.state.count++

this.listeners.forEach(listener => listener())

}

}

// 自定义 Hook 封装订阅逻辑

function useStore(selector) {

return useSyncExternalStore(

store.subscribe.bind(store), // 订阅函数

() => selector(store.getState()), // 获取当前状态

() => selector({ count: 0 }) // 服务端初始状态

)

}

// 使用外部数据

function Counter() {

const count = useStore(state => state.count)

return (

计数: {count}

)

}

useInsertionEffect:CSS-in-JS 样式插入

作用

useInsertionEffect 是 React 18 为 CSS-in-JS 库提供的特殊 Hook,它在 DOM 元素插入前执行 ,用于动态插入样式规则,避免样式闪烁问题。执行时机早于 useLayoutEffect。

使用场景

CSS-in-JS 库动态插入样式(如 styled-components、Emotion)

需要在 DOM 渲染前注入关键样式的场景

代码示例

javascript

复制代码

import { useInsertionEffect, useState } from 'react'

// 简化的 CSS-in-JS 实现

function useCSS(style) {

const styleRef = useRef(null)

useInsertionEffect(() => {

// 创建 style 标签并插入样式(在 DOM 元素插入前执行)

styleRef.current = document.createElement('style')

styleRef.current.textContent = style

document.head.appendChild(styleRef.current)

return () => {

document.head.removeChild(styleRef.current)

}

}, [style])

return styleRef

}

function StyledButton() {

const [color, setColor] = useState('blue')

// 动态生成样式

useCSS(`

.custom-button {

background: ${color};

color: white;

padding: 8px 16px;

border: none;

border-radius: 4px;

}

`)

return (

className="custom-button"

onClick={() => setColor('red')}

>

点击变色

)

}

其他实用 Hooks

useImperativeHandle:自定义暴露实例值

作用

useImperativeHandle 用于自定义通过 ref 暴露给父组件的实例值,避免将子组件的完整 DOM 实例暴露出去,增强组件封装性。

使用场景

父组件需要调用子组件的特定方法(如表单提交、重置)

限制父组件可访问的子组件功能(避免直接操作 DOM)

代码示例

javascript

复制代码

import { useRef, useImperativeHandle, forwardRef } from 'react'

// 使用 forwardRef 将 ref 传递给子组件

const CustomInput = forwardRef((props, ref) => {

const inputRef = useRef(null)

// 自定义暴露给父组件的方法

useImperativeHandle(ref, () => ({

focus: () => {

inputRef.current.focus()

},

clear: () => {

inputRef.current.value = ''

}

}))

return

})

// 父组件使用子组件暴露的方法

function ParentComponent() {

const inputRef = useRef(null)

return (

)

}

useDebugValue:自定义 Hook 调试信息

作用

useDebugValue 用于在 React DevTools 中显示自定义 Hook 的标签和值,方便调试复杂的自定义 Hook。

使用场景

开发共享自定义 Hook(如 useLocalStorage、useFetch)

增强自定义 Hook 的调试体验

代码示例

javascript

复制代码

import { useState, useEffect, useDebugValue } from 'react'

// 自定义 Hook:获取窗口尺寸

function useWindowSize() {

const [size, setSize] = useState({

width: window.innerWidth,

height: window.innerHeight

})

useEffect(() => {

const handleResize = () => {

setSize({

width: window.innerWidth,

height: window.innerHeight

})

}

window.addEventListener('resize', handleResize)

return () => window.removeEventListener('resize', handleResize)

}, [])

// 在 DevTools 中显示调试信息

useDebugValue(`width: ${size.width}, height: ${size.height}`)

return size

}

// 使用自定义 Hook

function ResponsiveComponent() {

const { width, height } = useWindowSize()

return (

窗口尺寸: {width} x {height}

)

}

总结:Hooks 最佳实践与注意事项

核心原则

只在顶层调用 Hooks:不要在循环、条件或嵌套函数中调用 Hooks,确保每次渲染时 Hooks 调用顺序一致。

只在函数组件或自定义 Hook 中调用:避免在普通 JavaScript 函数中使用 Hooks。

依赖数组要完整 :useEffect、useMemo、useCallback 的依赖数组应包含所有外部变量,避免闭包陷阱。

性能优化建议

避免过度优化 :useMemo 和 useCallback 本身有性能开销,仅在确有性能问题时使用。

合理拆分组件:将复杂逻辑拆分为小型组件,减少不必要的重渲染。

使用 React.memo 谨慎 :仅对纯展示组件使用 React.memo,避免浅比较成本高于重渲染成本。

常见陷阱

闭包陷阱 :useEffect 中依赖项未更新时,内部函数可能捕获旧的状态值。

过度使用 Context:Context 变化会导致所有消费组件重渲染,复杂状态建议使用状态管理库。

忽略清理函数 :忘记清理 useEffect 中的订阅或定时器,可能导致内存泄漏。

通过合理使用和组合这些 Hooks,开发者可以编写出更简洁、可维护且高性能的 React 应用。React 18 新增的 Hooks 进一步增强了并发渲染下的用户体验,建议在项目中逐步实践和迁移。# React 18 新增 Hooks

useId:唯一 ID 生成器

作用

useId 是 React 18 引入的用于生成唯一 ID 的 Hook,特别适用于需要在服务端渲染 (SSR) 中避免 hydration 不匹配的场景。它生成的 ID 带有稳定的前缀,确保客户端和服务端生成的 ID 一致。

使用场景

为表单元素生成关联的 id 和 htmlFor 属性

为无障碍 (a11y) 属性生成唯一标识符(如 aria-labelledby)

避免 SSR 时因随机 ID 导致的 hydration 警告

代码示例

javascript

复制代码

import { useId } from 'react'

function FormInput() {

// 生成唯一 ID

const inputId = useId()

const passwordId = useId()

return (

用户名:

密码:

)

}

注意:useId 生成的 ID 包含 : 字符,不适合用于 CSS 选择器或 querySelector。

useTransition:非阻塞状态更新

作用

useTransition 允许将某些状态更新标记为"非紧急",React 会优先处理紧急更新(如输入框输入),延迟处理非紧急更新,从而避免 UI 卡顿,提升用户体验。

使用场景

大型列表过滤或排序(如搜索框输入过滤长列表)

复杂数据计算或转换(不希望阻塞用户输入)

任何可能导致 UI 卡顿的非紧急状态更新

代码示例

javascript

复制代码

import { useState, useTransition } from 'react'

function SearchList({ items }) {

const [query, setQuery] = useState('')

const [filteredItems, setFilteredItems] = useState(items)

// isPending 表示过渡是否进行中,startTransition 包装非紧急更新

const [isPending, startTransition] = useTransition()

const handleSearch = (e) => {

const newQuery = e.target.value

// 紧急更新:立即更新输入框的值

setQuery(newQuery)

// 标记为非紧急更新:过滤列表(可能耗时)

startTransition(() => {

setFilteredItems(

items.filter(item =>

item.name.toLowerCase().includes(newQuery.toLowerCase())

)

)

})

}

return (

type="text"

value={query}

onChange={handleSearch}

placeholder="搜索..."

/>

{isPending ? (

加载中...

) : (

    {filteredItems.map(item => (

  • {item.name}
  • ))}

)}

)

}

useDeferredValue:延迟更新低优先级值

作用

useDeferredValue 与 useTransition 类似,用于延迟更新低优先级的值。不同之处在于,useDeferredValue 是对值进行延迟,而 useTransition 是对更新函数进行延迟包装。

使用场景

当某个值的计算可能阻塞 UI,但又无法通过 useTransition 包装(如从 props 接收的值)

需要基于延迟值进行渲染,且希望保持组件结构简洁

代码示例

javascript

复制代码

import { useState, useDeferredValue } from 'react'

function ProductList({ products }) {

const [searchQuery, setSearchQuery] = useState('')

// 延迟更新搜索查询(低优先级)

const deferredQuery = useDeferredValue(searchQuery)

// 基于延迟值过滤产品(避免每次输入都立即过滤)

const filteredProducts = products.filter(product =>

product.name.toLowerCase().includes(deferredQuery.toLowerCase())

)

return (

type="text"

value={searchQuery}

onChange={(e) => setSearchQuery(e.target.value)}

placeholder="搜索产品..."

/>

{filteredProducts.map(product => (

{product.name}

))}

)

}

useSyncExternalStore:外部数据订阅

作用

useSyncExternalStore 用于订阅外部数据源(如 Redux、 Zustand 等状态管理库,或浏览器 API 如 localStorage),确保在并发渲染模式下数据的一致性和可预测性。

使用场景

订阅外部状态管理库(替代 useEffect 手动订阅)

监听浏览器 API 变化(如 localStorage、sessionStorage)

确保并发模式下的数据同步

代码示例

javascript

复制代码

import { useSyncExternalStore } from 'react'

// 模拟外部数据源(如 Redux store)

const store = {

state: { count: 0 },

listeners: [],

subscribe(listener) {

this.listeners.push(listener)

return () => {

this.listeners = this.listeners.filter(l => l !== listener)

}

},

getState() {

return this.state

},

increment() {

this.state.count++

this.listeners.forEach(listener => listener())

}

}

// 自定义 Hook 封装订阅逻辑

function useStore(selector) {

return useSyncExternalStore(

store.subscribe.bind(store), // 订阅函数

() => selector(store.getState()), // 获取当前状态

() => selector({ count: 0 }) // 服务端初始状态

)

}

function Counter() {

const count = useStore(state => state.count)

return (

计数: {count}

)

}

useInsertionEffect:CSS-in-JS 样式插入

作用

useInsertionEffect 是 React 18 新增的副作用 Hook,其执行时机在 DOM 变更之前,比 useLayoutEffect 更早。主要用于 CSS-in-JS 库在渲染前插入样式,避免样式闪烁(Flash of Unstyled Content, FOUC)。

使用场景

CSS-in-JS 库内部实现样式插入

需要在 DOM 元素渲染前注入关键样式

性能敏感的样式操作(避免布局偏移)

代码示例

javascript

复制代码

import { useInsertionEffect, useState } from 'react'

// 模拟 CSS-in-JS 样式插入

function useCSS(styles) {

useInsertionEffect(() => {

// 在 DOM 更新前插入样式

const styleSheet = document.createElement('style')

styleSheet.textContent = styles

document.head.appendChild(styleSheet)

return () => {

document.head.removeChild(styleSheet)

}

}, [styles])

}

function StyledComponent() {

const [color, setColor] = useState('red')

// 使用 useInsertionEffect 注入样式

useCSS(`

.styled-div {

color: ${color};

font-size: 20px;

padding: 10px;

}

`)

return (

动态样式文本

)

}

总结:Hooks 最佳实践与注意事项

基础原则

只在函数组件或自定义 Hook 中调用 Hooks,不要在普通函数、循环或条件语句中调用

遵循依赖数组规则 :确保 useEffect、useMemo、useCallback 的依赖数组完整包含所有外部变量

避免过度优化 :useMemo 和 useCallback 有性能开销,仅在确实存在性能问题时使用

常见陷阱

闭包陷阱 :useEffect 中访问的状态是捕获的渲染时的状态,如需最新状态可使用 useRef 存储

依赖缺失:忘记在依赖数组中添加变量,导致副作用逻辑使用旧值

过度使用 Context :useContext 会导致消费组件在 Context 值变化时重新渲染,避免存储频繁变化的值

自定义 Hook 组合

通过组合内置 Hooks 可以创建自定义 Hook,封装复用逻辑:

scss

复制代码

// 自定义 Hook:带防抖的输入处理

function useDebouncedInput(initialValue, delay = 300) {

const [value, setValue] = useState(initialValue)

const [debouncedValue, setDebouncedValue] = useState(initialValue)

const timerRef = useRef(null)

useEffect(() => {

// 清除上一次定时器

if (timerRef.current) clearTimeout(timerRef.current)

// 设置新定时器

timerRef.current = setTimeout(() => {

setDebouncedValue(value)

}, delay)

return () => {

clearTimeout(timerRef.current)

}

}, [value, delay])

return [value, setValue, debouncedValue]

}

React Hooks 提供了强大的能力来简化组件逻辑和优化性能。掌握本文介绍的常用 Hooks 及其使用场景,能够帮助开发者编写更简洁、高效和可维护的 React 应用。随着 React 版本的迭代,新的 Hooks 会不断出现,建议持续关注官方文档以获取最新信息。

相关推荐

雾灯打开的正确方式
365bet体育在线怎么样

雾灯打开的正确方式

📅 10-22 👁️ 9307