Back To Articles

React Code Reuse - Custom Hooks

🧑🏻‍💻 海豹人 Sealman 📅 February 4, 2022

Article Image

本文介紹為什麼我們要建立並使用 Custom Hooks,以及講解如何撰寫創建自己的 Hooks,讓我們在開發 React 專案時更好地複用各種邏輯與程式碼。

什麼是 Custom Hook

  • Custom Hooks 可以使用 React Hooks 與 State 等函式
  • 使用時機:當不同元件裡有著一定程度的共通邏輯時,我們會想要複用邏輯,在各個元件中只去撰寫不同的部分
  • 特性:每一次使用 Custom Hook 時,各別內部的 State 與 Effect 都是完全獨立的

示範建立一個 Custom Hook

Custom Hooks 其實不是 Functional Components,它是一個函式,只是在做法上有點類似

首先我通常習慣在 src 底下建立一個名為 hooks 的資料夾,專門用來存放自己建立的 Custom Hooks。而 Custom Hooks 的檔案名稱也是依照個人喜好即可,我自己喜歡用 useXxx.js 作為命名規則,舉例來說,新增 src/hooks/useCounter.js 檔案。

創建好檔案後,我們必須在將函式名稱命名為 useXxx,例如:useCounter。這種函式名稱的命名方式是必須遵守的規範,這是為了讓 React 能夠辨別這是一個 Custom Hook,讓你能夠在裡面使用 useEffect 等 Hooks。

import { useEffect, useState } from "react";

const useCounter = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCounter((prevCounter) => prevCounter + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return counter;
};

export default useCounter;

如同內建的 React Hooks,這個 Custom Hook 也會 return 東西,我們建立的 Custom Hook 可以回傳「任何」型別。在這個範例當中,我是回傳一個計算過後的 number

最後記得導出這個 Custom Hook,這樣我們才能在其他元件中調用它。

使用自己建立的 Custom Hook

現在我們就到其他元件中使用 useCounter,以此取得回傳值(到這裡,我們就已經成功做到邏輯拆分囉)。

在使用 Custom Hooks 的時候,如果在「多個元件」中使用同一個 Custom Hook,每個元件都會產生一套自己的 Custom Hook,也就是裡面使用的 State 或 Effect 等資料都是「不會共用」的。

共用的是邏輯,不會共用狀態

const ForwardCounter = () => {
  const counter = useCounter(); // 這個 counter 是 Custom Hook 回傳的

  return <Card>{counter}</Card>;
};

export default ForwardCounter;

接下來,我們可以再針對不同的邏輯去做改變,像是透過「參數」來指定不同的邏輯。例如:透過 forwards 參數,給予 Custom Hook false 表示遞減,預設的 true 則為遞增。

const useCounter = (forwards = true) => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // 根據參數的值判斷要執行的動作
      if (forwards) {
        setCounter((prevCounter) => prevCounter + 1);
      } else {
        setCounter((prevCounter) => prevCounter - 1);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [forwards]); // 記得把參數放入 Dependencies array

  return counter;
};

使用上加上參數,例如:選擇傳入 false 表示要執行遞減。

const BackwardCounter = () => {
  const counter = useCounter(false);

  return <Card>{counter}</Card>;
};

使用 Custom Hooks 的注意事項

1. 小心傳遞參考類型

如果 Custom Hook 回傳的內容包含函式(或物件),在使用 useEffect 時「不要」將它們加入 dependencies array 來偵測變化。

為什麼?理論上應該加入沒錯,但實際上這會導致無限循環!因為每次生成的新函式或物件雖然看起來一樣,但實際上是不同的引用,這會導致 useEffect 不斷重跑。

解決方法:對可能變動的函式(或物件)使用 useCallback(或 useMemo),這樣就能確保它們是同一個引用。

import { useState, useEffect, useCallback } from "react";

const useCustomHook = () => {
  const [value, setValue] = useState(0);

  const updateValue = useCallback(() => {
    setValue((prev) => prev + 1);
  }, []);

  return { value, updateValue };
};

const MyComponent = () => {
  const { value, updateValue } = useCustomHook();

  useEffect(() => {
    updateValue(); // 加上 useCallback 避免導致無限循環
  }, [updateValue]);

  return <div>{value}</div>;
};

2. 避免在使用 Custom Hook 時傳入參數,將外部依賴改為函式參數

除了使用 useCallbackuseMemo 之外,還有另一個解決方法,就是將 Custom Hook 所依賴的變數改為函式的參數。

這意味著在 Custom Hook 內部使用這些變數的地方,我們直接把它們作為函式參數來傳遞。這樣一來,我們就不需要在 Custom Hook 中傳遞這些變數,Custom Hook 也不需要新增 dependencies。

import { useEffect } from "react";

const useCustomHook = () => {
  const logDependency = (dependency) => {
    console.log(dependency);
  };

  return logDependency;
};

const MyComponent = ({ someProp }) => {
  const logDependency = useCustomHook();

  useEffect(() => {
    // 傳遞 someProp 作為參數給 logDependency,而不是直接依賴 someProp
    // 減少了不必要的重複渲染和潛在的錯誤
    logDependency(someProp);
  }, [someProp, logDependency]);

  return <div>{someProp}</div>;
};

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • Custom Hooks

References