簡化React Hook的5種方法

OIP.jpeg

在編寫自定義Hook時,很容易產生過於複雜的解決方案。這有時會致使不穩定的行爲,建立無用的從新渲染,或者只是使其更難維護。考慮到這一點,我想分享5種我發現的幫助簡化定製Hook的方法。javascript

減小 useState 數量

當你使用hook進行開發時,很容易使用過多的 useState 調用,或者將全部的狀態縮減爲單一的、過於複雜的 useState。提升hook的可讀性的最好方法之一就是優先考慮你的useState調用。我喜歡在我寫的鉤子中遵循一些關於狀態實現的規則。前端

優先考慮易讀性

我更喜歡將狀態讀取爲對象,而不是使用多個具備簡單值的useState命令。使用更少的 useState 命令也會讓你的鉤子的返回更容易,而且在組件中的實現更直接。雖然這是個人偏好,但代碼是一個很是我的化的東西,也是很是有表現力的。我寫代碼時的第一條規則是優先考慮可讀性,遵循這個規則會讓你的代碼更容易維護,迫使你去思考你所寫的東西,並讓別人更容易遵循你的代碼。若是這是你從這個文章中帶走的惟一東西,那麼我已經完成了個人工做。java

評估狀態對象的內容

組件從一開始就沒有被完美地規劃過,隨着組件的增加,你的 useState 中包含的屬性可能也會變得愈來愈複雜。在整個開發週期中,我強烈建議評估你的useState調用的內容,以肯定將狀態部分分紅其餘useState調用是否有意義。你可能想按功能或類型對狀態值進行分組。通常來講,我喜歡把狀態數據按照我認爲一般會一塊兒更新的屬性來分組,或者按照狀態屬性的功能來分組,好比數據和視圖屬性。react

利用你的Hook返回

當我剛開始寫自定義Hook時,很容易遵循相似於默認的useState鉤子的返回樣式。雖然這並非壞事,但在函數之上使用一個返回數組來返回多個狀態變量,會很麻煩。想象一下,除了處理數據選擇的函數外,還能夠返回2個不一樣的狀態變量(1個是數據狀態,1個是視圖狀態)的鉤子,用數組風格的返回方式編寫,它可能看起來像這樣。git

function useBasicHook() {
  const [dataState, setDataState] = useState({
    serverData: {},
    selections: {}
  });
  const [viewState, setViewState] = useState({
    menuExpanded: false,
    submitFormData: {}
  })
  
  const toggleMenuExpand = () => {
    setViewState({
      menuExpanded: !viewState.menuExpanded,
      submitFormData: viewState.submitFormData
    })
  }
  
  return [dataState, viewState, toggleMenuExpande];
}

function BasicComponent(){
  const [dataState, viewState, toggleMenuExpand] = useBasicHook();
  
  return <div>
    </div>
}

看看這個hook,很容易看出,若是在返回中添加額外的函數或變量,hook的實現會很快失控。若是你不當心破壞了數組的順序,或者用不正確的名稱,會形成額外的混亂和可能的錯誤。咱們能夠經過更新hook返回一個對象來防止這種狀況的發生,就像這樣。數組

function useBasicHook() {
  const [dataState, setDataState] = useState({
    serverData: {},
    selections: {}
  });
  const [viewState, setViewState] = useState({
    menuExpanded: false,
    submitFormData: {}
  })
  
  const toggleMenuExpand = () => {
    setViewState({
      menuExpanded: !viewState.menuExpanded,
      submitFormData: viewState.submitFormData
    })
  }
  
  return {
    dataState: dataState,
    viewState: viewState,
    toggleMenuExpand: toggleMenuExpand
  };
}

function BasicComponent(){
  const state = useBasicHook();
  // or
  // const {dataState, viewState, toggleMenuExpand} = useBasicHook();
  
  return <div>
    </div>
}

將返回值轉換爲對象還有其餘好處,包括:異步

  • 若是hook在多個組件之間共享或做爲庫共享,則在更新後提升hook版本的兼容性;
  • 在使用Hook在組件頂部提供相同級別的Hook API時,仍然能夠解構對象。

還有一件很酷的事情,你能夠用你的鉤子返回,就是在你的狀態中建立基於組件工廠函數的小狀態。這提供了一種很好的方式,能夠將組件構建器共享給實現鉤子的組件,而無需將狀態公開給該組件。函數

使用合併鉤子簡化 setState 調用

在React中使用類而不是基於函數的組件進行開發,當涉及到狀態管理時,確實有一些開箱即用的優點,對我來講,最主要的是舊狀態與新狀態的合併。React Docs for State提供了React.Component中內置的狀態合併功能的良好示例。雖然該功能沒有直接內置到鉤子中,但咱們能夠經過一個簡單的自定義鉤子來複制這種行爲,它能夠替換咱們的 useState 調用,給咱們一樣的行爲。spa

function useMergeState(initialState) {
  const [state, setState] = useState(initialState);
  // 使用 useRef 來改進異步調用 setState 時的功能。
  const stateRef = useRef(state);

  function setRefState(newState) {
      stateRef.current = newState;
      return setState(newState);
  }

  function mergeState(newState) {
    var finalState = newState;
    /**
     * 判斷狀態數據類型是否匹配,若是匹配,則繼續合併,
     * 若是不匹配,則拋出一個控制檯警告,用新的狀態覆蓋。
     */
    if (typeof stateRef.current !== typeof newState) {
      console.warn(
        "useMergeState warning: 狀態數據類型不匹配,用新的狀態覆蓋狀態。"
      );
      finalState = newState;
    } else {
      /**
       * 在此處理合並
       */
      if (typeof stateRef.current == "object" && !Array.isArray(stateRef.current)) {
        // 現有狀態是一個對象,繼續嘗試合併
        if (typeof newState == "object" && !Array.isArray(newState)) {
          finalState = { ...stateRef.current, ...newState };
        }
      }
    }

    return setRefState(finalState);
  }

  return [stateRef.current, mergeState];
}

考慮拆分Hook

不管組件的複雜程度如何,我老是建議使用自定義鉤子;然而,在構建自定義鉤子時,將一個過於複雜的鉤子分割成多個較簡單的鉤子是很是有用的。在個人項目中,我喜歡根據功能來拆分鉤子邏輯,好比說,把一個鉤子拆成邏輯上的狀態子集,好比數據/Web API交互的鉤子和顯示狀態的單獨的鉤子,可能會有好處。回想一下鉤子返回部分的例子鉤子,這樣拆開來可能會有幫助。翻譯

function useDataHook() {
  const [dataState, setDataState] = useState({
    serverData: {},
    selections: {}
  });
  
  return dataState;
}

function useDisplayHook() {
  const [viewState, setViewState] = useState({
    menuExpanded: false,
    submitFormData: {}
  })
  
  const toggleMenuExpand = () => {
    setViewState({
      menuExpanded: !viewState.menuExpanded,
      submitFormData: viewState.submitFormData
    })
  }
  
  return {
    viewState: viewState,
    toggleMenuExpand: toggleMenuExpand
}

function BasicComponent(){
  const data = useDataHook();
  const display = useDisplayHook();
  
  return <div>
    </div>
}

拆分後的示例掛鉤

評估 useEffect 調用,以防止沒必要要的從新渲染

useEffect鉤子很是有用,可是若是使用不當,可能會致使過分渲染。查看自定義鉤子時,值得評估你的useEffect調用。我喜歡遵照如下經驗法則:

  • 若是一個 useEffect 在同一個鉤子做用域中監聽狀態變量,那麼這個效果不該該更新狀態自己。
  • 若是你有多個useEffect語句在偵聽同一組變量,請考慮將它們組合在一塊兒。
  • 儘管結合使用 useEffects 有助於減小從新渲染次數,但首先要優先考慮代碼的可讀性。

來源:https://levelup.gitconnected.com,做者:Ben Simons,翻譯:公衆號《前端全棧開發者》
subscribe2.png

相關文章
相關標籤/搜索