未命名#

Data: 2026-04-24 13:57:27

我们首先了解你可能会最常使用的钩子。useState 钩子可以说是所有 React 开发者最常使用的钩子,因为除非你使用了某种框架,否则你会经常用到这个钩子。React 诞生的一个重要原因就是可以帮助我们管理应用的状态,并在应用状态变化时重新渲染组件。useState 钩子最适合用于需要自己管理简单特定状态的客户端组件。而且,在很多情况下我们需要管理状态。React 的 useState 钩子可能是我们要讲的最通用的一个钩子。使用 useState 钩子时,你可以提供一个状态初始值,这个状态初始值可以是任意的 JavaScript 值。随后这个值会被存储在你调用 useState 钩子时返回的状态变量中。useState 钩子的返回值是一个数组,你可以使用解构赋值将 useState 钩子的返回数组解构成两个单独的变量。第一个是状态变量,第二个变量是用于更新组件状态的的函数。 useState 钩子非常适合用于捕获用户在表单字段中输入的内容,比如输入框、文本区域和选择框。也可以用于控制组件的显示或隐藏,比如模态框、工具提示或者下拉菜单,只要设置一个布尔状态初始值即可。你也可以使用布尔状态变量,根据条件应用样式类名或内联样式。在处理数值状态时,比如购物车或计数器, useState 钩子也是一个非常理想的选择。 useReducer 是另一个状态钩子,它在管理比 useState 更复杂的状态时非常有用。你不会经常需要用到这个钩子,但如果你有很多相关联的状态,这个钩子就非常值得使用。 useReducer 钩子使用一个 reducer 函数更新状态,这可以从根本上简化你的代码,因为所有的状态更新都可以在一个函数中完成。 useReducer 接受一个 reducer 函数,以及一个初始状态值。它和 useState 一样,返回一个数组,这个数组包含了状态变量,以及一个名为 dispatch 的新函数。当调用 dispatch 函数时, dispatch 函数就会运行 reducer 函数,并传入一个叫做 action 的数据。 action 数据的作用是可以根据传入的 action 有条件的设置状态。 useReducer 很适合简化包含了多个相关联状态值的组件,比如一个包含多个输入框的表单。 useReducer 钩子也适用于那些状态依赖于其他值的组件,比如游戏组件,这使得 useReducer 成为一个非常适合和用于用户交互频繁的应用的选择。你可能没听说过的一个状态钩子是 using external store。这是一个你很可能根本不会使用的钩子,它主要作用是将非 React 的状态存储到 React 组件中。因此你不会使用到这个钩子,除非你想从 0 开始创建你自己的状态管理库。在了解状态钩子之后,我们现在了解一下副作用钩子。副作用钩子用于执行副作用操作。但什么是副作用?副作用是指在组件渲染过程中,与组件渲染结果无直接关联,但可能影响组件外部环境或状态的操作。一个执行副作用的简单示例是使用 document API 设置标题。要实现这一点,首先要给 useEffect 提供一个要运行的函数,默认情况下,这个函数会在组件每次渲染后运行。要改变这种行为,你可以给它传入一个依赖项数组,在这个数组中的值发生变化时,这个副作用函数就会运行。当按钮被点击时,com 变量会在状态中被更新,这会使副作用函数运行,并更新标题。我们来看两种类型的副作用。第一种是事件驱动副作用,这种副作用在某个事件发生时执行,比如点击按钮。第二种是渲染驱动副作用,这种副作用在组件渲染后运行,比如获取数据。这可能会让你感到惊讶,但 useEffect 实际上不应该用于这两种副作用类型。相比在 useEffect 中,在事件被触发时执行副作用,直接在事件处理函数中执行副作用反而能够简化你的代码。与其在组件挂载时获取数据并放入状态中,不如使用更高级的工具来获取数据,比如 React Query 或是像 Next 点 js 这样的框架自带的数据获取模式。你什么时候应该使用 useEffect 钩子呢?你并不需要经常使用这个钩子,useEffect 钩子主要用于你需要将 React 代码与浏览器 API 同步的时候,比如这个例子,在这个例子中,我们使用一个引用钩子将 React 状态与浏览器的媒体 API 同步,并根据状态播放或暂停媒体。实际上,有一个比 useEffect 更特殊的钩子就是 useLayoutEffect 钩子。useEffect 钩子会在 React 绘制 UI 之后运行,而 useLayoutEffect 钩子是在 React 绘制 UI 之前运行,所以 useEffect 是一个异步钩子。而这个 useLayoutEffect 钩子主要用于你希望在显示 UI 内容之前执行的异步操作,比如设置状态。而这就意味着在一些特定的使用场景中,useLayoutEffect 钩子的使用频率会比 useEffect 钩子更低。useLayoutEffect 钩子适合在下面这种情况中使用,当你想从浏览器 API 中获取一些初始状态时,比如这个例子中,我们使用 getBoundingClientRect 测量一个 tooltip 的高度,然后在浏览器重新绘制之前将这个高度保存在状态中,这样用户在看到界面之前就完成了这一步 useInsertionEffect 是一个更加小众的副作用钩子,你可能从来没有听说过,因为这个钩子是专门为 CSS 和 JS 库的开发者设计的,比如 styled-components 或 Motion 这样的样式库,这个钩子的运行时间比 useEffect 钩子和 useLayoutEffect 钩子更早。它的作用是确保所有的 CSS 样式都能运用到正确的组件元素上。useRef 用于直接。访问 DOM 元素或组件实例。useRef 钩子有点像 useState,这个钩子可以用于存储数据,但不会触发组件重新渲染。和 useState 钩子一样, useRef 可以存储任意的数据值,但 useRef 钩子更简单,因为 useRef 钩子只返回一个值,也就是你传入的值。要访问存储在 useRef 钩子中的值,你可以使用 current 属性。引用和状态之间的另一个重要区别是,引用是可变的,而状态是不可变的。因此,存储在 current 属性中的值可以直接使用等号运算符进行修改。useRef 钩子在这个计时器的例子中就非常有用。我们将 interval ID 存在一个引用中,我们可以在我们需要的时候使用 stopTimer 函数清除这个 interval。DOM 元素也可以存储在引用中,你可以通过将创建的引用连接到元素的 ref 属性来实现。引用连接到元素的 ref 属性后,就可以使用 useRef 钩子的 current 属性访问应用底层的 DOM 元素。useImperativeHandle 也是一种引用钩子,但很少使用,只有在你需要转发一个引用的时候才会使用到。因此,你需要将 useImperativeHandle 钩子和 forwardRef 函数一起使用,并且只有在你还需要向传入引用的父组件暴露一个方法时才需要这样做。假设你有一个父组件,需要获取子组件中的一个输入框的焦点,应该如何实现?要实现这一点,你首先需要使用 forwardRef 函数将引用转发给子组件。而在 React 19 中,不再需要这样做。为了将 focus 方法暴露给父组件的引用,你可以使用 useImperativeHandle 钩子将 focus 方法挂载到父组件的引用上。useMemo 是两个用于提升应用性能的钩子之一。它使用一种叫做记忆化的方法,通过缓存之前的结果来提升应用性能。useMemo 只会在它的依赖项发生变化时重新计算缓存的值,这使 useMemo 非常适合执行高消耗的计算。useMemo 的写法看起来与 useEffect 钩子类似,但不是用于副作用,而且必须返回一个值。useMemo 钩子可能执行的一个典型计算是计算一个数组中所有数字的总和。只有在 numbers 数组发生变化时,这个函数才会重新运行,并把计算结果保存到 sum 变量中。和 useMemo 钩子类似,useCallback 也会对传入的内容进行缓存。但与 useMemo 钩子不同的是,useCallback 是用于缓存函数,特别是传递给子组件的回调函数。这样可以提升性能,避免在每次重新渲染时重新创建回调函数。在这个例子中,我们将 increment 函数作为 props 传递给 Button 组件。由于 increment 函数会更新组件状态,increment 函数每次运行时都会导致组件重新渲染。所以,为了防止组件每次重新渲染时都重新创建 increment 函数,我们要使用 useCallback 包裹 increment 函数。有一个上下文钩子叫做 useContext,这个钩子其实非常简单,主要用于读取上下文值。因此,如果一个组件被包裹在 ContextProvider 组件中,你就可以使用这个钩子读取通过上下文传递下来的值。 UseContext 可以在任何嵌套在 Provider 里的组件中使用,无论嵌套的多深都可以。要读取上下文的值,你只需要把创建好的 Context 传给 useContext 就可以了。在 React 中,所有的状态更新默认都被认为是紧急的,也就是说,只要状态发生变化,就会立即触发组件的重新渲染。useTransition 是一个过渡钩子,我们可以使用这个钩子指定某些状态更新不是紧急的。这在处理涉及大量计算的状态更新时非常有用,因为如果状态更新被立即执行,可能会导致不良的用户体验。我们可以通过将这些状态更新包裹在一个过渡中,使我们的应用变得更加响应迅速。useTransition 的一个很好的使用场景是根据用户输入来过滤一个列表。在输入框中输入内容可能会导致 UI 卡顿或响应迟缓,因为应用在每次按键之后,都尝试重新渲染列表。我们可以使用 useTransition 中的 startTransition 函数将过滤状态更新标记为非紧急。useTransition 还会提供一个名为 isPending 的布尔值。 IsPending 会告诉我们当前是否有状态处于过渡等待中。这样我们就可以在状态更新完成之前向用户显示一个加载状态。useDeferredValue 是另一个过渡钩子,你可以使用这个钩子延迟或暂停一次状态更新,以保持应用到响应迅速。useDeferredValue 钩子和 useTransition 钩子非常相似,但我们可以使用 useDeferredValue 钩子在最合适的时机更新状态,而不是我们自己手动设置执行。要使用 useDeferredValue 钩子,你只需要把你想要延迟的值传递给 useDeferredValue 钩子就可以了。就和 useTransition 钩子的例子一样,useDeferredValue 最适合用于像过滤列表这样的场景,这能带来和 useTransition 钩子一样的改善用户体验,但不需要你手动设置过渡,也不需要使用任何异步状态。唯一使用像 useDebugValue 这种随机钩子的原因是,你经常需要使用 React DevTools。如果不是,那你可能永远都不会使用到这个钩子。但如果你使用了,并且你在使用自定义钩子时,你可以将你的自定义钩子标签传递给 useDebugValue 钩子。自定义钩子标签可以使任意的字符串。这样你可以更容易的在 React DevTools 扩展中找到你的自定义钩子。useId 是另一个随机钩子,正如它的名称所示,在调用 useId 钩子时会创建一个唯一的 ID,并且不需要任何参数。但我知道你在想什么,不行,你不能使用 useId 钩子给你的列表项创建 key 属性。useId 钩子用于创建在表单输入框和表单输入框标签之间共享的动态 ID。如果要在同一页面中多次重复使用表单输入框,useId 就会很有用。在这个表单组件中,我们两次使用了 EmailInput 组件。所以为了确保两个 email input 组件各自有唯一的 ID,并且不会互相冲突,我们可以使用 useId 钩子创建唯一的 ID 来进行分开。

如果你真的想深入学习 React,请访问我的个人网站,获取这本 React 核心概念完全指南,帮助你从头到尾掌握本视频中介绍的每一个 React 概念。希望你从这本书中学习到更

🟣 State Management(状态管理)#

表格

Hook解释
useState定义组件状态
useReducer复杂状态管理
useSyncExternalStore订阅外部数据源

🟢 Context Hooks(上下文)#

表格

Hook解释
useContext读取上下文值

🔴 Transition Hooks(过渡)#

表格

Hook解释
useTransition标记非紧急更新
useDeferredValue延迟更新状态值

🟡 Ref Hooks(引用)#

表格

Hook解释
useRef创建可变引用
useImperativeHandle暴露子组件方法

🟢 Random Hooks(杂项)#

表格

Hook解释
useDebugValue自定义 Hook 调试
useId生成唯一 ID

🟣 Performance Hooks(性能优化)#

表格

Hook解释
useMemo缓存计算结果
useCallback缓存函数引用

🔵 Effect Hooks(副作用)#

表格

Hook解释
useEffect处理副作用
useLayoutEffect同步执行副作用
useInsertionEffect注入样式专用

🟠 React 19 Hooks(React 19 新增)#

表格

Hook解释
useFormStatus获取表单提交状态
useFormState管理表单状态
useOptimistic乐观更新 UI
use读取 Promise/Context

今天我整理一下笔记,把我以前记录的下面几个 react 钩子的概念和使用方式、场景简单的过一下。

一共 7 类,20 个。这应该是涵盖 react 使用里的所有的常用钩子了!

/ 01 / State Management(状态管理)
useState - 定义组件状态
useReducer - 复杂状态管理
useSyncExternalStore - 订阅外部数据源
Context Hooks(上下文)
useContext - 读取上下文值


/ 02 / Transition Hooks(过渡)
useTransition - 标记非紧急更新
useDeferredValue - 延迟更新状态值


/ 03 / Ref Hooks(引用)
useRef - 创建可变引用
useImperativeHandle - 暴露子组件方法


/ 04 / Random Hooks(杂项)
useDebugValue - 自定义 Hook 调试
useId - 生成唯一 ID


/ 05 / Performance Hooks(性能优化)
useMemo - 缓存计算结果
useCallback - 缓存函数引用


/ 06 / Effect Hooks(副作用)
useEffect - 处理副作用
useLayoutEffect - 同步执行副作用
useInsertionEffect - 注入样式专用


/ 07 / React 19 Hooks(React 19 新增)
useFormStatus - 获取表单提交状态
useFormState - 管理表单状态
useOptimistic - 乐观更新 UI
use - 读取 Promise/Context

不深入细讲,只是简单每个都说说。

State Management(状态管理)#

先说我们最常使用的钩子。useState 钩子是我们最频繁用到的钩子。

因为 React 诞生的重要目的,就是管理状态,统一管理应用状态!也就是在状态发生变化时,自动重新渲染组件。

const [age, setAge] = useSatate(42);  // 设置一个状态初始值 42

const handleChange = (e) => {  
  setValue(e.target.value);  // 事件一激活,就改值
};

<input
  type="text"
  value={value}
  onChange={handleChange}
/>

这个值存在第一个变量 age 里,第二个变量是一个修改状态的函数。上面这个例子,我们在 input 里面输入什么,它就马上等于什么了。

useReducer 是另一个状态钩子。

可以管理比 【useState】 更复杂的状态。这个用的少,但更强大。

const reducer = (state, action) => {
  switch (action) {
    case 'increment':
      return state + 1;
    default:
      return state;
  }
};

const [count, dispatch] = useReducer(reducer, 0);

<button onClick={() => dispatch('increment')}>Increment</button>

这个 dispatch 新函数,里面有个关键字 action ,可以接收我们传入给 dispatch 的参数,然后做出更灵活的变化。

尤其是什么游戏界面等等,界面复杂的,交互复杂的,会更好用。

然后是【useSyncExternalStore】,这个钩子比较冷门。它可以把非 react 的状态,存到 react 组件中。

然后是 副作用钩子 【useEffect】, 也即是在组件渲染中,与渲染结果无关联,但会影响环境状态的一些操作。

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

useEffect(() => {
  document.title = `你点击了 ${count} 次`;
}, [count]);

<button onClick={() => setCount(count + 1)}>
  Click me
</button>

什么意思呢?看上面这个代码,useEffect 的第二个参数里,传入了 count ,那么但不过这个 count 变化时,就会执行里面的函数。

注意,【useEffect】 不应该用于 点击按钮时执行、获取数据 时执行,这种场景。

其实并不需要经常使用这个,一般我们用【useEffect】是需要 react 与 浏览器 API 同步,比如用 React 控制视频(或音频)的播放、暂停:

const ref = useRef(null); // 创建“引用”,抓住视频 DOM 元素 
useEffect(() => {
  if (isPlaying) {
    ref.current.play();  // 播放
  } else {
    ref.current.pause();  // 暂停
  }
}, [isPlaying]);

<video ref={ref} src={src} loop playsInline />

当我们值 isPlaying 设置成 true 时,就播放了。这种,才是 【useEffect】 真正合适的场景!

其实,有个差不多的钩子,更加好用叫【useLayoutEffect】。这个是在显示 UI 之前就执行的,它的使用例子,在渲染前就获取某元素的高度:

const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => {
  const { height } = ref.current.getBoundingClientRect();  // 获取提示框高度
  setTooltipHeight(height);  // 设置高度
}, []);

厉害吧!用它,浏览器还没开始渲染这个提示框 ,我们就获取到了这个提示框的高度了!然后保存在状态里。

非常好用,可以防止页面闪烁跳跃,懂吧?

如果比早运行对吧?其实还有更早的。

还有一个很小众的钩子 【useInsertionEffect】,这个钩子专门为 CSS 和 JS 的库开发的。这个运行的更早,比【useLayoutEffect】还早!专门用于确保所有的 CSS 样式都能运用到正确的组件元素上。

说说【useRef】钩子吧。

它类似于 【useState】,也能存状态,不过它不会触发组件渲染。所以更简单了!【useRef】叫引用。

引用是可变的,而状态是不可变的。因此能使用等号运算符直接修改。比如 ref.current =‘something’ ,下面是一个计时器使用例子:

// 一个计时器使用案例
const [timer, setTimer] = useState(0);
const intervalRef = useRef(); // useRef 用来保存 setInterval 的 ID
 
const startTimer = () => {
  intervalRef.current = setInterval(() => {  // 存进去了
    setTimer((prevTimer) => prevTimer + 1);
  }, 1000);
};

const stopTimer = () => {
  clearInterval(intervalRef.current);  // 停止时就能用到
};

<p>Timer: {timer} seconds</p>
<button onClick={startTimer}>开始计时</button>
<button onClick={stopTimer}>停止计时</button>

你可能会问,为什么不直接使用变量呢?因为 react 组件一更新,变量就没了…. 使用 react 内部的值,肯定有用!

有个略冷门的钩子:【useImperativeHandle】。

转发一个引用时,会用到。比如:

function ParentComponent() {
  const inputRef = useRef();

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

  // ...

  return (
    <>
      <CustomInput ref={inputRef} />
      <button onClick={handleFocus}>激活 input</button>
    </>
  );
}

// 上面的代码肯定无法运行
// 因此我们使用 forwardRef + useImperativeHandle ,暴露出去
// 把子组件传递给父组件,然后就可以用了
const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef();

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

  return <input ref={inputRef} />;
});

因为直接给自定义组件(如CustomInput)传ref默认是拿不到 DOM 元素的。

再说说【useMemo】这个钩子。

使用一个叫「记忆化」的方法,缓存之前的写法,来提升应用性能。它只会在依赖项发生变化时,重新计算缓存的值,非常适合执行高消耗的计算,举个例子:

function SumComponent({ numbers }) {
  const sum = useMemo(() => {  // 把求和的结果缓存起来
    return numbers.reduce((total, n) => total + n, 0);
  }, [numbers]);

  return <h1>合计: {sum}</h1>;
}

里面,把求和的结果,存起来。组件再渲染时就不会重复计算了。(自由 number 变化,才会重新缓存)。

和 【useMemo】差不多,有个叫【useCallback】的钩子。

这个能缓存函数!!

比如这个例子:

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

  // 单击后会执行
  const increment = useCallback(() => {
    setCount((c) => c + 1);
  }, []);

  return (
    <>
      <div>{count}</div>
      <Button onClick={increment} />
    </>
  );
}


// button 组件
function Button({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

因为有个问题,每次运行都会导致组件重新渲染。那我们只要【useCallback】一下,下次再渲染,就不用重复创建 increment 函数了。

再说说 【useContext】这个钩子吧。能读取上下文(可理解为 react 版全局变量)的值:

const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  const theme = useContext(ThemeContext);
  // ...
}

createContext 创建了个值(react 版全局变量),然后这个值使用 Provider 方法,给里面填充(Value 放什么,就是什么),之后我们在另一个组件里,比如 Form 里,使用 useContext 就可以访问到了。

这个常常用于 theme 主题逻辑。

再说说【useTransition】钩子。

这是一个过渡钩子,我们可使用它,指定某些状态的更新!这个在处理涉及大量计算的状态更新时用处很大!因为状态更新如果被立即执行,那么可能会导致不良的用户体验。什么意思呢?比如这个例子:

const [filter, setFilter] = useState('');
const [inputValue, setInputValue] = useState('');
const [isPending, startTransition] = useTransition();

const filteredItems = items.filter(item => item.includes(filter));

<input
  value={inputValue}
  onChange={(event) => {
    setInputValue(event.target.value);
    startTransition(() => {
      setFilter(event.target.value);
    });
  }}
  placeholder="请填充内容...."
/>

{isPending ? (
  <p>加载中...</p>
) : (
  filteredItems.map(item => <div key={item}>{item}</div>)
)}

根据用户输入,来过滤一个列表。

但是,在输入框中输入,每次输入都重新渲染,会导致 UI 卡顿。

然后我们就用 【useTransition】这个钩子里的「startTransition」包裹,然后这个逻辑就意味着标记为「非紧急」了。

另一个值 isPending ,这是一个 布尔值,告知你,当前是否有状态还在等待中,这样我们就可以在状态更新完成之前,向用户显示一个「加载中…..」。

再说说 【useDeferredValue】这个钩子。

它和【useTransition】钩子很相似。不过,它可以在最合适的实际,更新状态。

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

一个完整的例子如下:

function FilteredList({ items }) {
  const [inputValue, setInputValue] = useState('');
  const deferredFilter = useDeferredValue(inputValue);

  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(deferredFilter.toLowerCase())
  );

  return (
    <>
      <input
        value={inputValue}
        onChange={(event) => setInputValue(event.target.value)}
        placeholder="Type to filter..."
      />
      {filteredItems.map(item => <div key={item}>{item}</div>)}
    </>
  );
}

这个【useDeferredValue】很适合像过滤列表这样的场景。无需你手动设置过渡,也不需要任何异步状态。

说一下【useId】钩子,这是一个随机钩子。

调用它时,会创建一个唯一的 ID 。

它一般用于在表单输入框之间共享的 Id ,什么意思呢?

看看这个例子:

function EmailInput({ name }) {
  const id = useId();

  return (
    <>
      <label htmlFor={id}>{name}</label>
      <input id={id} type="email" />
    </>
  );
}

function Form() {
  return (
    <form>
      <EmailInput name="Email" />
      <EmailInput name="Confirm Email" />
    </form>
  );
}

看下面的 Form 里,两次使用了 EmailInput 组件,为了防止里面的 input 冲突,于是我们就使用 【useId】钩子。


看我上面的笔记。

这个是我很久以前整理的笔记,排版很乱很不友好、言语措辞略显啰嗦、有很多表述不准确、错误也不少。

我希望你修改一下,并且所有钩子都加上小标题(中英措辞都要),优化一下言语(注意,不要有 AI 味)、有人味。然后基本原汁原味返还回来。代码方面,能中文本土化就本土化一下,不过也不太需要。。。

开头的那个类似目录的,要和文中一致。标题的话,就大小标题都有,也就是二级标题。

以及其他的你认为可以发到知乎,但是又不会引起人们的反感(一来,他们不喜欢 枯燥行文、而来他们极端厌恶任何 ai 痕迹)

对原文的修改幅度应小于 5% 。

- end -#

© 2025 –   海牧羽工厂 HMY Factory