React hooks

React Hooks 可以理解成:让函数组件拥有状态、生命周期、副作用、Context、DOM 引用等能力的 API。

1. React Hooks 全景图

Hook主要作用掌握重要度
useState管理组件状态⭐⭐⭐⭐⭐
useEffect处理副作用⭐⭐⭐⭐⭐
useContext读取 Context⭐⭐⭐⭐
useRef保存引用/DOM/不触发渲染的值⭐⭐⭐⭐⭐
useMemo缓存计算结果⭐⭐⭐⭐
useCallback缓存函数⭐⭐⭐⭐
useReducer管理复杂状态⭐⭐⭐⭐
useLayoutEffectDOM 更新后、浏览器绘制前执行⭐⭐⭐
useImperativeHandle自定义暴露给父组件的 ref⭐⭐⭐
useId生成稳定 ID⭐⭐
useTransition标记非紧急更新⭐⭐⭐
useDeferredValue延迟更新某个值⭐⭐⭐
useSyncExternalStore订阅外部 Store⭐⭐
useInsertionEffectCSS-in-JS 插入样式
Custom Hook自定义逻辑复用⭐⭐⭐⭐⭐

2. useState —— 管理状态

最基础,也是最重要的 Hook。

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>

      <button onClick={() => setCount(count + 1)}>
        +
      </button>
    </div>
  );
}

核心:

useState()
返回
[state, setState]

例如:

const [count, setCount] = useState(0);

count 是状态。

setCount 修改状态。

修改之后会触发组件重新渲染。


3. useEffect —— 处理副作用

这是 React 面试最重要的 Hook 之一

例如请求 API:

useEffect(() => {
  fetch("/api/users")
    .then(res => res.json())
    .then(data => {
      console.log(data);
    });
}, []);

常见用途:

API 请求
WebSocket
Timer
事件监听
订阅
DOM 操作
第三方组件

useEffect 的第二个参数非常重要

① 没有依赖数组

useEffect(() => {
  console.log("effect");
});

每次渲染后执行。


② 空数组

useEffect(() => {
  console.log("effect");
}, []);

通常用于组件首次挂载后的副作用。


③ 有依赖

useEffect(() => {
  console.log("userId changed");
}, [userId]);

userId 发生变化时执行。


Cleanup

useEffect(() => {

  const timer = setInterval(() => {
    console.log("hello");
  }, 1000);

  return () => {
    clearInterval(timer);
  };

}, []);

可以理解:

useEffect
执行副作用
return cleanup
组件卸载/依赖变化时清理

4. useRef —— 保存引用,但不会触发重新渲染

这个 Hook 很容易和 useState 搞混。

const countRef = useRef(0);

访问:

countRef.current

修改:

countRef.current++;

修改 useRef 不会触发组件重新渲染。


DOM 操作

这是 useRef 最经典的用途。

function Input() {

  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} />

      <button onClick={focusInput}>
        Focus
      </button>
    </>
  );
}

关系:

useRef
inputRef
DOM Element
inputRef.current.focus()

5. useState vs useRef

useStateuseRef
保存数据
修改后重新渲染
.current
保存 DOM
保存上一次值可以非常适合
触发 UI 更新

例如:

const [count, setCount] = useState(0);

调用:

setCount(10);

会重新渲染。

而:

const count = useRef(0);

count.current = 10;

不会重新渲染。

一句话:

需要改变后更新 UI,用 useState;只需要保存一个值,不希望触发重新渲染,用 useRef


6. useMemo —— 缓存计算结果

假设计算非常复杂:

const result = useMemo(() => {
  return expensiveCalculation(data);
}, [data]);

只有 data 发生变化时,才重新计算。

data
发生变化
重新计算

如果 data 没变:

data 没变化
直接使用之前计算结果

7. useCallback —— 缓存函数

例如:

const handleClick = useCallback(() => {
  console.log("click");
}, []);

它主要缓存的是:

函数对象本身

useMemo 缓存的是:

计算结果

所以:

useMemo

和:

useCallback

可以这样记:

useMemo
缓存 value

useCallback
缓存 function

8. useMemo vs useCallback

非常高频。

const value = useMemo(() => {
  return calculate(a, b);
}, [a, b]);

缓存:

calculate(a, b) 的结果

而:

const fn = useCallback(() => {
  calculate(a, b);
}, [a, b]);

缓存:

函数 fn

可以简单记:

Memo = Memorize Value

Callback = Memorize Function


9. useContext —— 跨组件共享数据

假设:

App
User
Profile
Avatar

如果 Avatar 需要 User 信息。

传统方式可能需要:

App
 ↓ props
User
 ↓ props
Profile
 ↓ props
Avatar

这就是 Prop Drilling

使用 Context:

const UserContext = createContext(null);

Provider:

<UserContext.Provider value={user}>
  <App />
</UserContext.Provider>

子组件:

const user = useContext(UserContext);

就可以直接获得:

user

10. useReducer —— 复杂状态管理

当状态比较复杂的时候:

const [state, dispatch] = useReducer(reducer, initialState);

例如:

function reducer(state, action) {

  switch (action.type) {

    case "increment":
      return {
        ...state,
        count: state.count + 1
      };

    case "decrement":
      return {
        ...state,
        count: state.count - 1
      };

    default:
      return state;
  }
}

调用:

dispatch({
  type: "increment"
});

useState vs useReducer

简单状态:

useState

复杂状态:

useReducer

例如:

const [name, setName] = useState("");

非常适合 useState

但如果:

user
 ├── name
 ├── age
 ├── address
 ├── orders
 ├── loading
 └── error

状态之间存在复杂关联:

ACTION
reducer
new state

通常 useReducer 更清晰。


11. useLayoutEffect

和:

useEffect

非常容易被问到。

区别主要在执行时机。

大致可以理解:

React 更新 DOM
useLayoutEffect
浏览器绘制
useEffect

所以:

useLayoutEffect

适合需要:

读取 DOM
测量 DOM
计算位置
同步修改 DOM

例如:

useLayoutEffect(() => {
  const height = ref.current.offsetHeight;

  console.log(height);
}, []);

而普通 API 请求通常使用:

useEffect

12. useImperativeHandle

这个 Hook 相对高级。

通常配合:

forwardRef

让父组件能够调用子组件暴露的方法。

例如子组件:

useImperativeHandle(ref, () => ({
  focus() {
    inputRef.current.focus();
  }
}));

父组件:

childRef.current.focus();

适合:

focus
scroll
reset
open
close
play
pause

这种命令式操作。


13. useId

React 提供稳定 ID:

const id = useId();

例如:

<label htmlFor={id}>
  Username
</label>

<input id={id} />

特别适合:

表单
label
accessibility
SSR

14. useTransition

用于 React 的并发更新场景。

例如:

const [isPending, startTransition] = useTransition();

然后:

startTransition(() => {
  setSearchResult(result);
});

告诉 React:

这个更新不是特别紧急,可以降低优先级。

例如搜索:

用户输入
立即更新 Input
搜索结果更新可以稍后

可以提高 UI 响应性。


15. useDeferredValue

useTransition 有点类似。

const deferredValue = useDeferredValue(value);

例如:

用户输入
value:立即更新
deferredValue:稍后更新

适合:

搜索
大量列表
复杂计算
大型 UI

16. Custom Hook

React 允许我们自己创建 Hook。

命名必须以:

use

开头。

例如:

function useUser(userId) {

  const [user, setUser] = useState(null);

  useEffect(() => {

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);

  }, [userId]);

  return user;
}

组件:

function UserProfile({ userId }) {

  const user = useUser(userId);

  return (
    <div>
      {user?.name}
    </div>
  );
}

这就是:

Custom Hook = 可复用的 React 业务逻辑。


17. 最重要的几个 Hook 怎么区分?

你可以记住这张图:

                  React Hooks
       ┌───────────────┼────────────────┐
       │               │                │
     状态             副作用             引用
       │               │                │
  useState          useEffect         useRef
  useReducer        useLayoutEffect
       └──────────────┐
                    Context
                 useContext
                      
                      
       性能优化
    ┌─────┴─────┐
    │           │
 useMemo    useCallback
    │           │
 缓存结果      缓存函数

Vincent zhai
Vincent zhai
Full-Stack Engineer