React優化篇-memo,useMemo,useCallback

React.memo

當16.6的memo問世,函數組件就有了類似PuerComponent和shouldComponentUpdate的解決方案,memo的使用方法:

const C = (props) => {
 return 

那一夜{props.name}真美

} export default React.memo(C)

當父組件執行render的時候,避免不了C組件的渲染和C函數的執行(如果不在外面判斷的話{isShowC && })。當到了C組件的時候,會淺比較C組件前後props值。如果props每一個屬性值都一樣,會跳過函數組件C的執行,減少了不必要的渲染,達到了性能優化。

memo第二個參數

第二個參數,是一個函數,該函數傳入參數是新props和上次props,我們可以在函數里面做判斷邏輯,控制返回值。當我們讓函數return true的時候,告訴react這兩個props是一樣的,javascript不用重新執行整個函數組件。反之false的時候會重新執行該組件

memo(IfEqula,() => false);

比如這行代碼,判斷函數一致返回false,memo包住的IfEqula組件無論怎樣都會重新執行 當我們用上了memo,就可以根據業務來進行優化了:

React.memo(C,(nextProps,prevProps) => {
 // 做我們想做的事情,類似shouldComponentUpdate
})

函數組件中傳入props值為函數時

我們都知道,js中函數不是簡單數據類型,也就是說function(){}和function(){}是不一樣的,與{}和{}不一樣同理。那麼我們傳入props.onClick(即使是長得一樣的內容完全一樣),前後props.onClick都不能劃上等號

{}}/>

覺得inline function不好看,那前面定義一下,實際上還是逃不了同一件事情:它們是不一樣的。這是因為,函數組件的渲染,也就是執行,每一次重新執行,函數作用域裡面一切都是重新開始。這就相當與上一次組件渲染const handelClick = () =>,後面渲染又一次const handleClick = () => {} ,它們都不是同一個東西

export default () => {
 const handleClick = () => {};
 return(
 
 )
}

這種情況下,我們可以用memo第二個參數來拯救對於一次的渲染的局面

// props : { a: 1 , onClick : () => {} }
 // 在我們知道onClick是做同一個事情的函數的前提下,不比較onClick
React.memo(C , (nextProps,prevProps) => nextProps.a === prevProps.a)

最後,前後props的onClick,它們只有一種情況是一樣的,把聲明抽象到組件外面去

const handerClick = () => {};
export defalut () => {
 return (
 

) };

這時候,有沒有想起class組件裡面總是onClick={this.handerClick}呢?this.handleClick一直都是同一個函數。這種情況,子組件為函數組件的時候,包一層memo實現PureComponent的效果

useCallback

函數組件把函數定義寫在外面,是可以解決問題。但是,如果handleClick依賴組件內部的一些變量,那handleClick又不得不寫在裡面(當然利用引用類型可以解決)。或者還是正常些,靠memo第二個參數來控制要不要重新渲染子函數組件,但是無論怎樣,都存在一個問題,就是那一塊代碼寫在裡面呢,都無法避免代碼的執行和函數的重新定義,比如

function a (){
 const b = function(){
 console.log(1);
 // 很多很多代碼
 }
}
a()
a() //函數b又被定義了一次

如果我們通過依賴來確定前後兩次是不是同一個函數,我們可以用函數記憶來實現整個功能

let prev
let prevDeps
function memorize(fn,deps){
  
 if(prev && isEqual(deps,prevDeps)){
 return prev
 }
 prevDeps = deps;
 prev = fn;
 return fn;
}
function a (){
 const b = memorize(function(){
 console.log(1);
  
 },[])
}
a();
a(); // 函數b是同一個

類似函數記憶的遠離,後來有了useCallBack的出現,多了一種新的解決方案,根據依賴生成一個函數:

const handeClick = useCallBack(() => {
 console.log(dep)
},[dep])

當dep不變,每一次函數組件的執行,handelClick都是同一個函數。如果dep變了,那麼handleClick又是一個新的函數。

export default () => {
 // 沒有依賴,永遠是同一個函數
 const handleClick = useCallBack(() => {},[]);
 // 依賴a,重新執行函數組件,a不變的,是同一個函數
 // a變了handleClick是新的函數
 const handelClick1 = useCallBack(() => {},[a]);
 return(
 

) }

react組件也是一個函數,那其實useCallback還可以做一個函數組件:

export default () => {
 const handleClick = useCallback(() => {},[]);
 const Cpn = useCallback(({name}) => {
 return 
 },[handleClick])
 return (
 

) }

當然這只是一個簡單的場景,如果用了hooks,還沒有解決問題或者暫時沒有想到優雅的封裝技巧,想用高階組件的時候,不妨嘗試一下useCallback

useMemo

const a = useMemo(() => memorizeValue,deps);

當deps不變,a的值還是上次的memorizeValue,省去了重新計算的過程。如果memorizeValue是一個函數,和useCallback是一樣的效果

useCallback(fn,deps) <=> useMemo(() => fn ,deps)

我們可以試一下同步執行的代碼,當時間非常長的時候,useMemo可以發揮它的作用了:

// 強制更新組件
const useForceUpdate = () => {
 const forceUpdate = useState(0)[1];
 return () => forceUpdate( x => x + 1)
};
// 一個很耗時間的代碼
function slowlyAdd(n){
 console.time('add slowly');
 let res = n;
 for (let i = 0 ; i < 2000000000 ; i ++){
 res += 1;
 }
 console.timeEnd('add slowly');
 return res;
}
// useMemo 記憶結果的一個自定義hook
function useSlowlyAdd(n){
 const res = useMemo(() => {
 return slowlyAdd(n);
 },[n]);
 return res;
}
export default () => {
 const [ count , add ] = useState(1);
 const forceUpdate = useForceUpdate();
 const handleClick = useCallback(() => {},[]);
 useSlowlyAdd(count); // 第一次這裡會耗很多時間,頁面卡死一陣
 return(
 <>
 
 
 <>
 )
}

第一次進來,頁面暫時沒有人設反應一陣,這是因為slowlyAdd佔用了住縣城。當我們點擊‘更新頁面’更新的時候,頁面並沒有卡斯,而且組件也重新渲染執行了一次。當我們點擊+,頁面又開始卡死一陣。

這是因為點擊+的時候,修改了useMemo的依賴n,n變了重新計算,計算耗費時間。如果點擊更新頁面,沒有修改到依賴n,不會重新計算,頁面也不會卡。

當然useMemo也可以做高階組件,用起來的時候,可以寫成reactElement的形式了

const HOC = useMemo(() => ,deps)

收藏

舉報


分享到:


相關文章: