React Hooks性能優化指南


前言

本文主要分享一下 React Hooks 性能優化可以從哪幾個方面入手

Hooks的性能問題

要想解決性能問題,關鍵在於組件重復渲染的處理。在使用 React Hooks 后,很多人會抱怨渲染次數變多,比如我們會把不同的數據分成多個 state 變量,每個值的變化都會觸發一次渲染。

舉個🌰

現在有個父子組件,子組件依賴父組件傳入的 name 屬性,但是父組件 name 屬性和 text 屬性變化都會導致 Parent 函數重新執行,所以即使傳入子組件 props 沒有任何變化,甚至子組件沒有依賴於任何 props 屬性,都會導致子組件重新渲染

const Child = ((props: any) => {
  console.log("公眾號:前端開發愛好者 的子組件,我更新了...");
  return (
      <div>
          <h3>子組件</h3>
          <div>text:{props.name}</div>
          <div>{new Date().getTime()}</div>
      </div>
  )
})
const Parent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("")
  const handleClick = () => {
      setCount(count + 1);
  }
  const handleInputChange = (e) => {
      setText(e.target.value)
  }
  return (<div>
      <input onChange={handleInputChange} />
      <button onClick={handleClick}>+1</button>
       <div>count:{count}</div>
      <Child name ={text}/>
  </div>)
}

上面的代碼執行你會發現,不管是觸發 handleInputChange 還是觸發 handleClick ,子組件都會在控制台輸出 我是前端開發愛好者的子組件,我更新了... 所以即使傳入子組件 props 沒有任何變化,甚至子組件沒有依賴於任何 props 屬性,子組件都會重新渲染

想要解決重復渲染的問題,可以使用 react 的親手制造升級的兒子,他有三個方法用來做優化,分別是 React.memo useCallback useMemo

  • React.memo : 和 class 組件時期的 PureComponent 一樣,做簡單額數據類型比較

  • useMemo : 可以用來比較復雜類型的數據,不如 Object Array 等

  • useCallback : 升級版本,用於控制傳遞函數時候控制組件是否需要更新

React.memo

使用 memo 包裹子組件時,只有 props 發生改變子組件才會重新渲染。使用 memo 可以提升一定的性能。


const Child = React.memo((props: any) => {
    console.log("公眾號:前端開發愛好者 的子組件,我更新了..."); // 只有當props屬性改變,集name屬性改變時,子組件才會重新渲染
    return (
        <div>
            <h3>子組件</h3>
            <div>text:{props.name}</div>
            <div>{new Date().getTime()}</div>
        </div>
    )
})
const Parent = () => {
    const [count, setCount] = useState(0);
    const [text, setText] = useState("")
    const handleClick = () => {
        setCount(count + 1);
    }
    const handleInputChange = (e) => {
        setText(e.target.value)
    }
    return (<div>
        <input onChange={handleInputChange} />
        <button onClick={handleClick}>+1</button>
         <div>count:{count}</div>
        <Child name ={text}/>
    </div>)
}

但如果傳入的 props 包含函數,父組件每次重新渲染都是創建新的函數,所以傳遞函數子組件還是會重新渲染,即使函數的內容還是一樣。如何解決這一問題,我們希望把函數也緩存起來,於是引入 useCallback

useCallback

useCallback 用於緩存函數,只有當依賴項改變時,函數才會重新執行返回新的函數,對於父組件中的函數作為 props 傳遞給子組件時,只要父組件數據改變,函數重新執行,作為 props 的函數也會產生新的實例,導致子組件的刷新

使用 useCallback 可以緩存函數。需要搭配 memo 使用

const Child = React.memo((props: any) => {
    console.log("公眾號:前端開發愛好者 的子組件,我更新了...");
   return (
       <div>
           <h3>子組件</h3>
           <div>text:{props.name}</div>
           <div> <input onChange={props.handleInputChange} /></div>
           <div>{new Date().getTime()}</div>
       </div>
   )
})
const Parent = () => {
   const [count, setCount] = useState(0);
   const [text, setText] = useState("")
   const handleClick = () => {
       setCount(count + 1);
   }
   const handleInputChange = useCallback((e) => {
       setText(e.target.value )
   },[])
   return (<div>
       <button onClick={handleClick}>+1</button>
       <div>count:{count}</div>
       <Child name={text} handleInputChange={handleInputChange}/>
   </div>)
}

useCallback 第二個參數依賴項什么情況下使用呢,看下面的例子


//修改handleInputChange
const handleInputChange =useCallback((e) => {
        setText(e.target.value + count)
    },[])

以上例子 count 改變會發生什么事呢?

count 改變,但 handleInputChange 不依賴與任何項,所以 handleInputChange 只在初始化的時候調用一次函數就被緩存起來,當文本改變時或者 count 改變時函數內部的 count 始終為 0,至於為什么需要看 useCallback 源碼后解答。

所以需要將 count 加入到依賴項,count 變化后重新生成新的函數,改變函數內部的 count

const handleInputChange =useCallback((e) => {
        setText(e.target.value + count)
    },[count])

useMemo

useMemo 使用場景請看下面這個例子,getTotal 假設是個很昂貴的操作,但該函數結果僅依賴於 countprice,但是由於 color 變化,DOM 重新渲染也會導致該函數的執行,useMemo 便是用於緩存該函數的執行結果,僅當依賴項改變后才會重新計算

const Parent = () => {
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const handleClick = () => {
        setCount(count + 1);
    }
    const getTotal = ()=>{
        console.log("getTotal 執行了 ...") // 該函數依賴於count和price,但color變化也會導致該函數的執行
        return count * price
    }
    return (<div>

        <div> 顏色: <input onChange={(e) => setColor(e.target.value)}/></div>
        <div> 單價: <input value={price}  onChange={(e) => setPrice(Number(e.target.value))}/></div>
        <div> 數量:{count} <button onClick={handleClick}>+1</button></div>
        <div>總價:{getTotal()}</div>
    </div>)
}

修改后如下,注意 useMemo 緩存的是函數執行的結果

const Parent = () => {
    console.log("parent 執行了...")
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const handleClick = () => {
        setCount(count + 1);
    }
    const getTotal = useMemo(()=>{
        console.log("getTotal 執行了 ...")
        return count * price
    },[count, price])
    return (<div>

        <div> 顏色: <input onChange={(e) => setColor(e.target.value)}/></div>
        <div> 單價: <input value={price}  onChange={(e) => setPrice(Number(e.target.value))}/></div>
        <div> 數量:{count} <button onClick={handleClick}>+1</button></div>
        <div>總價:{getTotal}</div>
    </div>)
}

至此重復渲染的問題的解決基本上可以告一段落。

在 React 中是極力推薦函數式編程,可以讓數據不可變性作為我們優化的手段。我在 React class 時代大量使用了 immutable.js 結合 redux 來搭建業務,與 React 中 PureComponnet 完美配合,性能保持非常好。但是在 React hooks 中再結合 typescript 它就顯得有點格格不入了,類型支持得不是很完美。這里可以嘗試一下 immer.js,引入成本小,寫法也簡潔了不少。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM