刚开始使用hook的时候,听旁边的小伙伴说,useCallback的作用是缓存函数,可以不每次渲染的重新创建函数,提升性能,然后给函数式组件的每个函数都使用了useCallback,但是用了几天后觉得,不对呀,既然是这样 react为什么不把所有函数默认useCallback而让使用者去加,这样多此一举呢,于是自己就研究了一下,到底什么时候该使用useCallback。
举个例子:
function CandyDispenser() {
const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
const [candies, setCandies] = React.useState(initialCandies)
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
return (
<div>
<h1>Candy Dispenser</h1>
<div>
<div>Available Candy</div>
{candies.length === 0 ? (
<button onClick={() => setCandies(initialCandies)}>refill</button>
) : (
<ul>
{candies.map(candy => (
<li key={candy}>
<button onClick={() => dispense(candy)}>grab</button> {candy}
</li>
))}
</ul>
)}
</div>
</div>
)
}
下面这两种写法哪种性能更好呢?
const dispense = React.useCallback(candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}, [])
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
从我们的具体例子中退后一步,甚至从React那里考虑一下:执行的每行代码都有成本。让我稍微重构一下?useCallback ?的例子来更清楚地说明事情(没有实际的改变,只是移动下代码):
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
const dispenseCallback = React.useCallback(dispense, [])
这是原来的:
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
是的,除了useCallback 版本做了更多的工作之外,它们完全相同。 我们不仅需要定义函数,还要定义一个数组([] )并调用?React.useCallback ,它本身会设置属性和运行逻辑表达式等。
因此,在这两种情况下,JavaScript 必须在每次渲染中为函数定义分配内存,并且根据?useCallback ?的实现方式,你可能会获得更多的函数定义内存分配(实际情况并非如此,但重点还在这里)
在组件的第二次渲染中,原来的?dispense ?函数被垃圾收集(释放内存空间),然后创建一个新的?dispense ?函数。 但是使用?useCallback ?时,原来的?dispense ?函数不会被垃圾收集,并且会创建一个新的?dispense ?函数,所以从内存的角度来看,这对性能影响更大。
?性能优化不是免费的。 它们总是带来成本,但这并不总是带来好处来抵消成本。
所以应该什么时候使用?useCallback?
useCallback 要配合子组件的?shouldComponentUpdate ?或者?React.memo ?一起来使用的,否则就是反向优化。
举个例子:
// Button.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count2, setCount2] = useState(0);
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, []);
return (
<Button
count={count2}
onClickButton={handleClickButton2}
>Button2</Button>
);
}
|