iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Modern Web

30天入門:學會第一個前端框架-React系列 第 17

Day 17 | React入門:Custom Hook (自定義Hook)

  • 分享至 

  • xImage
  •  

我們在前兩篇文章有利用 useEffect 去 fetch 資料,但把這部分和主要元件的程式碼放在一起,會讓整體看起來很冗長,因此這篇文章會說到自定義 Hook 來解決這部分的問題

為什麼需要自定義 Hook

  • 重複使用邏輯
  • 分離邏輯與 UI
  • 提升維護性與可讀性

自定義 Hook 的特色

  • 命名規則:必須以 use 開頭(例如:useFetch)
  • 邏輯封裝:把「重複的狀態管理 + 副作用邏輯」抽離成一個 Hook
  • 可重複使用:一次寫好,多個元件都能使用
  • 回傳值靈活:可以回傳狀態、方法,甚至一整個物件

使用方法:

useFetch.js

const useFetch () =>{
(…)
}	
Export default useFetch;

例如我們上一篇文章的這個程式碼:

import { useEffect, useState } from "react";
import BlogList from "./BlogList";

const Home = () => {
  const [blogs, setBlogs] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      fetch('http://localhost:8000/blogs')
      .then(res => {
        if (!res.ok) { 
          throw Error(''無法讀取資料來源'');
        }
        return res.json();
      })
      .then(data => {
        setIsLoading(false);
        setBlogs(data);
        setError(null);
      })
      .catch(err => {
        setIsLoading(false);
        setError(err.message);
      })
    }, 1000);
  }, [])

  return (
    <div className="home">
      {error && <div>{ error }</div>}
      { isLoading && <div>資料載入中...</div> }
      {blogs && <BlogList blogs={blogs} />}
    </div>
  );
}
 
export default Home;

缺點:程式碼太冗長,而且如果其他元件也需要獲取資料,就需要再寫一遍


解法:抽出邏輯

useFetch.js

import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [blogs, setBlogs] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      fetch(url)
      .then(res => {
        if (!res.ok) { 
          throw Error(''無法讀取資料來源'');
        }
        return res.json();
      })
      .then(data => {
        setIsLoading(false);
        setBlogs(data);
        setError(null);
      })
      .catch(err => {
        setIsLoading(false);
        setError(err.message);
      })
    }, 1000);
  }, [url])


  return { data, isLoading, error };
}
 
export default useFetch;

因此在原本的元件裡就可以直接導入:
Home.js

import BlogList from "./BlogList";
import useFetch from "./useFetch";

const Home = () => {
  const { error, isLoading, data: blogs } = useFetch('http://localhost:8000/blogs')

  return (
    <div className="home">
      { error && <div>{ error }</div> }
      { isLoading && <div>資料載入中...</div> }
      { blogs && <BlogList blogs={blogs} /> }
    </div>
  );
}

export default Home;

為什麼抽出邏輯後,原本的http://localhost:8000/blogs要改成 url 參數

如果不傳入 url,Hook 內部的 API 位址就會被固定,只能抓某一個 API;
但如果改成接收 url 參數,就能在呼叫 Hook 時傳入不同的 API,讓同一個 Hook 能夠重複使用

傳入url:

const blogs = useFetch("http://localhost:8000/blogs");
const users = useFetch("http://localhost:8000/users");
使用時可以用同一個 Hook 也能抓取其他 API

比較

沒有 url 參數 有 url 參數
寫法 useEffect(() => { fetch("http://localhost:8000/blogs") ... }, []) useFetch("http://localhost:8000/blogs")}
彈性 低,只能抓固定的 API 高,同一個 Hook 抓取不同的 API
維護性 換 API 需要修改 Hook 內部的程式碼 只需要修改呼叫Hook的地方
重複使用性 幾乎只能用在單一場景 可以在多個元件、API 重複使用

結論

使用自定義 Hook 需要注意的地方

  1. 避免過度拆分
    • 不是所有程式邏輯都需要抽成 Hook,如果只是單純一個小邏輯,硬要抽出來反而會增加複雜度
  2. 參數設計要有彈性
    • 就像上述提到的「有沒有 url 參數」差異,如果設計得太死,Hook 就會失去彈性
  3. 回傳值的設計
    • 一個 Hook 通常會回傳「資料 + 狀態 + 操作方法」,例如: const { data, isLoading error } = useFetch(url);

上一篇
Day 16 | React入門:useEffect 應用-資料載入與錯誤處理
下一篇
Day 18 | React入門:React Router 路由控制(上)
系列文
30天入門:學會第一個前端框架-React18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言