gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 17:55:12 24 4
gpt4 key购买 nike

假设我们有这样的组件

const Example = () => {
const [counter, setCounter] = useState(0);

const increment = () => setCounter(counter => counter + 1);
return (
<div>
<Button onClick={increment} />

<div>{counter}</div>
</div>
);
}
当我通过 onClick处理程序作为 箭头函数 , 我的 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%20shouldn't%20use,previous%20function%20is%20garbage%20collected .
简短的回答是因为每次都重新创建箭头函数,这会损害性能。
这篇文章提出的一种解决方案是包裹在 中。使用回调 钩子(Hook),带空数组。当我改成这个时, 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)还是 总是 将其包装在 useCallback 中???

最佳答案

The short answer is because arrow function is recreated every time, which will hurt the performance.


这是一个普遍的误解。每次重新创建箭头函数 无论哪种方式 (尽管 useCallback 后续的可能会立即被丢弃)。什么 useCallback这样做是为了让您使用回调的子组件在它被内存时不会重新渲染。
我们先来看看误解。考虑 useCallback称呼:
const increment = useCallback(() => setCounter(counter => counter + 1), []);
执行如下:
  • 计算第一个参数 () => setCounter(counter => counter + 1) , 创建函数
  • 计算第二个参数 [] , 创建一个数组
  • 调用 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 如果 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 中回调中的代码和 ComponentB在示例中。 ComponentA (不使用 useCallback )可以使用 count 的值它关闭(在限制内!), () => setCount(count + 1) .但是 ComponentB 中的那个总是必须使用 setter 的回调形式, () => setCount(count => count + 1) .那是因为如果你继续使用第一个 increment您创建的 count它关闭将是陈旧的 - 你会看到计数变为 1,但永远不会进一步。

    最后一点:如果你经常重新渲染一个组件,以至于创建和丢弃它的各种功能可能会导致过多的内存流失(一种罕见的情况),你可以通过使用 ref 来避免它。来看看更新 ComponentB使用 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。 :-)
    这是一个罕见的组件,甚至需要第一级优化,更不用说第二级了;但是当/如果你这样做了,你就是这样做的。

    关于javascript - 我们应该在 React 功能组件的每个函数处理程序中使用 useCallback,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64134566/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com