React Hooks 中的 useCallback 其实并不能避免 function 在 render 时的 allocation

1
2
3
4
5
6
7
8
9
10
11
12
13
const FC = () => {
const [visible, setVisible] = useState(false)

const handleShow = () => setVisible(true)
const handleHide = () => setVisible(false)

return (
<>
<button onClick={handleShow}>hide</button>
<button onClick={handleHide}>hide</button>
</>
)
}

上面的例子中,每次 FC 的渲染都会重新定义一次,handleShowhandleHide 两个 function

借助 useCallback 可以改写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
const FC = () => {
const [visible, setVisible] = useState(false)

const handleShow = useCallback(() => setVisible(true), [])
const handleHide = useCallback(() => setVisible(false), [])

return (
<>
<button onClick={handleShow}>hide</button>
<button onClick={handleHide}>hide</button>
</>
)
}

实际上,这样组件的性能会更差,因为 useCallback 并不能阻止这两个 function 被重新定义,反而增加了运算量:

1
const handleShow = useCallback(() => setVisible(true), [])

可以拆解成

1
2
const handleShow = () => setVisitble(true)
React.useCallback(handleShow, [])

比原来还多一步

真正需要 useCallback 的情形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const FC = () => {
const [visible, setVisible] = useState(false)

const handleShow = useCallback(() => setVisible(true), [])
const handleHide = useCallback(() => setVisible(false), [])

return (
<>
<Button onClick={handleShow}>show</Button>
<Button onClick={handleHide}>hide</Button>
</>
)
}

const Button = (props) => {
const { onClick } = props

useEffect(
() => {
console.log(onClick) // 每次 onClick 改变,做些什么
},
[onClick]
)

return (
<button onClick={onClick}>{props.children}</button>
)
}

上面的这种情况,在 <Button> 组件中,用到了 useEffect。 而 useEffect 对于对象类型的 props 是比较他们的 reference,来判断要不要执行。

如果在 FC 中没有用 useCallback,那么每次 FC 的渲染都会定义全新的 handleShow(每次的 reference 都不一样),所以都会在 <Button> 中触发 console.log(onClick)

所以 useCallback 就是用来控制相同的依赖参数下,是否需要保持这个 funciton 的 reference,而他并不能避免重新定义这个 function。

相关链接:When to useMemo and useCallback