Day10-React Hook 篇-打造自己的 Hook:Custom Hook

经过前面几天的介绍,我们认识了许多常使用的 hooks,不过除了那些 hooks 之外,我们也可以将一些常用的共同程序逻辑抽取出来写一个函式,这就是 Custom Hook。

使用 Custom Hook 要知道的事

  1. 使用 Custom hook 必须以 use 作为函式名称的开头,让 eslint-plugin-react-hooks 去检查这个 hook 是否有违反 react hook 的规则。
  2. 可以在 Custom hook 里面使用其他的 React hook。

useAxios

在了解 Custom Hook 後,我们来实做一个 Custom Hook,这个 hook 是一个用来呼叫 Api 并进行一些处理的 Custom Hook,名字就叫 useAxios。

2022/1/26 更新 注: 这里只是示范如果做一个 Custom hook,在呼叫 api 还有其他的做法,例如 redux dispatch async action + middleware,读者可以自行依照状况判断使用~

第一步,建立雏形

在 codesandbox 新增一个档案 useAxios,引入 axios 套件後,我们要使用 JSONplaceholder 的 api 作为 Base URL,等这个 hook 完成後会使用它来呼叫 JSONplaceholder 的 api。

在 useAxios 档案我们宣告一个函式 useAxios,在做呼叫 api 时我们也会需要对一些像是呼叫 api 的载入时间和错误讯息做处理,所以建立两个 state: isLoading 和 error,并且建立一个发出请求的函式 sendRequest,在这个函式内会对呼叫的 api 做处理,最後回传一个包含 isLoading、error 和 sendRequest 的物件。

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

axios.defaults.baseURL = "https://jsonplaceholder.typicode.com";

const useAxios = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const sendRequest = useCallback(() => {}, []);

  return {
    isLoading,
    error,
    sendRequest,
  };
};

export default useAxios;

第二步,完成 sendRequest 函式

在 sendRequest 中,我们会传入两个参数 requestConfig 和 applyData,因为在呼叫每只 api 的路径、http 方法和其他参数都不太一样,所以透过 requestConfig 可以将这些资讯带入到 sendRequest 里面,处理完後透过 callback function 也就是第二个参数 applyData 将呼叫 api 的结果回传。

完成後的程序码如下:

import { useState, useCallback } from 'react';
import axios from 'axios';

axios.defaults.baseURL = "https://jsonplaceholder.typicode.com";

const useAxios = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const sendRequest = useCallback(async (requestConfig, applyData) => {
    setIsLoading(true);
    setError(null);

    let res;
    try {
      res = await axios({
        url: requestConfig.url,
        method: requestConfig.method ? requestConfig.method : 'GET',
        headers: requestConfig.headers ? requestConfig.headers : {},
        data: requestConfig.data ? requestConfig.data : null,
      });
    } catch (error) {
      setError(error.message || 'Something went wrong!');
    } finally {
      if (res) {
        applyData(res.data);
      }
      setIsLoading(false);
    }
  }, []);

  return {
    isLoading,
    error,
    sendRequest,
  };
};

export default useAxios;

完成 useAxios hook,我们就可以实际使用罗!

在 App 元件中呼叫几支 JSONplaceholder 的 api。

import { useEffect, useState } from "react";

import useAxios from "./useAxios";

export default function App() {
  const [data, setData] = useState([]);
  const { isLoading, error, sendRequest: fetchData } = useAxios();
  const { sendRequest: createData } = useAxios();

  useEffect(() => {
    fetchData({ url: "/posts" }, (res) => {
      console.log(res);
      setData(res);
    });
  }, []);

  const clickNewData = () => {
    createData(
      {
        url: "/posts",
        method: "POST",
        data: {
          // id 没加是因为 JSONPlaceholder 只做假新增不会更新到资料库,所以 JSONPlaceholder 会自动产生一个假的 id
          userId: 10,
          title: "新增的物件",
          body: "example"
        }
      },
      (res) => console.log(res)
    );
  };

  if (isLoading) return <h1>Loading...</h1>;
  if (error) return <h1>{error}</h1>;

  return (
    <>
      <ul>
        {data.map((item) => (
          <li key={item.id}>
            <p>{item.title}</p>
          </li>
        ))}
      </ul>
      <button onClick={clickNewData}>新增</button>
    </>
  );
}

这次的实作程序码在以下连结,另外附上 JSONplaceholder 的官网连结:

范例程序码

JSONplaceholder


<<:  Day 09: Valid Palindrome

>>:  [前端暴龙机,Vue2.x 进化 Vue3 ] Day15.组件介绍

gMSA 设定无密码的工作排程 (下)

使用gMSA作为Sql Server的服务帐号 有时候为了让管理跨server的资源更方便,服务就需...

Day29-JDK可视化监控工具:visualVM(五)

前言 延续着上篇的介绍,这篇要来介绍visualVM的Sampler页签 Sampler 这边我延续...

D19: 工程师太师了: 第10话

工程师太师了: 第10话 杂记: 前阵子朋友传了一个社团给我, 里面大致是一个武林盟主的设计, 想要...

[ Raspberry Pi ] Compute module 4 eMMC 烧录流程 ((CM4))

终於拿到手的Raspberry Pi Compute module 4 参考 官网 (确保电脑没有连...

[第二十一只羊] 迷雾森林舞会XV 建立村庄 游戏角色设定

天亮了 昨晚是平安夜 关於迷雾森林故事 习惯 洛神:8号玩家请继续发言 8号: 我的视角,我觉得我的...