[英]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:是这样执行的:
Evaluate the first argument, () => setCounter(counter => counter + 1)
, creating a function评估第一个参数
() => setCounter(counter => counter + 1)
,创建一个函数
Evaluate the second argument, []
, creating an array评估第二个参数
[]
,创建一个数组
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.现在,假设
Button
被React.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.查看示例中
ComponentA
和ComponentB
的回调中的代码。 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.
重新定义一个函数没有问题,但在每次状态更新时重新运行它是有问题的,而且通常很危险。
useCallback
when the function needs to be inside dependency array of useEffect
useCallback
当函数必须是内部的依赖性阵列useEffect
There are two cases I can think of right now:目前我能想到的有两种情况:
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>
);
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.