React 中有许多性能优化的手段,useMemo
与 useCallback
是 hooks 推行后最为常用的两种方法,但是任何优化方案都是有成本的,如果为了图方便给每个函数、组件都套上 useCallback
、Memo
,不仅代码可读性会变差,还会因为参数的传递问题产生非预期的 bug。
正好最近在看代码规范的问题,自己也时常滥用这两个函数,重新整理学习下喵~
简介 先简单看下 React docs 中关于两者的介绍:
useCallback useCallback reference
1 2 3 4 5 6 7 const memoizedCallback = useCallback ( () => { doSomething (a, b); }, [a, b], );
Returns a memoized callback
.
useMemo useMemo reference
1 2 const memoizedValue = useMemo (() => computeExpensiveValue (a, b), [a, b]);
Returns a memoized value
.
相同点 无论 useCallback
还是 useMemo
都是通过缓存来优化性能的,只不过 useCallback
缓存的是函数的引用,useMemo
缓存的则是(函数)返回值。两者可以做到等价:
1 useCallback (fn, deps) === useMemo (() => fn, deps))
这两个函数的第二个参数中,比较值更新的方法采用的是 JavaScript 的绝对相等 ===
,需要注意的是,JS 中函数被视为对象,除非他们引用相等,否则两个对象就算值一样,他们也不是全等的:
1 2 3 4 5 6 7 8 9 10 11 12 const string1 = 'hi' ;const string2 = 'hi' ;console .log (string1 === string2); console .log ('hi' === 'hi' ) const foo1 = ( ) => 'bar' ;const foo2 = ( ) => 'bar' ;const foo1Ref = foo1;console .log (foo1 === foo2) console .log (foo1 === foo1Ref)
区别 useCallback
与 useMemo
返回的内容不同。
useCallback
会返回一个未执行的函数,而 useMemo
则返回执行完函数后的值。
1 2 3 4 5 6 7 8 9 const foo = ( ) => 'bar' ;const testCallback = useCallback (foo, []);const testMemo = useMemo (foo, []);console .log (testCallback); console .log (testMemo); console .log (testCallback ()); console .log (testMemo ());
在真实场景中,我们一般会这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const App = ({...props} ) => { const memoizedCallback = useCallback (() => { someFunc (foo, bar); }, [foo, bar]); const memoizedResult = useMemo (() => someOtherFunc (foo, bar), [ foo, bar, ]); }
何时使用? 在使用函数式组件时,常常会产生组件的重渲染,而组件的重渲染又会带来函数的引用改变或值的重计算,都是一笔开销。
useMemo
适合用于大量数据运算的场景,如:
1 2 3 const value = React .useMemo (() => { return Array (100000 ).fill ('' ).map (v => v); }, [a]);
useCallback
则适用于与 memo
搭配,减少由函数操作带来的重渲染,比如下面这个例子:
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 29 30 31 32 import React , { useState } from "react" ;const Child = ({ handleChildClick } ) => { console .log ("<=== Child render" ); return <button onClick ={handleChildClick} > handleChildClick</button > ; }; const Parent = ( ) => { const [count, setCount] = useState (0 ); const handleChildClick = ( ) => { console .log ("Click Child" ); }; const handleParentClick = ( ) => { console .log ("Click Parent" ); setCount (count => count + 1 ); }; return ( <div > Count: {count} <hr /> <button onClick ={handleParentClick} > handleParentClick</button > <Child handleChildClick ={handleChildClick} /> </div > ); }; export default Parent ;
在不加任何操作的情况下,每次点击 handleParentClick 的按钮,都会打印出 Child render 的信息:
1 2 3 4 5 6 <=== Child render click Parent <=== Child render click Parent <=== Child render
尝试给 handleChildClick
函数套上 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 29 30 31 import React , { useState, useCallback } from "react" ;const Child = ({ handleChildClick } ) => { console .log ("<=== Child render" ); return <button onClick ={handleChildClick} > handleChildClick</button > ; }; const Parent = ( ) => { const [count, setCount] = useState (0 ); const handleChildClick = useCallback (() => { console .log ("Click Child" ); }, []); const handleParentClick = ( ) => { console .log ("Click Parent" ); setCount (count => count + 1 ); }; return ( <div > Count: {count} <hr /> <button onClick ={handleParentClick} > handleParentClick</button > <Child handleChildClick ={handleChildClick} /> </div > ); }; export default Parent ;
发现效果还是一样:
1 2 3 4 5 6 <=== Child render click Parent <=== Child render click Parent <=== Child render
再给 Child 套一层 memo
:
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 29 30 31 32 33 import React , { useState, memo, useCallback } from "react" ;const Child = memo (({ handleChildClick } ) => { console .log ("<=== Child render" ); return <button onClick ={handleChildClick} > handleChildClick</button > ; }); const Parent = ( ) => { const [count, setCount] = useState (0 ); const handleChildClick = useCallback (() => { console .log ("Click Child" ); }, []); const handleParentClick = ( ) => { console .log ("Click Parent" ); setCount (count => count + 1 ); }; return ( <div > Count: {count} <hr /> <button onClick ={handleParentClick} > handleParentClick</button > <Child handleChildClick ={handleChildClick} /> </div > ); }; export default Parent ;
这样就符合预期啦:
为什么该例中需要 useCallback
和 memo
搭配起来用才有效果?
首先,将 Child 组件使用 memo
包裹,以达到 React.PureComponent
的效果,即能够对 props
进行浅比较,但是父组件因为 count
的改变,handleChildClick
会不断刷新引用,只有给这个函数套上 useCallback
进行缓存后,memo
的比较才能起作用,最终使得 Child 组件不被重复渲染,性能得到提升。
Comments