我们是否应该在 React 功能组件的每个函数处理程序中使用 useCallback

2023-12-05

假设我们有这样的组件

const Example = () => {
  const [counter, setCounter] = useState(0);
  
  const increment = () => setCounter(counter => counter + 1); 
  return (
    <div>
      <Button onClick={increment} />
      
      <div>{counter}</div>
    </div>
  );
}

当我通过了onClick处理程序作为箭头函数, my eslint发出警告:

error    JSX props should not use arrow functions        react/jsx-no-bind

正如我从这篇文章的答案中读到的:https://stackoverflow.com/questions/36677733/why-shouldnt-jsx-props-use-arrow-functions-or-bind#:~:text=Why%20you%20 不应该%20use,previous%20function% 20是%20垃圾%20已收集.

简而言之,因为每次都会重新创建箭头函数,这会损害性能。 这篇文章提出的一个解决方案是将使用回调钩子,带有空数组。当我更改为这个时,eslint 警告真的消失了。

const Example = () => {
  const [counter, setCounter] = useState(0);
  
  const increment = useCallback(() => setCounter(counter => counter + 1), []);
  
  return (
    <div>
      <Button onClick={increment} />
      
      <div>{counter}</div>
    </div>
  );
}

不过,也有另一种观点认为过度使用由于 useCallback 的开销,useCallback 最终会降低性能。一个例子在这里:https://kentcdodds.com/blog/usememo-and-usecallback

这真的让我很困惑吗?因此,对于功能组件,在处理内联函数处理程序时,我应该只编写箭头函数(忽略 eslint)还是always将其包装在 useCallback 中???


简而言之,因为每次都会重新创建箭头函数,这会损害性能。

这是一个普遍的误解。每次都会重新创建箭头函数无论哪种方式(虽然与useCallback后续的可能会立即扔掉)。什么useCallback这样做的目的是使您使用回调的子组件在被记忆后不会重新渲染。

我们先来看看错误的观念。考虑useCallback call:

const increment = useCallback(() => setCounter(counter => counter + 1), []);

执行起来是这样的:

  1. 评估第一个参数,() => setCounter(counter => counter + 1), 创建一个函数

  2. 评估第二个参数,[],创建一个数组

  3. Call useCallback用这两个参数,返回一个函数

如果不使用,请与现有的进行比较useCallback:

const increment = () => setCounter(counter => counter + 1);

这要简单得多:创建函数。那么它就不必执行上面的#2 和#3 操作。

让我们继续讨论什么useCallback实际上确实很有用。我们看一下回调函数用在什么地方:

<Button onClick={increment} />

现在,假设Button被记忆为React.memo或类似的。如果increment每次组件渲染时都会发生变化,然后Button每次组件发生变化时都必须重新渲染;它不能在渲染之间重复使用。但如果increment在渲染之间是稳定的(因为你使用了useCallback带空数组),调用的记忆结果Button可以重复使用,不必再次调用。

这是一个例子:

const { useState, useCallback } = React;

const Button = React.memo(function Button({onClick, children}) {
    console.log("Button called");
    return <button onClick={onClick}>{children}</button>;
});

function ComponentA() {
    console.log("ComponentA called");
    const [count, setCount] = useState(0);
    // Note: Safe to use the closed-over `count` here if `count `updates are
    // triggered by clicks or similar events that definitely render, since
    // the `count` that `increment` closes over won't be stale.
    const increment = () => setCount(count + 1);
    return (
        <div>
            {count}
            <Button onClick={increment}>+</Button>
        </div>
    );
}

function ComponentB() {
    console.log("ComponentB called");
    const [count, setCount] = useState(0);
    // Note: Can't use `count` in `increment`, need the callback form because
    // the `count` the first `increment` closes over *will* be slate after
    // the next render
    const increment = useCallback(
        () => setCount(count => count + 1),
        []
    );
    return (
        <div>
            {count}
            <Button onClick={increment}>+</Button>
        </div>
    );
}

ReactDOM.render(
    <div>
        A:
        <ComponentA />
        B:
        <ComponentB />
    </div>,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

请注意,单击中的按钮ComponentA总是打电话Button再次,但单击按钮ComponentB没有。

你想什么时候这样做?这很大程度上取决于您,但是当您的组件的状态以不影响内容的方式频繁更改时,这可能是有意义的increment因此不影响Button and if Button渲染时必须做大量工作。Button可能不会,但其他子组件可能会。

例如,useCallback在我之前的例子中,如果你使用的话可能毫无意义count作为按钮的文本,因为这意味着Button必须重新渲染,无论:

const { useState, useCallback } = React;

const Button = React.memo(function Button({onClick, children}) {
    console.log("Button called");
    return <button onClick={onClick}>{children}</button>;
});

function ComponentA() {
    console.log("ComponentA called");
    const [count, setCount] = useState(0);
    // Note: Safe to use the closed-over `count` here if `count `updates are
    // triggered by clicks or similar events that definitely render, since
    // the `count` that `increment` closes over won't be stale.
    const increment = () => setCount(count + 1);
    return (
        <div>
            <Button onClick={increment}>{count}</Button>
        </div>
    );
}

function ComponentB() {
    console.log("ComponentB called");
    const [count, setCount] = useState(0);
    // Note: Can't use `count` in `increment`, need the callback form because
    // the `count` the first `increment` closes over *will* be slate after
    // the next render
    const increment = useCallback(
        () => setCount(count => count + 1),
        []
    );
    return (
        <div>
            <Button onClick={increment}>{count}</Button>
        </div>
    );
}

ReactDOM.render(
    <div>
        A:
        <ComponentA />
        B:
        <ComponentB />
    </div>,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

另请注意useCallback不是免费的,它会影响回调中的代码。查看回调中的代码ComponentA and ComponentB在示例中。ComponentA(这不使用useCallback)可以使用的值count它会关闭(在限制范围内!),() => setCount(count + 1)。但其中的一个ComponentB总是必须使用setter的回调形式,() => setCount(count => count + 1)。那是因为如果你继续使用第一个increment你创造的,count它关闭就会过时——您会看到计数变为 1,但不会再进一步​​了。


最后一点:如果您经常重新渲染组件,以至于创建并丢弃您传递给的各种函数useCallback or useMemo可能会导致过多的内存搅动(arare情况),您可以通过使用 ref 来避免这种情况。我们来看看更新情况ComponentB使用 ref 而不是useCallback:

const incrementRef = useRef(null);
if (!incrementRef.current /* || yourDependenciesForItChange*/) {
    // Note: Can't use `count` in `increment`, need the callback form because
    // the `count` the first `increment` closes over *will* be slate after
    // the next render
    incrementRef.current = () => setCount(count => count + 1);
}
const increment = incrementRef.current;

这只会创建increment函数一次(在该示例中,因为我们没有任何依赖项),它不会创建和丢弃像使用这样的函数useCallback做。它之所以有效,是因为 ref 的初始值为null,然后第一次调用组件函数时,我们看到它是null,创建函数,并将其放在 ref 上。所以increment仅创建一次。

该示例确实重新创建了我们传递的函数setCount每次increment叫做。也可以避免这种情况:

const incrementRef = useRef(null);
if (!incrementRef.current) {
    // Note: Can't use `count` in `increment`, need the callback form because
    // the `count` the first `increment` closes over *will* be slate after
    // the next render
    const incrementCallback = count => count + 1;
    incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
const { useState, useRef } = React;

const Button = React.memo(function Button({onClick, children}) {
    console.log("Button called");
    return <button onClick={onClick}>{children}</button>;
});

function ComponentA() {
    console.log("ComponentA called");
    const [count, setCount] = useState(0);
    // Note: Safe to use the closed-over `count` here if `count `updates are
    // triggered by clicks or similar events that definitely render, since
    // the `count` that `increment` closes over won't be stale.
    const increment = () => setCount(count + 1);
    return (
        <div>
            {count}
            <Button onClick={increment}>+</Button>
        </div>
    );
}

function ComponentB() {
    console.log("ComponentB called");
    const [count, setCount] = useState(0);
    const incrementRef = useRef(null);
    if (!incrementRef.current) {
        // Note: Can't use `count` in `increment`, need the callback form because
        // the `count` the first `increment` closes over *will* be slate after
        // the next render
        const incrementCallback = count => count + 1;
        incrementRef.current = () => setCount(incrementCallback);
    }
    const increment = incrementRef.current;
    return (
        <div>
            {count}
            <Button onClick={increment}>+</Button>
        </div>
    );
}

ReactDOM.render(
    <div>
        A:
        <ComponentA />
        B:
        <ComponentB />
    </div>,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

就避免不必要的函数创建而言,这确实达到了 11。 :-)

这是一个罕见的组件,甚至需要第一级优化,更不用说第二级优化了;但当/如果你这样做,那就是你这样做的方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我们是否应该在 React 功能组件的每个函数处理程序中使用 useCallback 的相关文章

  • 如何根据按钮单击折叠和展开 Kendo UI 树视图中的所有树节点?

    这是行不通的 您可以使用此代码 1 崩溃 折叠kendoTree查看文档 http docs kendoui com api web treeview methods collapse treeview kendoTreeView var
  • 如何使用 axios / jest 测试失败的请求

    我创建了一个非常小的应用程序 如果您传递硬币和数量 它可以计算为某些加密货币支付的总价格 我想测试错误 但我总是收到 收到的承诺已解决而不是被拒绝 我相信这是因为如果 url 错误 axios 仍然会解决承诺 我遇到的第二个问题是 我尝试测
  • 来自 DataURL 的 Blob?

    Using FileReader s readAsDataURL 我可以将任意数据转换为数据 URL 有没有办法将数据 URL 转换回Blob使用内置浏览器 API 的实例 用户 Matt 一年前提出了以下代码 如何在javascript中
  • HTML5 服务器端事件:EventSource 与包装的 WebSocket

    HTML5 服务器发送事件 SSE API 是否只是 HTML5 WebSocket 之上的受限制的 基于事件的 API 在我看来 一个EventSource只是一个WebSocket that Cannot send data 使用tex
  • JavaScript 变量赋值与 OR 对比 if 检查[重复]

    这个问题在这里已经有答案了 在 JavaScript 中 我最近意识到你可以使用 OR 赋值的逻辑运算符 我想知道这是否被认为是不好的做法 特别是 我有一些具有可选数组输入的函数 如果输入是null or undefined我应该将它设置为
  • 是否可以将反应组件导出为非反应项目中的函数

    有没有办法在非 React 项目中将 React 组件导出为函数并传入 props 作为函数的参数 我最近用 create react app 完成了一个 React 项目 现在我想将它用于其他非 React 项目 纯 Javascript
  • 是否可以覆盖 javaScript 原始数据类型?

    问题是不言自明的 我知道可以扩展原始数据类型 例如string但有可能覆盖它吗 这是在采访中被问到的一个问题 不 你不能覆盖任何东西 Ecma脚本定义原始类型 http es5 github com x4 3 2 Undefined Nul
  • 具有行组的 JQuery 斑马条纹表

    我通常将斑马条纹表行设置为奇数 偶数 如下所示 效果很好 table tbody tr visible even this addClass even table tbody tr visible odd this addClass odd
  • 提交前验证表单(比检查空字段更复杂)

    我有一个包含时间输入的表单 具体来说 开放时间和结束时间 当按下提交按钮时 它会转到一个 php 页面 其中这些输入将添加到数据库中 在允许提交表单之前我想检查一些事情 例如 我想确保开始时间早于 小于 结束时间 这是表格 Opens
  • 光滑的轮播缓动示例

    我正在使用 Slick Carousel http kenwheeler github io slick http kenwheeler github io slick 但不知道如何合并不同的幻灯片切换 有人有例子可以分享吗 这是我目前拥有
  • 专用网络:web3.eth.getAccounts() 始终发送空数组

    我正在运行一个私人以太坊网络 我确实用https aws amazon com blockchain templates 整个设置已经完成 AWS 上的设置看起来正确 现在 我正在尝试创建帐户并检索所有这些帐户 为此 我使用以下方法 Web
  • 空 URL 哈希导致页面在 js 事件上跳转

    我有一个带有下一个和上一个按钮的照片库 如果我的某个 javascript 方法由于某种原因被破坏 那么当单击其中一个按钮时 它会向 url 添加一个哈希值 即 www google com 我知道可以给散列一个 div id 来跳转到页面
  • 使用 Lodash 的 TypeScript:_.map(["123", " 234 "], _.trim) 返回 boolean[]?

    我有一个字符串数组 它们已像这样分割 var searchValue 600 800 123 180 var groups searchValue split gt 600 800 123 180 因此项目周围可能存在空格 并且我想删除空格
  • 在声明组件选择器时添加指令 - Angular 7

    我正在学习 Angular 并通过单击按钮动态创建组件 我正在尝试使用 Angular Material 的拖放功能来拖动这些创建的组件以对它们进行排序 我的基本组件 html 中有以下代码 div style margin 20px di
  • ThreeJS无法加载Json文件

    首先 我已经读过这个问题 https stackoverflow com questions 17201888 three js exporter export object not working with jsonloader r58没
  • 使用 jQuery Tablesorter 操作后如何恢复当前页面?

    我正在使用 tablesorter 但无法找到有关插件 tablesorter 寻呼机的任何文档 问题是我有一个显示一些数据的表 并且在每一行中都有一个删除链接 该链接附加了要删除的元素的唯一标识符 显然 是否可以保存我正在删除的页面 然后
  • 有没有办法防止输入 type=“number” 获得多个点值?

    我只想得到十进制值 如 1 5 0 56 等 但它允许多个点 有什么办法可以预防吗 您可以使用pattern属性
  • 错误:创建 React Native 项目版本 0.59.9 时找不到 template.config.js

    当我尝试创建 React Native 项目版本 0 59 9 时 出现以下错误 错误错误 无法在 react native 模板中找到 var folders zc h93bvpb573q24 5ynvgkn1wc0000gn T rnc
  • 如何根据所需表单输入的值更改 CSS 样式

    我想知道如何编写 javascript 来改变所需的表单元素的样式 如果它们有价值的话就改变它们 我想要做的是当所需的文本字段为空时 在它们周围有一个彩色边框 并在它们有值时删除边框样式 我想做的是编写一个 javascript 函数来检查
  • 获取淘汰赛中被点击元素的索引

    获取无序列表中单击元素的索引的最佳方法是什么 让我举个例子 假设我有以下 HTML 代码 ul li p p li ul 现在我有以下 javascript 代码来获取索引 self itemClicked function data it

随机推荐

  • 选择优质包第 2 部分

    资源和链接 如何为 GitHub 项目编写出色的自述文件 dbader org 开源许可证解释 choosealicense com
  • Python 的 map() 函数:转换 Iterables(摘要)

    在本课程中 您学习了如何map 工作原理以及如何使用它来处理可迭代对象 你还了解了一些Pythonic您可以用来替换的工具map 在你的代码中 您现在知道如何 使用Pythonmap 使用map 到过程和转换不使用显式循环的迭代 结合map
  • Python 3.9 中很酷的新功能(概述)

    Python 3 9 来了 在过去的一年里 来自世界各地的志愿者一直致力于 Python 的改进 虽然 Beta 版本已经发布了一段时间 但 Python 3 9 的第一个正式版本已于2020 年 10 月 5 日 Python 的每个版本
  • 使用需求文件

    A 需求文件是项目所有依赖项的列表 这包括依赖项所需的依赖项 它还包含每个依赖项的特定版本 用双等号指定 pip freeze将列出当前项目的依赖关系stdout 此 shell 命令会将其导出为名为的文件requirements txt
  • 多处理模块

    在本课程中 您将了解为什么要采用这种方法 因为你的代码写在函数式编程风格 你可以相当容易地并行化它 有一个平行的map构建您可以使用的 这样 您就可以并行运行处理步骤 您将导入multiprocessing模块 因为它具有并行运行此操作所需
  • JSF:从 Jar 中提供资源

    我正在创建几个装有 Facelets 模板的罐子 供整个组织使用 在 JSF 1 2 中 此功能并不是开箱即用的 堆 Jboss EAP 5 1 Seam 2 2 富脸3 3 3 在我看来 我主要需要两个资源 查找 Faclets 资源的资
  • 在特定的开始、结束日期和时间限制内运行 Quartz Scheduler 作业

    我正在使用 Quartz Scheduler 来执行重复性任务 但我遇到了麻烦 在我的服务器端 我的用户想要指定一些日期范围 例如From 2013 09 27 with in 09 00 AM 12 00 PM to 2013 09 30
  • Pinterest 的官方 OAuth2 流程似乎返回无效的访问令牌

    我按照 Pinterest 的说明进行操作验证用户身份完成步骤 2 后 我手里拿着一个访问令牌 但是 当我使用此访问令牌进行 API 调用时 我收到以下响应 status failure code 3 host coreapp devpla
  • 有没有办法让 JQuery ajax 成功函数访问它所包含的对象?

    我有这样的 JavaScript function Cat this meow function meow ajax do AJAX call success this meow var TopCat new Cat 这不起作用 因为 th
  • 如何在正则表达式子例程中访问捕获组?

    我有一个模式想在几个地方使用 但我想访问该模式的内部部分 有没有办法做到这一点 In this 简化的例子 DEFINE lt isa gt s
  • 全局内存写入在 CUDA 中是否被视为原子?

    全局内存写入在 CUDA 中是否被视为原子操作 考虑以下 CUDA 内核代码 int idx blockIdx x blockDim x threadIdx x int gidx idx 1000 globalStorage gidx so
  • 通过参考返回

    PHP 文档中写道 不要使用按引用返回来提高性能 引擎 会自动对其进行优化 我希望返回对数组的引用 这是我的类的属性 PHP 如何优化这个 因为数组不是对象 如果数组有10亿个条目 如果我不通过引用传递它 我不会得到两个在内存中存储有10亿
  • MySQL SUM 函数在多个连接中

    嗨 这是我的情况 我有那些桌子 Customer id name Charges id amount customer id Taxes id amount charge id 所以我想计算费用和税费的总和 然后按客户 ID 分组 这是我的
  • EL 空运算符在 JSF 中如何工作?

    在 JSF 中 可以使用 EL 空运算符来呈现或不呈现组件 rendered not empty myBean myList 据我了解 该运算符既可以用作空检查 也可以检查列表是否为空 我想对我自己的自定义类的某些对象进行空检查 我需要实现
  • 访问共享内存进行读取时锁定

    如果我以只读方式访问共享内存 请检查某个条件if 块 我还应该锁定互斥锁吗 例如 mutex lock if var shared memory mutex unlock 这里是否需要锁定并且是良好的做法 如果您正在读取的变量可以同时写入
  • 如何使用 Matlab 按字母顺序对属性值对进行排序

    我想向现有文件添加属性值对 同时 所有属性应按字母顺序排序 例如 Info property 1 value 1 system property 2 value 2 我如何添加其他属性 以便所有属性都按字母顺序排序 我能够使用以下命令将属性
  • 使用 HTML5 音频更改 在 Chrome 中有效,但在 Safari 中无效

    我正在尝试制作一个可在每个主要浏览器中使用的 HTML5 音频播放列表 Chrome Safari Firefox IE9 但是 我不知道如何以跨浏览器兼容的方式更改源 UPDATED例如 更改
  • 迭代器无法正确访问问题

    我正在尝试使用迭代器访问向量的元素 但我得到奇怪的输出 std vector
  • Excel VBA 循环遍历可见的筛选行

    我有一个带有自动过滤器的 Excel 表格 在过滤表中 我只过滤了几行 我的目标是迭代所有可见行以收集数据以复制到另一张工作表 我想要一种方法来收集具有第一个可见行号的变量 我的草稿代码是 Dim cnp As String Dim nom
  • 我们是否应该在 React 功能组件的每个函数处理程序中使用 useCallback

    假设我们有这样的组件 const Example gt const counter setCounter useState 0 const increment gt setCounter counter gt counter 1 retur