前言
在看完了一遍 React 的基础内容后,现在又回过头来看 React 组件更新与优化相关的知识了,在使用 Hook 之后,与这个话题脱不开关系的便有 useMemo
、useCallback
和 React.memo
了,正好项目优化时碰到了这么些知识,刚好总结一番。
三者区别
首先从使用位置来看可以划分为两种:
那么分别来看有什么作用吧~
React.memo
React.memo
主要是为了缓存组件,当父组件 state 更新,但未改变子组件的入参的 state 值时,子组件应该不自我更新,而是用先前的缓存值:
父组件:
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
| import React, { useState, useCallback, useEffect } from 'react'; import Counter from './memo-test/counter';
function App() { const [counter1, setCounter1] = useState(1); const [counter2] = useState(2);
const updateCounter1 = () => { setCounter1(counter1 + 1); };
useEffect(() => { console.log('parent update'); });
return ( <> <button onClick={updateCounter1}>更新 Counter1</button>
<h3>Counter 1:</h3> <Counter count={counter1} cname='Counter1'></Counter>
<h3>Counter 2:</h3> <Counter count={counter2} cname='Counter2'></Counter> </> ); }
export default App;
|
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { memo } from 'react';
interface IProps { cname: string; count: number; }
const Counter = (props: IProps) => { const { count, cname } = props;
console.log(`updated ${cname}`);
return ( <p> {cname}'s value is: {count} </p> ); };
export default memo(Counter);
|
这里点击“更新 Counter1”的按钮后,会导致父组件更新,父组件更新后因为无缓存,因此子组件也就一起更新了,但是仔细分析一下,我们的 Counter2 的传参并无任何变动,这里更新后造成了多余的渲染,资源浪费,因此需要做一下缓存(子组件的注释替换一下即可),这样就 Counter2 就不会重复渲染了。为缓存和缓存后写法的结果分别为:
未缓存:
已缓存:
可见缓存后的组件在参数不变的情况下是不会重新渲染的!
useMemo
useMemo
主要是对变量进行缓存(当然函数也可以,但推荐使用 useCallback
,除非需要一些定制化逻辑操作),该 Hook 一般于 useEffect
进行比较。useMemo
是在依赖变动后,Dom 变动前触发的,而 useEffect
则是依赖变动后,Dom 变动后才出发(副作用)。
因此,如果依赖变更后需要立即做出反应时可以使用 useMemo
,如果是依赖变动后,需要带着触发一些其它的操作,则使用 useEffect
:
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 34 35 36 37 38 39 40
| import React, { useState, useMemo, useEffect } from 'react';
function App() { const [counter1, setCounter1] = useState(1);
const updateCounter1 = () => { setCounter1(counter1 + 1); };
const getRandom = () => { return Math.random() * 10; }; const memoRandom = useMemo(() => { return Math.random() * 10; }, []);
useEffect(() => { console.log('counter1 变动了'); }, [counter1]); useEffect(() => { console.log('getRandom 变动了'); }, [getRandom]); useEffect(() => { console.log('memoRandom 变动了'); }, [memoRandom]);
return ( <> <button onClick={updateCounter1}>更新 Counter1</button> <span> </span>
<p>Counter1: {counter1}</p> <p>Random: {getRandom()}</p> <p>MemoRandom: {memoRandom}</p> </> ); }
export default App;
|
这里变更 counter1 时,咱们的 randome 会一直变更,但这显然不是我们需要的,此处就可以使用 useMemo
做一次缓存,这样就不会随着父组件的刷新而变动了。
useMemo
可以直接理解为 Vue 的 computed
钩子
useCallback
useCallback
是对组件内的函数进行缓存。当组件的 state 变动后,其会更新整个组件内容,未做缓存的处理的函数都会重新生成一次,这在有 useEffect
监听的情况下会造成每次都触发,因此可用 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import React, { useState, useCallback, useMemo, useEffect } from 'react'; import Counter from './memo-test/counter';
function App() { const [counter1, setCounter1] = useState(1); const [counter2, setCounter2] = useState(2);
const updateCounter1 = () => { setCounter1(counter1 + 1); };
const updateCounter2 = () => { setCounter2(counter2 + 1); }; const callbackedCounter2 = useCallback(() => { setCounter2(counter2 + 1); }, [counter2]);
useEffect(() => { console.log('updateCounter1 变动了'); }, [updateCounter1]); useEffect(() => { console.log('updateCounter2 变动了'); }, [updateCounter2]); useEffect(() => { console.log('callbackedCounter2 仅在 counter2 变更时变更'); }, [callbackedCounter2]);
return ( <> <button onClick={updateCounter1}>更新 Counter1</button> <span> </span> <button onClick={updateCounter2}>更新 Counter2</button>
<h3>Counter 1:</h3> <Counter count={counter1} cname='Counter1'></Counter>
<h3>Counter 2:</h3> <Counter count={counter2} cname='Counter2' callbackedCounter2={callbackedCounter2} ></Counter> </> ); }
export default App;
|
点击“更新 Counter1”的结果如下:
如果仅仅只是父组件内有函数变动,用不用 useCallback
影响不大,但是一旦有子组件有传入父组件的函数时,一定得注意 useCallback 来缓存父组件的函数,否则会带来不必要的渲染。
useCallback
是 useMemo
的语法糖:useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
总结
React.memo
的使用场景:父组件频繁变更的内容不会影响到子组件时,需要对子组件做 React.memo
缓存处理;
useMemo
的使用场景:当组件内的函数返回值不需要随着组件的刷新而变更时,需要对其做 useMemo
缓存处理
useCallback
的使用场景:父组件传递函数给子组件,该函数均需做 useCallback
缓存处理,减少子组件重复渲染;
1 2 3 4 5 6 7 8 9 10 11 12 13
| const memoFunc = useMemo(() => {
return () => { }; }, []);
const callbackFunc = useCallback(() => { }, []);
|
⚠️ 注:开发环境请根据具体情况进行缓存优化,因为缓存的监听函数也是需要消耗资源的,过度缓存有时会适得其反。
参考文章