假设我们有这样的组件
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
正如我从这篇文章的回答中读到的:这个函数是一个垃圾收集器,它是一个垃圾收集器。
简单的回答是因为每次都要重新创建箭头函数,这会影响性能。这篇文章提出的一个解决方案是用一个useCallback钩子 Package ,并使用空数组。当我改为这样时,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)还是总是将其 Package 在useCallback中???
1条答案
按热度按时间2vuwiymt1#
简短的回答是因为每次都重新创建箭头函数,这会损害性能。
这是一个常见的误解。arrow函数每次都被重新创建无论如何(尽管使用
useCallback
,后续的函数可能会立即被丢弃)。useCallback
所做的是使你使用回调的子组件在被记忆化时不会重新呈现。让我们先来看看误解,考虑一下
useCallback
调用:它是这样执行的:
1.计算第一个参数
() => setCounter(counter => counter + 1)
,创建函数1.计算第二个参数
[]
,创建一个数组1.用这两个参数调用
useCallback
,返回一个函数与不使用
useCallback
时的结果进行比较:那就简单多了:创建函数,这样就不必执行上面的#2和#3。
让我们来看看
useCallback
实际上做了什么有用的事情。现在,假设
Button
被存储为React.memo
或类似的存储,如果increment
在每次组件呈现时都改变,那么Button
必须在每次组件改变时重新呈现;但是如果increment
在渲染之间是稳定的(因为你使用了useCallback
和一个空数组),那么调用Button
的记忆结果可以被重用,不需要再次调用。下面是一个例子:
第一个
请注意,单击
ComponentA
中的按钮始终会再次调用Button
,但单击ComponentB
中的按钮不会。这在很大程度上取决于你,但是当你的组件的状态频繁改变,并且不影响
increment
的内容,也不影响Button
和时,如果Button
在呈现时必须做大量的工作,那么这可能是有意义的。Button
可能不会,但是其他子组件可能会。例如,如果您使用
count
作为按钮的文本,则我前面的示例中的useCallback
可能没有意义,因为这意味着无论如何Button
都必须重新呈现:一个
还要注意,
useCallback
不是免费的,它会影响回调中的代码。请查看示例中ComponentA
和ComponentB
回调中的代码。ComponentA
(它不使用useCallback
)可以使用它关闭的count
的值但是ComponentB
中的函数必须使用setter的回调形式() => setCount(count => count + 1)
,这是因为如果你一直使用你创建的第一个increment
,它关闭的count
将失效-您将看到计数变为1,但不会再进一步。最后一点:如果您频繁地重新呈现组件,以至于创建和丢弃传递给
useCallback
或useMemo
的各种函数可能会导致过多的内存混乱(***罕见***情况),您可以通过使用ref来避免这种情况。让我们看看如何将ComponentB
更新为使用ref而不是useCallback
:这只会创建一次
increment
函数(在这个例子中,因为我们没有任何依赖关系),它不会像使用useCallback
那样创建和丢弃函数。它工作是因为ref的初始值是null
,然后第一次调用组件函数时,我们看到它是null
,创建函数,并将其放在ref上。因此increment
只创建一次。这个例子确实重新创建了每次调用
increment
时传递给setCount
的函数,也可以避免这种情况:在避免不必要的函数创建方面,这真的是11。:-)
这是一个罕见的组件,需要甚至第一级的优化,更不用说第二级;但是如果你做了,那就是你做。