文章

自定義 Hooks(Custom Hooks)

自定義 Hooks(Custom Hooks)

Custom Hooks 是 React 中的一種功能,它允許我們將組件邏輯封裝到一個可重用的函數中。這樣,我們可以把多個組件中共用的邏輯提取出來,而不需要重複編寫代碼。自定義 Hooks 其實就是一個以 “use” 開頭的普通 JavaScript 函數,它可以在內部使用其他 React Hooks,如 useStateuseEffect 等。

自定義 Hooks 的特點

  1. 邏輯重用:把組件中複雜的邏輯封裝到一個單獨的 Hook 中,減少代碼重複,提升可讀性和可維護性。
  2. 名稱規範:自定義 Hook 必須以 “use” 開頭,這是 React 用來判斷這個函數是否遵循 Hooks 規則的重要標記。
  3. 與普通函數不同:自定義 Hooks 是可以使用其他 Hooks 的普通函數,因此可以使用狀態、效應等功能。

1. 自定義 Hook 範例:useFetch

需求:

我們想要在多個組件中實現數據請求功能,但不想在每個組件中都重複編寫相同的邏輯。這時可以使用自定義 Hook 來提取這部分邏輯。

自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

在這個 useFetch Hook 中:

  • 我們封裝了數據請求的邏輯,通過 fetch API 進行異步請求。
  • 它接收一個 url 作為參數,並返回三個狀態:data(數據)、loading(是否加載中)、error(錯誤信息)。

使用自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import useFetch from './useFetch';

function Users() {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>加載中...</p>;
  if (error) return <p>發生錯誤: {error.message}</p>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default Users;

在這個 Users 組件中,我們使用了 useFetch 來請求用戶數據,並直接利用返回的 dataloadingerror 來處理不同的界面顯示。


2. 自定義 Hook 範例:useLocalStorage

需求:

有時候我們想把某些狀態持久化到瀏覽器的 localStorage 中,以便在頁面刷新後仍然保留數據。

自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log(error);
    }
  }, [key, value]);

  return [value, setValue];
}

export default useLocalStorage;

在這個 useLocalStorage Hook 中:

  • 我們從 localStorage 中讀取初始值,如果沒有找到則使用默認的 initialValue
  • 每當狀態 value 發生變化時,我們都會把新值存儲到 localStorage 中。

使用自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import useLocalStorage from './useLocalStorage';

function App() {
  const [name, setName] = useLocalStorage('name', '');

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>你好,{name}</p>
    </div>
  );
}

export default App;

在這個 App 組件中,我們使用 useLocalStorage 來存儲用戶輸入的名字,並且每次頁面刷新後,輸入框中的內容依然保持。


3. 自定義 Hook 範例:usePrevious

需求:

有時候我們需要獲取上一個渲染周期中的狀態值。這時可以使用自定義的 usePrevious Hook。

自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useRef, useEffect } from 'react';

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

export default usePrevious;

在這個 usePrevious Hook 中:

  • 我們使用 useRef 保存前一個狀態值,並在 useEffect 中更新這個值。
  • 每次組件重新渲染時,ref.current 都保存著上一次的值。

使用自定義 Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState } from 'react';
import usePrevious from './usePrevious';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>當前計數: {count}</p>
      <p>上一次計數: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

export default Counter;

在這個 Counter 組件中,我們使用 usePrevious 來獲取上一個計數值,並顯示出來。


4. 自定義 Hooks 的設計原則

  • 以 “use” 開頭:這是自定義 Hook 的命名規則,用來告訴 React 它是符合 Hooks 規則的。
  • 符合 Hooks 規則:自定義 Hook 需要遵守 React 的 Hooks 規則,不能在循環、條件語句或嵌套函數中調用 Hooks。
  • 重用邏輯:當組件中有重複的邏輯時,可以考慮將其提取到自定義 Hook 中,從而提高代碼的可讀性和可維護性。

總結

自定義 Hooks 是 React 中強大的工具,可以讓我們將重複的邏輯封裝並重用,從而使代碼更加模組化、簡潔和可維護。當你發現多個組件中存在相似的邏輯時,考慮將它們提取到自定義 Hook 中,這不僅可以提高開發效率,還能讓代碼更加清晰。

本文章以 CC BY 4.0 授權