简体   繁体   English

Should we use useCallback in every function handler in React Functional Components

[英]Should we use useCallback in every function handler in React Functional Components

let's say we have the components like this

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

When I passed the onClick handler as an arrow function, my eslint throw a warning:

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

As I read from an answer from this post: 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.

The short answer is because arrow function is recreated every time, which will hurt the performance. One solution proposed from this post is to wrapped in a useCallback hook, with empty array. And when I change to this, the eslint warning really disappear.

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

However, there is also another opinion saying that overusing useCallback will eventually slowdown the performance due to the overheads of useCallback. One example is here: https://kentcdodds.com/blog/usememo-and-usecallback

This is making me really confused? So for Functional Components, when dealing with inline function handler, should I just write the arrow function (ignore the eslint) or always wrap it in a useCallback ???

The short answer is because arrow function is recreated every time, which will hurt the performance.简短的回答是因为每次都会重新创建箭头函数,这会损害性能。

This is a common misconception.这是一个普遍的误解。 The arrow function is recreated every time either way (although with useCallback subsequent ones may be thrown away immediately).箭头功能重建每次要么时间的方式(虽然useCallback后续各个可立即扔掉)。 What useCallback does is make it possible for the child component you use the callback on to not re-render if it's memoized. useCallback所做的是使您使用回调的子组件在记忆化时不会重新渲染。

Let's look at the misconception first.我们先来看看这个误解。 Consider the useCallback call:考虑useCallback调用:

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

That's executed like this:是这样执行的:

  1. Evaluate the first argument, () => setCounter(counter => counter + 1) , creating a function评估第一个参数() => setCounter(counter => counter + 1)创建一个函数

  2. Evaluate the second argument, [] , creating an array评估第二个参数[] ,创建一个数组

  3. Call useCallback with those two arguments, get back a function使用这两个参数调用useCallback ,返回一个函数

Compare with what you have if you don't use useCallback :如果您不使用useCallback请与您拥有的进行比较:

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

That's much simpler: Create the function.这要简单得多:创建函数。 It doesn't then have to do #2 and #3 above.然后它不必执行上面的#2 和#3。

Let's move on to what useCallback actually does that's useful.让我们继续讨论useCallback实际做了哪些有用的事情。 Let's look at where the callback is used:我们来看看回调的用处:

<Button onClick={increment} />

Now, suppose Button is memoized with React.memo or similar.现在,假设ButtonReact.memo或类似的东西记住了。 If increment changes every time your component renders, then Button has to re-render every time your component changes;如果每次你的组件渲染increment改变,那么每次你的组件改变时Button都必须重新渲染; it can't be reused between renders.它不能在渲染之间重复使用。 But if increment is stable between renders (because you used useCallback with an empty array), the memoized result of calling Button can be reused, it doesn't have to be called again.但是如果在渲染之间increment是稳定的(因为你使用useCallback和一个空数组),调用Button的记忆结果可以被重用,它不必再次调用。

Here's an example:下面是一个例子:

 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>

Note that clicking the button in ComponentA always calls Button again, but clicking the button in ComponentB doesn't.请注意,单击ComponentA的按钮始终会再次调用Button ,但单击ComponentB中的按钮不会。

When do you want to do that?你想什么时候这样做? That's largely up to you, but it probably makes sense when your component's state will change frequently in ways that don't affect the contents of increment and thus don't affect Button and if Button has to do significant work when rendered.这在很大程度上取决于您,但是当您的组件状态以不影响increment内容的方式频繁更改时,这可能是有意义的,因此不影响Button并且如果Button在呈现时必须做大量工作。 Button probably doesn't, but other child components may. Button可能不会,但其他子组件可能会。

For instance, the useCallback in my previous example is probably pointless if you use count as the text of the button, since that means Button has to re-render regardless:例如,如果您使用count作为按钮的文本,我之前示例中的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> <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>

Also note that useCallback isn't free, it impacts the code in the callback.另请注意, useCallback不是免费的,它会影响回调中的代码。 Look at the code in the callbacks in ComponentA and ComponentB in the examples.查看示例中ComponentAComponentB的回调中的代码。 ComponentA (which doesn't use useCallback ) can use the value of count that it closes over (within limits!), () => setCount(count + 1) . ComponentA (不使用useCallback )可以使用它关闭的count值(在限制范围内!), () => setCount(count + 1) But the one in ComponentB always has to use the callback form of the setter, () => setCount(count => count + 1) .但是ComponentB那个总是必须使用 setter 的回调形式, () => setCount(count => count + 1) That's because if you keep using the first increment you create, the count it closes over will be stale — you'd see the count go to 1, but never further.这是因为如果您继续使用您创建的第一个increment ,它关闭的count将是陈旧的——您会看到计数变为 1,但不会再增加。

In my opinion, useCallback is not for performance.在我看来, useCallback不是为了性能。 I cannot think of any reason that defining a function is really expensive.我想不出任何原因来定义一个函数真的很昂贵。 Unlike useMemo , useCallback just memoize the function and does not actually execute it.useMemo不同, useCallback只是useCallback函数而不实际执行它。

So when should we use it?那么我们应该什么时候使用它呢?

The main reason is to prevent re-running a function unnecessarily.主要原因是为了防止不必要地重新运行函数。 Redefining a function is not problematic, but re-running it on every state update is buggy and often dangerous.重新定义一个函数没有问题,但在每次状态更新时重新运行它是有问题的,而且通常很危险。

TL DR;特尔博士; Only use useCallback when the function needs to be inside dependency array of useEffect仅使用useCallback当函数必须是内部的依赖性阵列useEffect

There are two cases I can think of right now:目前我能想到的有两种情况:

  1. For example, a function is async, and we need to run it when any of dependency has been changed:例如,一个函数是异步的,我们需要在任何依赖项发生变化时运行它:
const [data, setData] = useState([]);
const [filter, setFilter] = useState({});

const fetchData = useCallback(async () => {
  const response = await fetchApi(filter);
  setData(response.data);
}, [filter]);

useEffect(() => {
  fetchData();
}, [fetchData]);

(If the function is not async, we may use useEffect directly without using useCallback ) (如果函数不是异步的,我们可以不使用useEffect直接使用useCallback

However, no need to wrap it with useCallback when it is only run by user interaction:但是,当它仅由用户交互运行时,不需要用useCallback包装它:

const [data, setData] = useState([]);
const [filter, setFilter] = useState({});

const fetchData = async () => {
  const response = await fetchApi(filter);
  setData(response.data);
};

return (
  <button onClick={fetchData}>Fetch Data</button>
);
  1. When you should pass a function prop to 3rd-party component:何时应该将函数 prop 传递给 3rd-party 组件:
const onAwesomeLibarayLoaded = useCallback(() => {
  doSomething(state1, state2);
}, [state1, state2]);

<AwesomeLibrary 
  onLoad={onAwesomeLibarayLoaded}
/>

Because AwesomeLibrary component might do something like example 1 with passed onLoad function:因为AwesomeLibrary组件可能会使用传递的onLoad函数执行类似于示例 1 的操作:

const AwesomeLibarary = ({onLoad}) => {
  useEffect(() => {
    // do something
    onLoad();
  }, [onLoad]);
};

If you're sure it is not inside useEffect then it is OK even if you don't use useCallback .如果您确定它不在useEffect那么即使您不使用useCallback

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 React 函数式组件中,我们应该更喜欢使用 useCallback 还是应该使函数更纯并保留在组件之外? - In React functional components, should we prefer using useCallback or should we make functions pure and keep outside the component? 我们可以使用 HOC 在 React 中包装功能组件吗 - Can we use HOC to wrap functional components in React 在创建React组件时,我应该使用功能组件,React.createClass还是扩展React.Component? - When creating React components should I use functional components, React.createClass or extends React.Component? 跨多个组件重用 React.useCallback() function - Reuse React.useCallback() function across multiple components React 函数式组件中的函数应该被包装吗? - Should functions within React functional components be wrapped? 如何在功能组件中混合 useCallback 和 useRef - How to mix useCallback with useRef in functional components 什么时候在 React 中使用 useCallback? - When to use useCallback in React? 反应功能组件-说它不是 function - React functional components- say its not a function React 函数式无状态组件、PureComponent、Component; 有什么区别,我们什么时候应该使用什么? - React functional stateless component, PureComponent, Component; what are the differences and when should we use what? 我应该将此 function 包装在 useCallback 中吗? - Should I wrap this function in a useCallback?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM