React常用Hooks全面解析:16个核心钩子的概念与使用场景

更新日期: 2026-04-27 阅读: 91 标签: Hook

本文整理了React开发中最常用的16个Hook,分为6大类,覆盖日常开发的大部分场景。内容基于实际使用经验手打整理,未包含React 19新增特性。


一、六大分类概览

分类Hook名称用途
State Management(状态管理)useState定义组件状态
State Management(状态管理)useReducer复杂状态管理
State Management(状态管理)useSyncExternalStore订阅外部数据源
Effect Hooks(副作用)useEffect处理副作用
Effect Hooks(副作用)useLayoutEffect同步执行副作用
Effect Hooks(副作用)useInsertionEffect注入样式专用
Ref Hooks(引用)useRef创建可变引用
Ref Hooks(引用)useImperativeHandle暴露子组件方法
Performance Hooks(性能优化)useMemo缓存计算结果
Performance Hooks(性能优化)useCallback缓存函数引用
Context Hooks(上下文)useContext读取上下文值
Transition Hooks(过渡)useTransition标记非紧急更新
Transition Hooks(过渡)useDeferredValue延迟更新状态值
Random Hooks(杂项)useDebugValue自定义Hook调试
Random Hooks(杂项)useId生成唯一ID

下面按分类逐一说明每个Hook的用法和适用场景。


二、State Management(状态管理)

状态管理是React最核心的功能——当状态发生变化时,自动重新渲染组件。这个分类包含三个Hook。

useState - 定义组件状态

使用频率最高的Hook,用于定义组件的局部状态。

const [value, setValue] = useState('');  // 设置状态初始值

const handleChange = (e) => {  
  setValue(e.target.value);  // 事件触发时更新状态
};

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

第一个变量value存储当前状态值,第二个变量setValue是更新状态的函数。上面的例子中,输入框内容会实时同步到value状态中。

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参数,可以根据不同的action做出灵活的状态变更。在游戏界面这类交互复杂、状态繁多的场景中,useReducer比useState更适用。

useSyncExternalStore - 订阅外部数据源

这个Hook比较冷门,用于将非React的状态(如第三方库、浏览器API)接入到React组件中,实现外部数据源的订阅。


三、Effect Hooks(副作用)

副作用指的是组件渲染过程中与渲染结果无直接关联、但会影响外部环境的一些操作。

useEffect - 处理副作用

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

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

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

第二个参数传入[count],表示只有当count变化时,才会执行回调函数。需要注意,useEffect不应该用于“点击按钮时执行”或“获取数据时执行”这类场景。

实际上useEffect并不需要频繁使用。它最合适的场景是让React与浏览器API同步,比如控制视频(或音频)的播放与暂停:

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 - 同步执行副作用

useLayoutEffect在浏览器完成渲染之前同步执行,可以用来避免页面闪烁或布局跳动。

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

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

浏览器还没开始渲染这个提示框,我们就已经获取到它的高度并保存为状态。这样在后续渲染中可以提前布局,避免视觉上的跳跃。

useInsertionEffect - 注入样式专用

这是一个更加小众的Hook,执行时机比useLayoutEffect还早,专门为CSS-in-JS库开发者设计,用于确保所有CSS样式都能正确应用到对应的组件元素上。


四、Ref Hooks(引用)

useRef - 创建可变引用

useRef类似于useState,也能存储值,但不会触发组件重新渲染。引用是可变的,可以直接通过等号运算符修改,例如ref.current = 'something'。

const [timer, setTimer] = useState(0);
const intervalRef = useRef(); // useRef用来保存setInterval的ID

const startTimer = () => {
  intervalRef.current = setInterval(() => {  // 存储定时器ID
    setTimer((prevTimer) => prevTimer + 1);
  }, 1000);
};

const stopTimer = () => {
  clearInterval(intervalRef.current);  // 停止时清除定时器
};

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

你可能会问,为什么不用普通变量?因为React组件每次重新渲染,普通变量的值会丢失。使用useRef保存的值在组件整个生命周期中都存在。

useImperativeHandle - 暴露子组件方法

这个Hook配合forwardRef使用,用于向父组件暴露子组件的方法。

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元素的。useImperativeHandle解决了这个问题,让父组件可以调用子组件暴露的方法。


五、Performance Hooks(性能优化)

useMemo - 缓存计算结果

useMemo通过记忆化技术缓存计算结果,避免不必要的重复计算。只有在依赖项发生变化时才会重新计算。

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

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

求和结果被缓存起来,组件重新渲染时不会重复执行reduce计算。只有当numbers数组发生变化时,缓存才会更新。

useCallback - 缓存函数引用

useCallback与useMemo类似,但专门用于缓存函数。

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

  // 缓存increment函数
  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后,只要依赖项不变,函数引用就不会改变,避免子组件不必要的重新渲染。


六、Context Hooks(上下文)

useContext - 读取上下文值

useContext用于读取上下文的值,可以理解为React版的全局变量。

const ThemeContext = createContext();

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

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

createContext创建一个上下文,通过Provider提供给子组件,其他组件中通过useContext即可读取到该值。这个模式常用于主题切换、语言偏好等全局配置。


七、Transition Hooks(过渡)

useTransition - 标记非紧急更新

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卡顿。用startTransition包裹过滤逻辑后,这段更新被标记为低优先级。isPending是一个布尔值,表示当前是否有状态更新正在等待中,可以在更新完成前显示加载提示。

useDeferredValue - 延迟更新状态值

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很适合过滤列表这类场景,无需手动设置过渡,也不需要任何异步状态管理。


八、Random Hooks(杂项)

useDebugValue - 自定义Hook调试

在React DevTools中为自定义Hook添加标签,方便调试时查看内部状态。

useId - 生成唯一ID

调用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组件,通过useId生成的唯一ID可以避免输入框冲突,无需手动管理ID。


总结

以上16个Hook覆盖了React日常开发的绝大多数场景。回顾一下核心要点:

  • 状态管理三件套:useState处理简单状态,useReducer应对复杂逻辑,useSyncExternalStore处理外部数据源。

  • 副作用处理有执行时机之分:useEffect在渲染后执行,useLayoutEffect在渲染前执行,useInsertionEffect专门处理样式注入。

  • 引用不同于状态:useRef的值可变且不触发重渲染,适合存储DOM引用或不需要响应式更新的值。

  • 性能优化靠缓存:useMemo缓存计算结果,useCallback缓存函数引用,减少不必要的重新计算和渲染。

  • 过渡机制提升体验:useTransition和useDeferredValue将非紧急更新延后处理,保证交互流畅。

理解这些Hook的执行时机和适用场景,才能在合适的场景选择合适的工具。不是所有场景都需要用Hook,也不是所有优化都值得加。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13717

相关推荐

结合高阶函数聊聊useMemo和useCallback

useCallback和useMemo是其中的两个 hooks,本文旨在通过解决一个需求,结合高阶函数,深入理解useCallback和useMemo的用法和使用场景。 之所以会把这两个 hooks 放到一起说,是因为他们的主要作用都是性能优化

分享 10 个可以使用 Vue.js 制作的有用的自定义钩hook

Vue.js 是我使用的第一个 JavaScript 框架。 我可以说 Vue.js 是我进入 JavaScript 世界的第一扇门之一。 目前,Vue.js 仍然是一个很棒的框架。 我认为有了组合 API,Vue.js 只会增长得更多

pytest插件探索——hook开发

conftest.py可以作为最简单的本地plugin调用一些hook函数,以此来做些强化功能。pytest整个框架通过调用如下定义良好的hooks来实现配置,收集,执行和报告这些过程:

useContext Hook 是如何工作的?

所有这些新的React Hook之间都有一个宗旨:就是为了使函数组件像类组件一样强大。useContext hook 与其它几个有点不一样,但它在特定场景下还是很有用的。React 的 Context API 是一种在应用程序中深入传递数据的方法

关于为什么使用React新特性Hook的一些实践与浅见

Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。Hook的语法更加简练易懂,消除了class的生命周期方法导致的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。

React封装强业务hook的一个例子

最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。

React中useState Hook 示例

到 React 16.8 目前为止,如果编写函数组件,然后遇到需要添加状态的情况,咱们就必须将组件转换为类组件。编写 class Thing extends React.Component,将函数体复制到render()方法中,修复缩进,最后添加需要的状态。

结合React的Effect Hook分析组件副作用的清除

我们在DidMount的时候通过ID订阅了好友的在线状态,并且为了防止内存泄漏,我们需要在WillUnmount清除订阅,但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么?

React官方团队出手,补齐原生Hook短板

然而实际上,由于回调函数被useCallback缓存,形成闭包,所以点击的效果始终是sendMessage()。这就是「闭包陷阱」。以上代码的一种解决方式是「为useCallback增加依赖项」

实现一个自定义 React Hook:UseLocalStorageState

最近做需求,需要将数据保存到 localStorage 里,在组件初始化的时候获取,然后修改该值的时候,要保存到本地的 localStorage 中。很显然,这些逻辑完全可以封装为一个 React Hook

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!