超性感的React Hooks(十一)useCallback、useMemo

2021-03-02 不知非攻

在實踐開發中,有一種優化手段叫做記憶函數

什麼是記憶函數?用一個例子來說明。

我們想要計算從1到某個整數的總和。封裝一個方法來實現這個目的。

function summation(target: number) {  let sum = 0;  for(let i = 1; i <= target; i++) {    sum += i;  }  return sum;}

驗證一下結果,沒有問題。

這個時候,我們思考一個問題,當我們重複調用summation(100)時,函數內部的循環計算是不是有點冗餘?因為傳入的參數一樣,得到的結果必定也是一樣,因此如果傳入的參數一致,是不是可以不用再重複計算直接用上次的計算結果返回呢?

當然可以,利用閉包能夠實現我們的目的。

let preTarget = -1;let memoSum = 0;
export function memoSummation(target: number) { if (preTarget > 0 && preTarget === target) { return memoSum; }
console.log('我出現,就表示重新計算了一次'); preTarget = target; let sum = 0; for (let i = 1; i <= target; i++) { sum += i; } memoSum = sum; return sum;}

多次調用memoSummation(1000),沒有問題,我們的目的達到了。後兩次的調用直接返回了記憶中的結果。

這就是記憶函數。記憶函數利用閉包,在確保返回結果一定正確的情況下,減少了重複冗餘的計算過程。這是我們試圖利用記憶函數去優化我們代碼的目的所在。

1

react hooks提供的api,大多都有記憶功能。例如

•useState•useEffect•useLayoutEffect•useReducer•useRef•useMemo 記憶計算結果•useCallback 記憶函數體

其他幾個api的使用方法,我們在前面已經一一跟大家分析過。這裡主要關注useMemo與useCallback。

useMemo

useMemo緩存計算結果。它接收兩個參數,第一個參數為計算過程(回調函數,必須返回一個結果),第二個參數是依賴項(數組),當依賴項中某一個發生變化,結果將會重新計算。

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;

useCallback

useCallback的使用幾乎與useMemo一樣,不過useCallback緩存的是一個函數體,當依賴項中的一項發現變化,函數體會重新創建。

function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

寫一個案例,來觀察一下他們的使用。

import React, { useMemo, useState, useCallback } from 'react';import { Button } from 'antd-mobile';
export default function App() { const [target, setTarget] = useState(0); const [other, setOther] = useState(0)
const sum = useMemo(() => { console.log('重新計算一次'); let _sum = 0; for (let i = 1; i <= target; i++) { _sum += i; } return _sum; }, [target]);
const inputChange = useCallback((e) => { console.log(e.target.value); }, []);
return ( <div style={{ width: '200px', margin: 'auto' }}> <input type="text" onChange={inputChange} /> <div style={{ width: '80px', margin: '100px auto', fontSize: '40px' }}>{target} {sum}</div> <Button onClick={() => setTarget(target + 1)}>遞增</Button> <Button onClick={() => setTarget(target - 1)}>遞減</Button>
<div style={{ width: '80px', margin: '100px auto', fontSize: '20px' }}>幹擾項 {other}</div> <Button onClick={() => setOther(other + 1)}>遞增</Button> <Button onClick={() => setOther(other - 1)}>遞減</Button> </div> )}

2

useMemo/useCallback的使用非常簡單,不過我們需要思考一個問題,使用他們一定能夠達到優化的目的嗎?

React的學習經常容易陷入過度優化的誤區。一些人在得知shouldComponentUpdate能夠優化性能,恨不得每個組件都要用一下,不用就感覺自己的組件有問題。useMemo/useCallback也是一樣。

明白了記憶函數的原理,我們應該知道,記憶函數並非完全沒有代價,我們需要創建閉包,佔用更多的內存,用以解決計算上的冗餘

useMemo/useCallback也是一樣,這是一種成本上的交換。那麼我們在使用時,就必須要思考,這樣的交換,到底值不值?

如果不使用useCallback,我們就必須在函數組件內部創建超多的函數,這種情況是不是就一定有性能問題呢?

不是的。

我們知道,一個函數執行完畢之後,就會從函數調用棧中被彈出,裡面的內存也會被回收。因此,即使在函數內部創建了多個函數,執行完畢之後,這些創建的函數也都會被釋放掉。函數式組件的性能是非常快的。相比class,函數更輕量,也避免了使用高階組件、renderProps等會造成額外層級的技術。使用合理的情況下,性能幾乎不會有什麼問題。

而當我們使用useMemo/useCallback時,由於新增了對於閉包的使用,新增了對於依賴項的比較邏輯,因此,盲目使用它們,甚至可能會讓你的組件變得更慢。

大多數情況下,這樣的交換,並不划算,或者賺得不多。你的組件可能並不需要使用useMemo/useCallback來優化。

3

那麼,什麼時候使用useMemo/useCallback比較合適?

總的原則,就是當你認為,交換能夠賺的時候去使用它們。

例如在一個一定會多次re-render的組件裡,input的回調沒有任何依賴項,我們就可以使用useCallback來降低多次執行帶來的重複創建同樣方法的負擔。

即使這樣,也可能並不會優化多少,因為我們緩存的函數體本身就非常簡單,不會造成太大的負擔

<input type="text" onChange={inputChange} />
const inputChange = useCallback((e) => { setValue(e.target.value);}, []);

但是,同樣的場景,如果該組件一定只會渲染一次,那麼使用useCallback就完全沒有必要。

通常情況下,當函數體或者結果的計算過程非常複雜時,我們才會考慮優先使用useCallback/useMemo。

例如,在日曆組件中,需要根據今天的日期,計算出當月的所有天數以及相關的信息。

不過,當依賴項會頻繁變動時,我們也要考慮使用useMemo/useCallback是否划算

每當依賴項變動,useMemo/useCallback不會直接返回計算結果,這個時候,結果會重新計算,函數體會重新創建。因此依賴項變動頻繁時,需要慎重考慮。

最後,一圖總結全文。

本系列文章的所有案例,都可以在下面的地址中查看

https://github.com/advance-course/react-hooks

本系列文章為原創,請勿私自轉載,轉載請務必私信我

關於如何學好JavaScript,我寫了一本書,感興趣的同學可點擊閱讀原文查看詳情。

相關焦點

  • 【Hooks】:[組]When to use React.useCallback()
    But what is useCallback and when does it make sense to use it?To recap, useCallback is a React Hook which returns a memoized version of the callback function it is passed.
  • 超性感的React Hooks(四):useEffect
    許多朋友試圖利用class語法中的生命周期來類比理解useEffect,也許他們認為,hooks只是語法糖而已。那麼,即使正在使用hooks,也有可能對我上面這一段話表示不理解,甚至還會問:不類比生命周期,怎麼學習hooks?我不得不很明確的告訴大家,生命周期和useEffect是完全不同的。
  • 函數式編程看React Hooks(一)簡單React Hooks實現
    從 react 的變化可以看出,react 走的道路越來越接近於函數式編程,輸入輸出一致性。當然也不是憑空地去往這個方面,而是為了能夠解決更多的問題。以下 三點是 react 官網所提到的 hooks 的動機 https://zh-hans.reactjs.org/docs/hooks-intro.html#motivation代碼重用:在hooks出來之前,常見的代碼重用方式是 HOC 和render props,這兩種方式帶來的問題是:你需要解構自己的組件,同時會帶來很深的組件嵌套複雜的組件邏輯:在class組件中
  • 不要過度使用React.useCallback()
    import React, { useCallback } from 'react';function MyComponent() { const handleClick = useCallback(() => { // handle the click event }, [
  • React Hooks使用小結
    社區,論壇裡各位大佬大多認為,更加的可抽象,邏輯可復用,代碼精簡,避免寫各種生命周期,這裡我就react在官方文檔提到的hooks出現的動機(emmm...不就是好處麼)與大家的使用感受做一下簡單的總結:公共邏輯的復用。
  • 你可能不知道的 React Hooks
    參考資料[1] Hooks API Reference: https://reactjs.org/docs/hooks-reference.html[2] useEffect: https://reactjs.org/docs/hooks-reference.html#useeffect[3] Hooks API Reference: https://reactjs.org
  • React:useHooks小竅門
    (給前端大全加星標,提升前端技能)英文:usehooks  譯文:林林小輝https://zhuanlan.zhihu.com
  • 「不容錯過」手摸手帶你實現 React Hooks
    例如,useState 是允許你在 React 函數組件中添加 state 的 Hook。規則檢查 使用 eslint-plugin-react-hooks 來檢查代碼錯誤    {      "plugins": ["react-hooks"],      // ...
  • React hooks 最佳實踐【更新中】
    1.儘量設計簡單的hookshooks 設計的初衷就是為了使開發更加快捷簡便,因此在使用hooks 的時候,我們不應該吝嗇使用較多的hooks,例如我們處理不同狀態對應不同邏輯的時候,按照寫class的邏輯,我們經常會在一個生命周期函數裡寫下多個邏輯,並用if區分;在寫hooks的時候,因為沒有shouldComponentUpdate這類的生命周期函數,我們應該將他們分離開,將他們寫在不同的useEffect
  • 寫React Hooks前必讀
    來源:相學長 https://zhuanlan.zhihu.com/p/113216415最近團隊內有同學,由於寫react hooks引發了一些bug,甚至有1例是線上問題。團隊內也因此發起了一些爭執,到底要不要寫hooks?到底要不要加lint?到底要不要加autofix?
  • React 16.8發布:hooks終於來了!
    如果你之前從未聽說過 hooks,可以參考以下這些資源:「Introducing hookss」解釋了我們為 React 添加 hooks 功能:https://reactjs.org/docs/hooks-intro.html「hookss at a Glance」對內置的 hooks 進行了快速的介紹:https://reactjs.org
  • React Hooks 原理與最佳實踐
    Custom Hooks對於 react 來說,在函數組件中使用 state 固然有一些價值,但最有價值的還是可以編寫通用 custom hooks 的能力。想像一下,一個單純不依賴 UI 的業務邏輯 hook,我們開箱即用。不僅可以在不同的項目中復用,甚至還可以跨平臺使用,react、react native、react vr 等等。
  • ahooks 正式發布:值得擁抱的 React Hooks 工具庫
    待我細細道來 😆在 ice/hooks RFC 期間,我們也對比參考了社區的同類方案諸如 react-use 等,但最終因為 react-use 提供的 Hooks 過於冗餘(已經超過 100+),大部分在實際業務中可能使用不到,以及它在一年時間內大版本已經變更到 v15 的原因等最終放棄選,最終確定 ice/hooks 中提供的 Hooks 一方面是基於 react-use 的基礎部分,另一方面更多的是貼合業務的,由業務中進行提煉出來的 Hooks 進行組合的方案。
  • 30 分鐘精通 React 新特性——React Hooks
    回到一開始我們用的例子,我們分解來看到底state hooks做了什麼:import { useState } from 'react';function Example() {  const [count, setCount] = useState(0);  return (    <div>
  • 寫React Hooks前需要注意什麼?
    必須完整閱讀一次React Hooks官方文檔英文文檔:https://reactjs.org/docs/hooks-intro.html中文文檔:https://zh-hans.reactjs.org/docs/hooks-intro.html其中重點必看hooks: useState、useReducer、useEffect、useCallback
  • 【實戰總結篇】寫React Hooks前必讀
    必須完整閱讀一次React Hooks官方文檔英文文檔:https://reactjs.org/docs/hooks-intro.html中文文檔:https://zh-hans.reactjs.org/docs/hooks-intro.html其中重點必看hooks: useState、useReducer、useEffect、useCallback
  • 是時候用 useMemo,useCallback,React's memo API 優化性能了?
    代碼如下:import React from 'react';import { v4 as uuidv4 } from 'uuid'; const App = () => {  console.log('Render: App');  const [users, setUsers] = React.useState
  • 30 分鐘精通 React 今年最勁爆的新特性 —— React Hooks
    回到一開始我們用的例子,我們分解來看到底state hooks做了什麼:import { useState } from 'react';function Example() {  const [count, setCount] = useState(0);  return (    <div>
  • React 源碼解讀之Hooks
    怎麼區分首次渲染和更新首次渲染時,因為 hooks 鍊表還未形成,所以需要構建 hooks 鍊表,當調用 useState, useEffect 等函數時,最終都會調用 mountState, mountEffect 等方法。
  • React-hooks入坑指南
    所以需要將一個完整的對象傳遞給useState// classthis.state = {  name: "smw",  age: 18};this.setState({  age: 20});// hooksconst [state, setState] = useState