可能你的react函數組件歷來沒有優化過

16.6以前,函數組件沒有像shouldComponentUpdate這樣的方法,也沒有相似PureComponent這種解決方案,避免不了函數組件裏面全部的代碼再次的執行,要依靠外面的條件渲染來控制,或者是高階組件。以前的話,選擇使用函數組件的狀況是一些比較簡單的又比較純的組件,只是負責展現的。並且函數組件最終編譯babel結果是隻執行createElement那一步;class組件同樣有生命週期要實例化,最終通過Babel成es5代碼的時候還很長前端

React.memo

當16.6的memo問世,函數組件就有了相似PureComponentshouldComponentUpdate的解決方案,memo的使用方法:react

const C = (props) => {
    return <section>那一晚上{props.name}的嫂子真美</section>
}

export default React.memo(C)
複製代碼

當父組件執行render的時候,避免不了C組件的渲染和C函數的執行(若是不在外面加判斷的話:{isShowC && <C />})。當到了C組件的時候,會淺比較C組件先後props值。若是props每個屬性值都同樣,會跳過函數組件C的執行,減小了沒必要要的渲染,達到了性能優化。數組

memo第二個參數

第二個參數,是一個函數,該函數傳入參數是新props和上次props,咱們能夠在函數裏面作判斷邏輯,控制返回值。當咱們讓函數return true的時候,告訴了react這兩個props是同樣的,不用從新執行整個函數組件;反之false的時候會從新執行該組件性能優化

memo(IfEqual, () => false);
複製代碼

好比這行代碼,判斷函數一直返回false,memo包住的IfEqual組件不管怎樣都會從新執行babel

當咱們用上了memo,就能夠根據業務來進行優化了:函數

React.memo(C, (nextProps, prevProps) => {
    // 作咱們想作的事情,相似shouldComponentUpdate
})
複製代碼

函數組件中傳入的props值爲函數時

咱們都知道,js中函數不是簡單數據類型,也就是說function(){}function(){}是不同的,與{}{}不同同理。那麼咱們傳入props.onClick(即便是長得同樣的內容徹底同樣),先後props.onClick都不能劃上等號性能

<div>
      <IfEqual onClick={() => {}} /> </div>
複製代碼

以爲inline function很差看,那前面定義一下,實際上仍是逃不了同一個事情:它們是不同的。此次是由於,函數組件的渲染,也就是執行,每一次從新執行,函數做用域裏面一切都是從新開始。這就至關於上一次組件渲染const handleClick = () => {},後面渲染又一次const handleClick = () => {},它們都不是同一個東西學習

export default () => {
  const handleClick = () => {} 
  return (
    <div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼

這種狀況下,咱們能夠用memo第二個參數來拯救多餘一次的渲染的局面:優化

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

最後,先後props的onClick,它們只有一種狀況是同樣的——把聲明抽到組件外面去ui

const handleClick = () => {} 
export default () => {
  return (
    <div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼

這時候,有沒有想起class組件裏面老是onClick={this.handleClick}呢?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 handleClick = useCallback(() => {
    console.log(dep)
}, [dep])
複製代碼

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

export default () => {
// 沒有依賴,永遠是同一個函數
const handleClick = useCallback(() => {}, []);

// 依賴a,從新執行函數組件,a不變的,是同一個函數
// a變了handleClick是新的函數
const handleClick1 = useCallback(() => {}, [a]);
  return (
    <div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼

react組件也是一個函數,那其實useCallback還能夠作一個函數組件:

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

  return (
    <div> <Cpn name="hi" /> </div> ) } 複製代碼

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

useMemo

const a = useMemo(() => memorizeValue, deps)
複製代碼

當deps不變,a的值仍是上次的memorizeValue,省去了從新計算的過程。若是memorizeValue是一個函數,和useCallback是同樣的效果:

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

咱們能夠試一下同步執行的代碼,當時間很是長的時候,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 (
    <> <button onClick={forceUpdate}>更新頁面</button> <button onClick={() => add(count + 1)}>+</button> </> ) } 複製代碼

第一次進來,頁面暫時沒有任何反應一陣,這是由於slowlyAdd佔用了js主線程。當咱們點擊‘更新頁面’更新的時候,頁面並無卡死,並且組件也從新渲染執行了一次。當咱們點擊+,頁面又開始卡死一陣。

這是由於點擊+的時候,修改了useMemo的依賴n,n變了從新計算,計算耗費時間。若是點擊更新頁面,沒有修改到依賴n,不會從新計算,頁面也不會卡

固然,useMemo也能夠作高階組件,用起來的時候,能夠寫成reactElement的形式了:

const HOC = useMemo(() => <C />, deps)
複製代碼

最後

有以下的組件,Big是一個10w個節點的組件,每個節點都綁定事件

const handleClick = useCallback(() => {}, []);
export default () => {
  return (
    <div> <Big onClick={handleClick} /> </div> ) } 複製代碼
  • 若是Big組件沒有memo包住,首次掛載和再次渲染父組件性能以下:

  • 若是Big組件有memo包住並且props被認爲是同樣的狀況下,首次掛載和再次渲染父組件性能以下:

總結一下對於props的某個屬性值爲函數的時候,如何作到子組件不從新執行多餘渲染:

關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技

相關文章
相關標籤/搜索