当前位置: 首页 > article >正文

React——useCallback

一、定义:

useCallback是一个允许你在多次渲染中缓存函数的 React Hook。它返回一个记忆化的回调函数,只有在依赖项改变时才会更新。这有助于避免在每次渲染时都创建新的函数实例,特别是在将回调函数传递给子组件时。

二、形式:

useCallback(function,dependencies)

参数:

function

1、定义:想要缓存的函数

2、特点:

  • 可以接受任何参数并返回任何值
  • React只会把这个函数返回给你,而不是直接调用!!(由你自己决定何时调用)
  • 进行下一次渲染时,dependencies没有变化,则funtion返回相同的函数;若有变化,React将新传入的函数缓存以便后续使用

dependencies

1、定义:有关是否更新function的所有响应值的一个列表

2、特点:

  • 响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。
  • 依赖列表必须具有确切数量的项,并且必须像 [dep1, dep2, dep3] 这样编写
  • React 使用 Object.is 比较每一个依赖和它的之前的值。

返回值:

在初次渲染时,useCallback 返回你已经传入的 function 函数

在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 function 函数;否则返回这一次渲染传入的 function。

三、注意点

1、useCallback 是一个 Hook,所以应该在 组件的顶层 或自定义 Hook 中调用(不应在循环或者条件语句中调用它)

原因:这样做是为了确保 Hook 的调用顺序在每次渲染中是一致的。React 使用这个顺序来跟踪每个 Hook 的状态和效果。

应对措施:如果你需要在循环或者条件语句中调用它,正确的办法应该是新建一个组件,并将state移入其中

2、useCallback 是一个有用的性能优化工具,但在某些情况下(尤其是在开发和特定的生产场景中),其缓存可能会被丢弃

四、用法

1、跳过组件的重新渲染

背景:默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,有时会导致重新渲染的很慢

初始解决:

将子组件包裹在memo中,例:

import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

好处:如果prop与上一次渲染时相同,这个子组件将跳过重新渲染

不足:在 JavaScript 中,function () {} 或者 () => {} 总是会生成不同的函数,所以因某个变量更改导致组件重新渲染时,那么作为prop传入子组件的函数每次都不一样时,这也导致了memo对性能的优化永远不会生效

优化解决:

思路:将作为prop传入的子函数传递给useCallback,这样可以确保它在多次重新渲染之间是相同的函数(除非是依赖项发生变化它才会变化),也就能让memo知道这时该prop与上次没有变化,子组件也就不会重新渲染。

注:useCallback只应用作性能优化,除非出于某种特定原因,否则不必将一个函数包裹在 useCallback 中

useCallback应用场景:

  • 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。
  • 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 useCallback 中的函数依赖于它,或者依赖于 useEffect 中的函数。

注意:useCallback不会阻止创建函数,你总是在创建一个函数(这很好!),但是如果没有任何东西改变,React会忽略它并返回缓存的函数

2、从记忆回调中更新state

背景:想实现在useCallback回调中基于之前的state来更新state

例:

做法一:指定依赖项

function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);

做法二:使用updater function

function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ 不需要 todos 依赖项

在 React 中,updater function 是一种用于更新组件状态的函数,它允许你基于当前状态计算下一个状态,而不需要将当前状态作为依赖项传useCallback。

好处:

  • 可以帮助减少依赖项的数量,从而避免不必要的重新渲染。
  • 可以确保在状态更新时始终使用最新的状态值。尤其在处理异步操作时,直接引用 状态值 可能会导致不一致的结果。

3、防止频繁触发Effect

背景:想要在Effect内部调用函数

例:聊天室的案例

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };

  }
useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();

  }, [createOptions]); // 🔴 问题:这个依赖在每一次渲染中都会发生改变
  //....

声明依赖的必要性:在 useEffect 中,如果你依赖的变量(例如 createOptions)没有被声明为依赖,React 将无法检测到这些变量的变化。这可能导致副作用使用过时的数据。

声明依赖存在潜在问题:在聊天室的例子中,这意味着每当 createOptions 变化时,useEffect 可能会重新连接到聊天室。假设连接的逻辑在 useEffect 中,这样就会造成一个循环:每次连接后,如果 createOptions 发生变化,就会重新连接,导致不断地连接和断开。

解决方法:在 Effect 中将要调用的函数包裹在 useCallback 中:

 const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ 仅当 roomId 更改时更改
这将确保如果 roomId 相同,createOptions 在多次渲染中会是同一个函数

优化解决方法:消除对函数依赖项的需求,将函数(例createOptions)移入 Effect 内部

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() { // ✅ 无需使用回调或函数依赖!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();

  }, [roomId]); // ✅ 仅当 roomId 更改时更改


http://www.kler.cn/a/402339.html

相关文章:

  • 虚表 —— 数据中的特殊成员
  • 微软Ignite 2024:建立一个Agentic世界!
  • 接口的扩展
  • GitLab 备份与恢复
  • CentOS环境上离线安装python3及相关包
  • Python 网络爬虫操作指南
  • Kubernetes常用命令
  • 2025年软考报名时是什么时候?开考科目如何安排?
  • 使用ufw配置防火墙,允许特定范围IP访问
  • 解决 electron 打包后部分电脑报错 Error: Dynamic Symbol Retrieval Error: Win32 error 126
  • CI配置项,IT服务的关键要素
  • Vue3 + Vite 项目引入 Typescript
  • 应聘美容师要注意什么?博弈美业收银系统/管理系统/拓客系统分享建议
  • 【并发模式】Go 常见并发模式实现Runner、Pool、Work
  • 海外媒体软文发稿:打开全球传播的新窗口-大舍传媒
  • Android CCodec Codec2 (二一)InputBuffers
  • 【工控】线扫相机小结 第三篇
  • 项目进度计划表:详细的甘特图的制作步骤
  • Vulnhub靶场案例渗透[11]- Momentum2
  • Linux进阶:压缩、解压
  • 开源控件:Qt/C++自定义异形窗口和颜色选择器 【工程源码联系博主索要】
  • 【游戏开发】【Unity】基本的Unity概念
  • 深入解析 MySQL 数据库:负载均衡
  • unity 打包WebGL打开后Input无法输入中文,在手机端无法调用输入法(使用WebGLInput)
  • 【Keil5教程及技巧】耗时一周精心整理万字全网最全Keil5(MDK-ARM)功能详细介绍【建议收藏-细细品尝】
  • SSHPASS或者rsync远程自动连接服务器并且在docker中跑脚本