跳到主要内容

钩子✅

React 内置了哪些 hooks ?

答案

React 19.1 版本下目前支持的 Hooks 列表如下:

Hook说明常见场景示例
useState组件状态管理计数器、表单输入
useEffect副作用处理数据请求、订阅、清理
useContext跨组件共享数据主题、用户信息
useReducer复杂状态逻辑管理多状态、依赖前一状态
useCallback缓存函数引用性能优化、子组件传参
useMemo缓存计算结果计算属性、性能优化
useRef获取/存储可变引用DOM、定时器、缓存值
useLayoutEffectDOM更新后同步副作用测量布局、动画
useImperativeHandle暴露ref自定义实例值转发ref、封装组件
useDebugValue自定义hook调试信息Hook开发
useId生成唯一ID表单、无障碍
useDeferredValue延迟值更新输入防抖、性能优化
useTransition标记并发更新低优先级UI切换
useSyncExternalStore订阅外部存储状态库、全局数据
useInsertionEffect样式插入前副作用CSS-in-JS库
useOptimistic乐观更新表单提交、列表更新
useActionState处理异步操作状态,react 19 新增异步数据加载、提交表单
useFormStatus表单状态管理,只针对 react-dom 环境表单提交状态、验证

代码示例

import React, { useState } from 'react'

// hooks 示例组件 import
import UseState from './useState'
import UseEffect from './useEffect'
import UseContext from './useContext'
import UseReducer from './useReducer'
import UseCallback from './useCallback'
import UseMemo from './useMemo'
import UseRef from './useRef'
import UseLayoutEffect from './useLayoutEffect'
import UseImperativeHandle from './useImperativeHandle'
import UseDebugValue from './useDebugValue'
import UseId from './useId'
import UseDeferredValue from './useDeferredValue'
import UseTransition from './useTransition'
import UseSyncExternalStore from './useSyncExternalStore'
import UseInsertionEffect from './useInsertionEffect'
import UseOptimistic from './useOptimistic'
import UseActionState from './useActionState'

// hooks 映射
const hookComponents: Record<string, React.ReactElement> = {
  useState: <UseState />,
  useEffect: <UseEffect />,
  useContext: <UseContext />,
  useReducer: <UseReducer />,
  useCallback: <UseCallback />,
  useMemo: <UseMemo />,
  useRef: <UseRef />,
  useLayoutEffect: <UseLayoutEffect />,
  useImperativeHandle: <UseImperativeHandle />,
  useDebugValue: <UseDebugValue />,
  useId: <UseId />,
  useDeferredValue: <UseDeferredValue />,
  useTransition: <UseTransition />,
  useSyncExternalStore: <UseSyncExternalStore />,
  useInsertionEffect: <UseInsertionEffect />,
  useOptimistic: <UseOptimistic />,
  useActionState: <UseActionState />
}

const hookOptions = Object.keys(hookComponents)

export default function ReactHooksDemo () {
  // 当前选中的 hook
  const [selectedHook, setSelectedHook] = useState<string>(hookOptions[0])

  return (
    <div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '800px', margin: '0 auto' }}>
      <h1>React Hooks 示例</h1>
      <p>选择下拉框查看各个 Hook 的示例和用法</p>
      <div style={{ marginBottom: 20 }}>
        <select
          value={selectedHook}
          onChange={e => setSelectedHook(e.target.value)}
          style={{ fontSize: 16, padding: '6px 12px' }}
        >
          {hookOptions.map(hook => (
            <option key={hook} value={hook}>
              {hook}
            </option>
          ))}
        </select>
      </div>
      <div style={{
        border: '1px solid #ddd',
        borderRadius: '8px',
        padding: '16px',
        minHeight: 200
      }}>
        {hookComponents[selectedHook]}
      </div>
    </div>
  )
}

常见误区

  • 误用 useEffect 导致死循环:依赖项数组未正确设置。
  • useRef 变化不会触发组件更新。
  • useMemo/useCallback 过度使用反而影响性能。
提示

大部分业务场景只需掌握 useState、useEffect、useContext、useRef,其他 hooks 可按需查阅官方文档。

延伸阅读

说一下 useState 的使用?

答案

useState 用在函数组件和自定义 Hook 中,用于声明和管理组件状态。它返回一个包含当前状态值和更新函数的数组。 useState 函数如下

type Dispatch<A> = A => void;
type BasicStateAction<S> = (S => S) | S;
function useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]
  • initialState:初始状态值
    • 可以是任意类型,也可以是一个返回初始状态的函数。
    • 也可是函数返回一个初始值,函数只会在组件首次渲染时调用一次。
  • 返回值是一个数组,第一个元素是当前状态值,第二个元素是更新状态的函数。
    • 第一个元素是当前状态值未传入 initialState 时默认为 undefined。
    • 更新函数
      • 支持直接传入新状态值
      • 也支持传入一个函数来基于当前状态计算新状态。

示例如下

实时编辑器
function Counter () {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Add</button>
    </div>
  )
}
结果
Loading...

使用中需要注意

  1. 遵守 Hooks 规则
    • 只能在函数组件或自定义 Hook 中调用 useState。
    • 不能在条件语句、循环或嵌套函数中调用 useState。
  2. 对于初始状态值不要传入复杂的函数,这样会导致每次组件渲染时都重新计算初始状态。
import { useState } from 'react'

function createInitialTodos () {
  const initialTodos = []
  const i = Date.now()
  // 模拟一个耗时操作,确保 useState 的初始化函数被调用
  while (Date.now() - i <= 2e2) ;

  for (let i = 0; i < 10; i++) {
    initialTodos.push({
      id: i,
      text: 'Item ' + (i + 1)
    })
  }

  return initialTodos
}

function App () {
  // 错误的使用 createInitialTodos() 计算初始值,导致每次组件渲染时都会重新计算初始值
  const [todos, setTodos] = useState(createInitialTodos())
  const [text, setText] = useState('')

  return (
    <>
      <input
        value={text}
        // 使用 onChange 更新文本框时,因为初始值每次重复计算导致卡顿
        onChange={(e) => setText(e.target.value)}
      />
      <button
        onClick={() => {
          setText('')
          setTodos([
            {
              id: todos.length,
              text
            },
            ...todos
          ])
        }}
      >
        Add
      </button>
      <ul>
        {todos.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    </>
  )
}

export default App

  1. 更新状态后,不要直接消费 state 的值,需要通过更新函数获取最新状态。
import { useState } from 'react'

function Counter () {
  const [age, setAge] = useState(42)

  function increment () {
    setAge(age + 1)
  }

  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment()
        increment()
        increment()
      }}>+3</button>
      <button onClick={() => {
        increment()
      }}>+1</button>
    </>
  )
}

function CallbackUpdateCounter () {
  const [age, setAge] = useState(42)

  function increment () {
    setAge(a => a + 1)
  }

  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment()
        increment()
        increment()
      }}>+3</button>
      <button onClick={() => {
        increment()
      }}>+1</button>
    </>
  )
}

export default function SetStateCallBack () {
  return (
    <div>
      <h2>setState 回调函数示例</h2>
      <p>直接传递状态,多次调用的时候会导致不正确的结果。</p>
      <Counter />
      <p>使用回调函数可以确保每次更新都基于最新的状态。</p>
      <CallbackUpdateCounter />
    </div>
  )
}

  1. 对于非原始更新,注意要重新赋值避免引用类型未修改导致的问题
import { useState } from 'react'

// 错误示例:直接修改对象
function ObjectMutationWrong () {
  const [person, setPerson] = useState({ name: 'Alice', age: 25 })

  function handleBirthday () {
    // 错误:直接修改状态对象
    person.age += 1
    setPerson(person) // 不会触发重新渲染,因为引用没变
  }

  return (
      <div>
         <p>姓名: {person.name}, 年龄: {person.age}</p>
         <button onClick={handleBirthday}>过生日(不生效)</button>
      </div>
  )
}

// 正确示例:创建新对象
function ObjectMutationCorrect () {
  const [person, setPerson] = useState({ name: 'Alice', age: 25 })

  function handleBirthday () {
    // 正确:创建新对象
    setPerson({ ...person, age: person.age + 1 })
  }

  return (
      <div>
         <p>姓名: {person.name}, 年龄: {person.age}</p>
         <button onClick={handleBirthday}>过生日(生效)</button>
      </div>
  )
}

// 错误示例:直接修改数组
function ArrayMutationWrong () {
  const [items, setItems] = useState(['苹果', '香蕉'])

  function addItem () {
    // 错误:直接修改状态数组
    items.push('橙子')
    setItems(items) // 不会触发重新渲染,因为引用没变
  }

  return (
      <div>
         <ul>
            {items.map((item, index) => (
               <li key={index}>{item}</li>
            ))}
         </ul>
         <button onClick={addItem}>添加水果(不生效)</button>
      </div>
  )
}

// 正确示例:创建新数组
function ArrayMutationCorrect () {
  const [items, setItems] = useState(['苹果', '香蕉'])

  function addItem () {
    // 正确:创建新数组
    setItems([...items, '橙子'])
  }

  return (
      <div>
         <ul>
            {items.map((item, index) => (
               <li key={index}>{item}</li>
            ))}
         </ul>
         <button onClick={addItem}>添加水果(生效)</button>
      </div>
  )
}

export default function ReferenceUpdate () {
  return (
      <div>
         <h2>引用类型状态更新示例</h2>

         <h3>对象更新</h3>
         <p>错误方式:直接修改对象属性</p>
         <ObjectMutationWrong />

         <p>正确方式:创建新对象</p>
         <ObjectMutationCorrect />

         <h3>数组更新</h3>
         <p>错误方式:直接修改数组内容</p>
         <ArrayMutationWrong />

         <p>正确方式:创建新数组</p>
         <ArrayMutationCorrect />
      </div>
  )
}

延伸阅读

说一下 useRef?

答案
  1. 不触发视图更新的信息,可以使用 useRef 来存储。
  2. 典型使用场景
实时编辑器
function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}
结果
Loading...
  1. 注意事项
import { useState, useRef } from 'react'
import { flushSync } from 'react-dom'

export default function TodoList () {
  const listRef = useRef(null)
  const [text, setText] = useState('')
  const [todos, setTodos] = useState(
    initialTodos
  )

  function handleAdd () {
    const newTodo = { id: nextId++, text }
    flushSync(() => {
      setText('')
      setTodos([...todos, newTodo])
    })
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    })
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  )
}

let nextId = 0
const initialTodos = []
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  })
}

延伸阅读

useReducer 的作用,和 useState 有什么区别?

答案

useReducer 是 React 提供的状态管理 Hook,适用于复杂的状态逻辑管理。它与 useState 的主要区别在于状态管理方式和适用场景。

基本语法

const [state, dispatch] = useReducer(reducer, initialState)
  • reducer: 纯函数,接收当前状态和动作,返回新状态
  • initialState: 初始状态值
  • state: 当前状态
  • dispatch: 分发动作的函数

与 useState 的核心区别

特性useStateuseReducer
适用场景简单状态管理复杂状态逻辑
状态结构单一值或简单对象复杂对象、多个相关状态
更新方式直接设置新值通过动作(action)描述变化
状态依赖适合独立状态适合依赖前一状态的更新
逻辑复用较难复用reducer逻辑易于复用和测试

典型使用场景对比

useState 适合:

// 简单计数器
function SimpleCounter () {
const [count, setCount] = useState(0)

return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
)
}

useReducer 适合:

// 复杂状态管理
type State = { count: number; error: string | null; loading: boolean }
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setError'; error: string }

function reducer (state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1, error: null }
case 'decrement':
if (state.count <= 0) {
return { ...state, error: '计数不能小于0' }
}
return { ...state, count: state.count - 1, error: null }
case 'reset':
return { count: 0, error: null, loading: false }
case 'setError':
return { ...state, error: action.error }
default:
return state
}
}

function ComplexCounter () {
const [state, dispatch] = useReducer(reducer, {
count: 0,
error: null,
loading: false
})

return (
<div>
<p>计数: {state.count}</p>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
<button onClick={() => dispatch({ type: 'increment' })}>增加</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
)
}

useReducer 的优势

  • 状态逻辑集中: 所有状态变更逻辑都在 reducer 中
  • 可预测性: 通过动作描述状态变化,便于调试
  • 逻辑复用: reducer 函数可以独立测试和复用
  • 复杂状态: 适合管理包含多个字段的状态对象

实际应用场景

1. 表单状态管理

function useFormReducer (initialValues) {
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value }
case 'SET_ERROR':
return { ...state, errors: { ...state.errors, [action.field]: action.error } }
case 'RESET':
return initialValues
default:
return state
}
},
{ ...initialValues, errors: {} }
)

return [state, dispatch]
}

2. 购物车管理

function cartReducer (state, action) {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.item] }
case 'REMOVE_ITEM':
return { ...state, items: state.items.filter(item => item.id !== action.id) }
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.id ? { ...item, quantity: action.quantity } : item
)
}
default:
return state
}
}

选择建议

  • 使用 useState: 状态简单、独立、更新逻辑简单
  • 使用 useReducer: 状态复杂、有多个相关字段、更新逻辑复杂、需要基于前一状态计算新状态

常见误区

  • 过度使用 useReducer: 简单状态也用 useReducer 增加复杂度
  • reducer 不纯: 在 reducer 中执行副作用操作
  • action 设计不当: action 类型不明确或包含过多逻辑
提示

在简单场景下优先使用 useState,当状态逻辑变得复杂、难以管理时再考虑重构为 useReducer

延伸阅读

说一下 useEffect?

答案

useEffect 用来绑定副作用函数,如数据获取、订阅事件、DOM 操作等。useEffect 函数定义如下

type EffectCallback = () => (void | (() => void));
function useEffect(effect: EffectCallback, deps?: Array<any> | void | null): void
  • effect:副作用函数,执行时机为组件渲染后。
    • 可以返回一个清理函数,在组件卸载或依赖项变化时调用。
  • deps:依赖项数组,控制副作用函数的执行时机。
    • 如果传入空数组 [],副作用函数只在组件首次渲染时执行。
    • 如果不传入 deps,则每次组件渲染都会执行副作用函数。
    • 如果传入依赖项数组,副作用函数会在依赖项变化时执行。
    • 注意不要动态改变依赖项 react 会抛出警告

使用中要注意

  1. 确保副作用函数中消费的变量在 deps 中都有申明
  2. 副作用函数获取的 state 为快照,如果要获取最新 state,采用回调模式消费数据,或者使用 useRef 存储最新值。特别是在回调涉及异步时间是需要尤为注意
  3. 正确设置清理函数避免内存泄漏问题

延伸阅读

useLayoutEffect 和 useEffect 有什么区别?

答案

useLayoutEffectuseEffect 的主要区别在于执行时机同步性

核心区别表格

特性useLayoutEffectuseEffect
执行时机DOM 更新后,浏览器绘制前浏览器绘制后,异步执行
同步性同步执行,阻塞渲染异步执行,不阻塞渲染
适用场景需要立即测量 DOM、避免视觉闪烁数据获取、订阅、清理等副作用
性能影响可能阻塞浏览器绘制不影响浏览器绘制性能

执行时机详细说明

  1. useLayoutEffect 在 React 渲染周期中的位置:

    • DOM 更新完成 → useLayoutEffect 执行 → 浏览器绘制
    • 同步执行,会阻塞浏览器的绘制过程
    • 适合需要立即获取最新 DOM 布局信息的场景
  2. useEffect 在 React 渲染周期中的位置:

    • DOM 更新完成 → 浏览器绘制 → useEffect 异步执行
    • 不阻塞浏览器的绘制过程
    • 更适合处理副作用操作

典型应用场景

useLayoutEffect 适用场景:

  • 测量 DOM 元素尺寸或位置,例如记录滚动位置、获取元素宽高等
  • 避免视觉闪烁的 DOM 操作
  • 需要在浏览器绘制前同步更新的场景

useEffect 适用场景:

  • 数据获取(API 调用)
  • 设置订阅或事件监听
  • 清理资源
  • 不需要阻塞渲染的副作用

延伸阅读

useCallback 和 useMemo 有什么区别,如何使用?

答案
属性useCallbackuseMemo
作用缓存函数缓存计算结果
返回值函数任意类型
典型场景事件处理、子组件 props复杂计算、依赖对象
依赖变化重新生成函数重新计算结果

代码示例

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

// 使用 React.memo 包裹子组件,观察 props 是否变化
const MemoizedButton = React.memo(function MemoizedButton ({ onClick, children }: { onClick: () => void, children: React.ReactNode }) {
  useEffect(() => {
    console.log(`MemoizedButton rendered with children: ${children}`)
  })

  return <button onClick={onClick}>{children}</button>
})

export default function App () {
  const [count, setCount] = useState(0)

  // ✅ useCallback:函数引用不会变,除非依赖变化
  const handleClickMemo = useCallback(() => {
    console.log('Clicked Memoized Button')
  }, [])

  // ❌ 每次渲染都创建新函数引用
  const handleClickNormal = () => {
    console.log('Clicked Normal Button')
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
      <button onClick={() => setCount(count + 1)}>Update Count {count}</button>
      <MemoizedButton onClick={handleClickMemo}>MemoizedButton with useCallback</MemoizedButton>
      <MemoizedButton onClick={handleClickNormal}>MemoizedButton no useCallback</MemoizedButton>
    </div>
  )
}

注意

一个典型的误区是认为 useCallback 和 useMemo 能阻止组件重新渲染,实际上 React 的理念是如果组件状态发生了变化整个子树都会重新刷新。而 useCallback 和 useMemo 只是一个缓存函数,消费的场景取决于你是否需要利用缓存。正确的避免重新渲染的方式是使用 React.memo、componentShouldUpdate、React.PureComponent 等技术,同时结合 useCallback 和 useMemo 来优化性能。进一步理解阅读 When should you NOT use React memo? 在 Issue 中 React 核心开发 Mark Erikson 做了详细的阐述

延伸阅读

useContext 的作用是什么 ?

答案

useContext 实现在函数组件中消费 Context 的值。

import { createContext, useContext, useState } from 'react'

const ThemeContext = createContext(null)

function Button ({ children }) {
  const theme = useContext(ThemeContext)
  return (
    <button style={{
      backgroundColor: theme === 'dark' ? '#333' : '#fff',
      color: theme === 'dark' ? '#fff' : '#000',
      padding: '10px 20px',
      border: 'none',
      borderRadius: '5px'
    }}>
      {children}
    </button>
  )
}

export default function MyApp () {
  const [theme, setTheme] = useState('light')
  return (
    <ThemeContext value={theme}>
      <Button>test</Button>
      <label>
        <input
          type="checkbox"
          checked={theme === 'dark'}
          onChange={(e) => {
            setTheme(e.target.checked ? 'dark' : 'light')
          }}
        />
        Use dark mode
      </label>
    </ThemeContext>
  )
}

function Panel ({ title, children }) {
  const theme = useContext(ThemeContext)
  const className = 'panel-' + theme
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

注意事项

  • Context 分割: 将不同功能的数据分到不同 Context 中,避免一个值变化导致整个组件树重新渲染
  • 使用 useMemo 缓存 Context 值: 防止对象/数组引用变化导致的不必要渲染
  • React.memo 包裹消费组件 避免不必要的渲染

延伸阅读

useReducer 和 useState 有什么区别?

答案
对比维度useStateuseReducer
概念用于管理单一状态值用于管理包含多字段或复杂逻辑的状态
使用方式const [state, setState] = useState(init?)const [state, dispatch] = useReducer(reducer, initialArg, init?)
更新方式直接传入新值或函数:setState(next)派发 action:dispatch({ type, payload })
适用场景简单状态、更新逻辑直接复杂状态、多个字段、更新逻辑集中处理
import { useReducer, useState } from 'react'

// 注意 action 实际上可以传任意结构,只是习惯上通过 action.type 来区分不同的操作
function reducer (state, action) {
  if (action.type === 'addAge') {
    return { ...state, age: state.age + 1 }
  } else if (action.type === 'changeName') {
    return { ...state, name: action.name }
  } else if (action.type === 'reset') {
    return { name: 'tom', age: 3 }
  } else {
    throw new Error('Unknown action type')
  }
}

function UseReducerApp () {
  const [state, dispatch] = useReducer(reducer, { name: 'tom', age: 3 })

  return (
    <div>
      <h1>age: {state.age}, name: {state.name}</h1>
      <button onClick={() => dispatch({ type: 'addAge' })}>Add Age</button>
      <input onChange={(e) => dispatch({ type: 'changeName', name: e.target.value })} />
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  )
}

function UseStateApp () {
  const [name, setName] = useState('tom')
  const [age, setAge] = useState(3)

  return (
     <div>
       <h1>age: {age}, name: {name}</h1>
       <button onClick={() => setAge(age + 1)}>Add Age</button>
       <input onChange={(e) => setName(e.target.value)} />
       <button onClick={() => {
         setName('tom')
         setAge(3)
       }}>Reset</button>
     </div>
  )
}

export default function App () {
  return (
      <div>
         <h2>useReducer vs useState</h2>
         <p>useReducer 更适合复杂状态逻辑,尤其是多个子值依赖于其他子值的情况。</p>
         <UseReducerApp />
         <p>useState 更简单,适合单一状态更新。</p>
         <UseStateApp />
      </div>
  )
}

提示

参看示例对于复杂的状态处理可以利用 useReducer 将状态逻辑从组件中抽离出来放在组件外,更具体的逻辑可参考 Extracting State Logic into a Reducer 官方文档说明

useInsertionEffect 有什么用?

答案

useInsertionEffect 是 React 18 引入的一个特殊 Hook,专门用于在 DOM 变更后、所有其他 effect 执行前插入样式。它主要为 CSS-in-JS 库设计,解决在渲染中注入样式的性能问题。

基本语法

useInsertionEffect(setup, deps?)
  • setup: 处理 effect 的函数,可以返回清理函数
  • deps: 可选的依赖项数组
import { useState, useInsertionEffect } from 'react'

// 模拟 CSS 插入函数
function insertStyle (id: string, css: string) {
  let styleTag = document.getElementById(id)
  if (!styleTag) {
    styleTag = document.createElement('style')
    styleTag.id = id
    document.head.appendChild(styleTag)
  }
  // 确保每次都更新样式,让效果更明显
  styleTag.textContent = css
  console.log(`Style "${id}" inserted/updated.`)
}

// --- 使用 useInsertionEffect ---
function InsertionBox () {
  const boxId = 'insertion-box'
  useInsertionEffect(() => {
    insertStyle(boxId, `
      #${boxId} {
        background-color: lightgreen;
        width: 200px;
        height: 80px;
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 10px;
        border: 2px solid green;
        color: black;
        font-weight: bold;
      }
    `)
  }, []) // 只在挂载时执行一次

  // 内部只放文本
  return <div id={boxId}>使用 useInsertionEffect</div>
}

// --- 应用组件 ---
export default function App () {
  return (
    <div>
      <h1>Hook 样式注入对比</h1>
      <InsertionBox />
    </div>
  )
}

提示

useInsertionEffect 是高度专业化的 Hook,主要用于 CSS-in-JS 库开发。普通应用开发应优先使用 useEffectuseLayoutEffect

与其他 Effect Hook 的执行顺序

Hook执行时机(React 渲染阶段)相对顺序主要用途(推荐场景)
useInsertionEffectDOM 变更后,绘制前,早于所有其他 effect(同步)最早(高优先级)注入样式(CSS-in-JS) 避免 FOUC、布局抖动
useLayoutEffectDOM 变更后,绘制前(同步),紧随 useInsertionEffect中间DOM 测量与读取强制同步布局动画初始值设定
useEffect浏览器绘制后(异步,排在任务队列中)最晚(非阻塞)异步任务(如:fetch、订阅)非视觉副作用(日志、事件绑定)

延伸阅读

useActionState 和 useFormStatus 有什么区别?

答案

useActionStateuseFormStatus 是 React 19 中引入的两个新 Hook,都用于处理表单相关的状态管理。

核心区别对比

特性useActionStateuseFormStatus
用途处理异步操作状态获取表单提交状态
适用范围通用异步操作仅针对表单提交
环境限制React 核心react-dom 环境
状态管理管理完整的异步操作生命周期只读的表单状态信息
返回值[state, action,pending]{ pending, data, method, action }
import { useActionState } from 'react'

async function increment (previousState, formData) {
  console.log('increment called with:', previousState, formData)
  return previousState + 1
}

export default function StatefulForm () {
  const [state, formAction] = useActionState(increment, 0)
  return (
    <form>
      {state}
      <button formAction={formAction}>Increment</button>
    </form>
  )
}

延伸阅读

useDeferredValue 和 useTransition 的区别?

答案

useDeferredValueuseTransition 都是 React 18 引入的并发特性 Hook,用于优化用户体验,核心区别如下

核心区别对比

特性useDeferredValueuseTransition
作用对象延迟值的更新延迟状态更新操作
使用方式包装值包装更新函数
返回值延迟的值[isPending, startTransition]
控制方式被动延迟主动标记
适用场景依赖外部数据频繁刷新的非核心状态,注意不在用户输入等中使用,详见 updating-an-input-in-a-transition-doesnt-work
原理差异创建值的延迟版本,React 会在高优先级更新完成后再更新延迟值将状态更新标记为可中断的低优先级任务,允许异步 action 结束后再触发更新
import { useState, useTransition } from 'react'

async function apiGennerateId () {
  console.log('触发异步请求')
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(Math.random().toString(36).substring(2, 15))
    }, (~~(Math.random() * 1000) + 500))
  })
}

function GetId () {
  const [id, setId] = useState(null)
  const [isPending, setPending] = useState(false)

  async function handleClick () {
    setPending(true)
    const newId = await apiGennerateId()
    // 每次更新都会触发渲染
    setId(newId)
    setPending(false)
  }

  return (
    <div>
      <button onClick={handleClick}>获取 ID</button>
      {isPending ? <p>生成中...</p> : id && <p>生成的 ID: {id}</p>}
    </div>
  )
}

function GetIdWithTransition () {
  const [id, setId] = useState(null)
  const [isPending, startTransition] = useTransition()

  async function handleClick () {
    startTransition(async () => {
      const id = await apiGennerateId()
      setId(id)
    })
  }

  return (
    <div>
      <button onClick={handleClick}>获取 ID</button>
      {isPending ? <p>生成中...</p> : id && <p>生成的 ID: {id}</p>}
    </div>
  )
}

function GetIdDemo () {
  return (
    <div>
      <h2>获取 ID 示例</h2>
      <p>不使用 transition 需要手动管理 pending, 每次更新都会触发刷新</p>
      <GetId />
      <p>只会在异步状态结束后才会触发更新避免不必要的渲染</p>
      <GetIdWithTransition />
    </div>
  )
}

export default GetIdDemo

提示

startTransition 的本质就是延迟的更新函数,效果类似 debounce。伪代码如下

let isInsideTransition = false

function startTransition (scope) {
isInsideTransition = true
scope()
isInsideTransition = false
}

function setState () {
if (isInsideTransition) {
// 对于处于 transiton 中的更新,React 会将其标记为低优先级,延迟调度
} else {
// 对于非 transition 中的更新,React 会立即调度
}
}

所以这也是为什么不建议在 startTransition 中使用 setTimeout、setInterval 等异步函数,因为会导致调度的标记丢失,可以通过 setTransition 包裹传入的回调解决此问题,详见 React doesn’t treat my state update as a Transition

延伸阅读

useImperativeHandle 的作用?

答案

useImperativeHandle 是 React 提供的 Hook,用于自定义通过 ref 暴露给父组件的实例值。react19 可以直接结合 useRef 使用,react18 及以前需要结合 forwardRef 使用。

import { useImperativeHandle, useRef } from 'react'

function CustomInput ({ ref }) {
  const inputRef = useRef(null)

  // 使用 useImperativeHandle 来暴露自定义方法
  useImperativeHandle(ref, () => ({
    focus: () => {
      console.log('CustomInput focused')
      inputRef.current?.focus() // 确保 inputRef 存在
      // 实际上可以调用内部的 input.focus() 方法
    },
    clear: () => {
      console.log('CustomInput cleared')
      inputRef.current.value = '' // 清空内部的 input 值
    }
  }))

  return (
    <input ref={inputRef} type="text" placeholder="Custom Input" />
  )
}

export default function App () {
  const inputRef = useRef(null)

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }

  const handleClear = () => {
    if (inputRef.current) {
      inputRef.current.clear()
    }
  }

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleFocus}>Focus Input</button>
      <button onClick={handleClear}>Clear Input</button>
    </div>
  )
}

提示

通过 useImperativeHandle 来限制父组件对子组件实例的访问,避免暴露不必要的内部方法和状态。除了直接操作 DOM 元素外,尽可能通过属性和事件来控制子组件的行为

延伸阅读

useSyncExternalStore 的作用?

答案

useSyncExternalStore 是 React 18 引入的 Hook,用于安全地订阅外部数据源,用来实现脱离 React 内部状态流,基于外部状态实现组件的自定义更新。

基本语法

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
  • subscribe: 订阅函数,接收监听器并返回取消订阅函数,注意回调函数注册和回收在框架内部实现。
  • getSnapshot: 获取当前状态快照的函数
  • getServerSnapshot: 可选,用于 SSR 的服务端快照函数
  • snapshot: 返回当前状态快照
import { useSyncExternalStore } from 'react'

// 定一订阅函数
function subscribe (callback: () => void) {
  // react 会在内部调用 subrscribe 函数来注册事件监听器
  window.addEventListener('online', callback)
  window.addEventListener('offline', callback)
  return () => {
    // react 会在内部调用返回的函数, 在组件卸载时来取消订阅
    window.removeEventListener('online', callback)
    window.removeEventListener('offline', callback)
  }
}

function getSnapshot () {
  // 获取当前的在线状态
  return navigator.onLine
}

// 封装为自定义钩子
function useOnline () {
  const online = useSyncExternalStore(subscribe, getSnapshot)
  return online
}

function OnlineStatus () {
  const online = useOnline()

  return (
    <div>
      <h1>Online Status</h1>
      <p>{online ? 'You are online' : 'You are offline'}</p>
    </div>
  )
}
export default OnlineStatus

提示

useSyncExternalStore 实现了外部状态和组件刷新的绑定,典型场景包括全局 DOM 事件或者外部状态管理库(如 Redux、MobX)的订阅。一般不直接使用 useSyncExternalStore, 而是通过封装的自定义 Hook 来使用。

延伸阅读

useOptimistic 的作用?

答案

useOptimistic 是 React 19 引入的一个 Hook,用于实现乐观更新(Optimistic Updates)。它允许在异步操作开始前通过一个预设的值来更新 UI, 当异步操作完成后,在基于实际结果更新状态。

基本语法

const [optimisticState, addOptimistic] = useOptimistic(state, updateFn)
  • state: 当前的实际状态,在异步操作完成后,optimisticState 的值会与之同步
  • updateFn(currentState, optimisticValue): 函数,用于生成乐观更新的状态,第一个参数为当前 state 的状态,第二个值为 addOptimistic 传入的值
  • optimisticState: 乐观更新后的状态
  • addOptimistic(optimisticValue): 触发乐观更新的函数
import { useState, useOptimistic } from 'react'

async function updateName (name) {
  // 模拟一个更新名字的 API 调用
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(name)
    }, 1000)
  })
}
function ChangeName ({ currentName, onUpdateName }) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName)

  const submitAction = async formData => {
    const newName = formData.get('name')
    setOptimisticName(newName + ' (optimistic)')
    const updatedName = await updateName(newName)
    onUpdateName(updatedName)
  }

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <p>
        <label>Change Name:</label>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName}
        />
      </p>
      <button
         type="submit"
      >
        Submit
      </button>
    </form>
  )
}

export default function UpdateNameExample () {
  const [name, setName] = useState()

  return (
    <div>
      <h1>Update Name Example</h1>
      <ChangeName currentName={name} onUpdateName={setName} />
      <p>Current Name: {name}</p>
    </div>
  )
}

提示

乐观更新适合高频交互和网络延迟明显的场景,但要确保异步操作的可靠性和错误处理。

延伸阅读

useId 的作用?

答案

useId 是 React 18 引入的一个 Hook,用于生成在客户端和服务器端都一致的唯一标识符。

基本语法

const id = useId(): string

核心作用

功能说明使用场景
无障碍性支持为屏幕阅读器等辅助技术提供关联aria-labelledbyaria-describedby
表单 label 和 input 匹配生成唯一 ID,避免冲突htmlForid
组件间唯一标识确保组件在 SSR 和 CSR 中 ID 一致复杂组件、动态生成的元素

延伸阅读

useDebugValue 的作用?

答案

useDebugValue 是 React 提供的一个调试专用 Hook,主要用于在 React 开发者工具(React DevTools)中为自定义 Hook 显示调试信息。

基本语法

useDebugValue(value: any, format?: (value: any) => any): void
  • value: 要在开发者工具中显示的值
  • format: 可选的格式化函数,用于自定义显示格式

延伸阅读

55%