我们知道,useMemo
和useCallback
主要作用是缓存中间状态,减少无意义的的render
从而提高性能。但是最近我发现我对它们的使用一直有误解!
💎 对useMemo
的误解
请看下面的代码,即使用了useMemo
,在isZero
的没有变的情况下,第二个子组件还是重新渲染了!
import { useCallback, useMemo, useState } from "react"; const Child = ({ value, onClick }) => { return ( <div style={{ height: 100, background: `#${(~~(Math.random() * (1 << 24))).toString(16)}` }} > my value is {value.toString()} </div> ); }; export default function App() { const [count, setCount] = useState(0); const isZero = useMemo(() => !!(count % 3), [count]); const onClick = useCallback(() => setCount(count + 1), [count]); return ( <div className="App"> <button onClick={onClick}>click me</button> <Child value={count} /> <Child value={isZero} /> </div> ); }
💡相关阅读
其实原因在之前的文章中也提到过:
React
每次当组件状态发生改变时,都会从当前组件开始一直到所有叶子节点组件重新渲染。
文中同时也提到了这个问题的解决方案:子组件使用memo
函数包裹,组件就可以按预期渲染了。
但是,此时我们去掉useMemo
,子组件依然是按期望渲染的。
❝❞
memo
和useMemo
类似,都是基于Object.is
的浅比较,仅仅对非引用类型有效。
所以上面的示例中,使用useMemo
是没有意义的。
💎 对useCallback
的误解
然而,上面的示例中,即使onClick
函数不使用useCallback
,组件也会按预期渲染。这是因为不管onClick
的回调函数的缓存是否发生改变,App
组件注定都会被渲染。
所以,现在我们得到了一个合理的代码,如下:
import { memo, useCallback, useMemo, useState } from "react"; const Child = memo(({ value, onClick }) => { return ( <div style={{ height: 100, background: `#${(~~(Math.random() * (1 << 24))).toString(16)}` }} > my value is {value.toString()} </div> ); }); export default function App() { const [count, setCount] = useState(0); // const isZero = useMemo(() => !!(count % 3), [count]); const isZero = !!(count % 3); // const onClick = useCallback(() => setCount(count + 1), [count]); const onClick = () => setCount(count + 1); return ( <div className="App"> <button onClick={onClick}>click me</button> <Child value={count} /> <Child value={isZero} /> </div> ); }
那到底应该何时使用useCallback
呢?
请看下面的例子。在上面的代码基础上添加如下代码:
const onClickMethod = () => console.log("lll"); return ( <div className="App"> <button onClick={onClick}>click me</button> <Child value={count} onClick={onClickMethod} /> <Child value={isZero} onClick={onClickMethod} /> </div> );
此时,发现组件无法按预期渲染了,不管isZero
是否发生变化,第二个Child
组件都会被重新渲染。
这是因为此时的onClickMethod
方法被做为Child
组件的onClick
属性了。
如果现在将onClickMethod
方法使用useCallback
包裹起来,就又正常了。
const onClickMethod = useCallback(() => console.log("lll"), []);
这才是useCallback
的正确用法!
💎 总结
我们在写组件时,应该遵循下面的规律,可以有效提高页面性能:
- 👉尽量多用
memo
方法包裹组件(减少渲染次数) - 👉当子组件的属性为非引用类型的中间状态时请用
useMemo
(减少渲染次数) - 👉当子组件的属性为函数时请用
useCallback
(减少渲染次数) - 👉仅作用在当前组件范围内的属性,尽量不要使用
useMemo
和useCallback
(减少调用)
好了今天的分享到这了,希望你也不要跟我一样再用错useMemo
和useCallback
了!