iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0
Modern Web

開始搞懂React生態系系列 第 11

Day 11 建立可重複在專案使用的客製化 React Hook

  • 分享至 

  • xImage
  •  

說明

當我們想要共享邏輯在兩個 Function 之間時,會提取它成為第三個 Function。

在 React 的世界中,Function Component 和 Hook 二者都是 Function,表示提出第三個共享邏輯的 Custom Hook Function,也是適用的。

一個 Custom Hook 是以 use 為開頭為命名的 JS Function,在它內部也會呼叫其他 Hook。

使用 use 開頭的命名規則,除了可以讓專案後續維護者知道是可以使用的 Hook,還可以讓 Lint 工具自動檢查是否違反 Hook 規則。

實際應用:建立 useMousePosition 來回傳滑鼠位置

首先我們先建立一個 App Component 裡面分別使用二個 Component

// App.jsx
import "./styles.css";
import MyComponentOne from "./components/MyComponentOne";
import MyComponentTwo from "./components/MyComponentTwo";

export default function App() {
  return (
    <div className="App">
      <MyComponentOne className="MyComponentOne" />
      <MyComponentTwo className="MyComponentTwo" />
    </div>
  );
}

元件分別取得滑鼠位置的寫法

// components/MyComponentOne.jsx
import { useEffect, useState } from "react";

export default function MyComponentOne({ className }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const setFromEvent = (e) => {
      const parent = e.target.parentElement;
      if (parent && parent.id && parent.id === "box1") {
        setPosition({ x: e.clientX, y: e.clientY });
      }
    };
    window.addEventListener("mousemove", setFromEvent);

    return () => {
      window.removeEventListener("mousemove", setFromEvent);
    };
  }, []);
  return (
    <div id="box1" className={className}>
      <h1>MyComponentOne</h1>
      <p>x:{position.x}</p>
      <p>y:{position.y}</p>
    </div>
  );
}
// components/MyComponentTwo.jsx
import { useEffect, useState } from "react";

export default function MyComponentTwo({ className }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const setFromEvent = (e) => {
      const parent = e.target.parentElement;
      if (parent && parent.id && parent.id === "box2") {
        setPosition({ x: e.clientX, y: e.clientY });
      }
    };
    window.addEventListener("mousemove", setFromEvent);

    return () => {
      window.removeEventListener("mousemove", setFromEvent);
    };
  }, []);
  return (
    <div id="box2" className={className}>
      <h1>MyComponentTwo</h1>
      <p>x:{position.x}</p>
      <p>y:{position.y}</p>
    </div>
  );
}

執行結果:https://codesandbox.io/s/throbbing-flower-qedyvk?file=/src/App.jsx:0-331

可以發現二個元件取得滑鼠位置的邏輯其實可抽取出共用的 Function,接下來就來試看看如何製作成 Custom Hook 吧

使用 自訂的 useMousePosition 抽取重複邏輯

Step 1. 建立一個 Custom Hook 的檔案,然後使用 use 做為開頭的命名

Step 2. 把 useState 及 useEffect 的邏輯搬運至 useMousePostion 的 Function 內

export const useMousePosition = () => {
  // Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
};  

Step 3. 傳入 elementId 用來判斷 setPostition 的變更時機

export const useMousePosition = (elementId) => {
  // Step 3. ? 傳入 elementId ? 用來判斷 setPostition 的變更時機
  // Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
};  

Step 4. 回傳 postion State

  
export const useMousePosition = (elementId) => {
  // Step 3. ? 傳入 elementId ? 用來判斷 setPostition 的變更時機
  // Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
  ...
  // Step 4. 回傳 postion State
  return position;
};  
useMousePosition Hook 完整檔案如下
// hooks/useMousePosition.js

import { useEffect, useState } from "react";

export const useMousePosition = (elementId) => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const setFromEvent = (e) => {
      const parent = e.target.parentElement;
      if (parent && parent.id && parent.id === elmentId) {
        setPosition({ x: e.clientX, y: e.clientY });
      }
    };
    window.addEventListener("mousemove", setFromEvent);

    return () => {
      window.removeEventListener("mousemove", setFromEvent);
    };
  }, [elmentId]);

  return position;
};

Step 5. 在元件裡使用 useMousePosition

// components/MyComponentOne.jsx
import { useMousePosition } from "../hooks/useMousePosition";

export default function MyComponentOne({ className }) {
  const position = useMousePosition("box1");
  return (
    <div id="box1" className={className}>
      <h1>MyComponentOne</h1>
      <p>x:{position.x}</p>
      <p>y:{position.y}</p>
    </div>
  );
}
// components/MyComponentTwo.jsx
import { useMousePosition } from "../hooks/useMousePosition";

export default function MyComponentTwo({ className }) {
  const position = useMousePosition("box2");
  return (
    <div id="box2" className={className}>
      <h1>MyComponentTwo</h1>
      <p>x:{position.x}</p>
      <p>y:{position.y}</p>
    </div>
  );
}
useMousePostion 完整範例

程式碼及執行結果:https://codesandbox.io/s/recursing-shtern-q3tbv4?file=/src/hooks/useMousePosition.js

範例總結

抽取重複邏輯至 useMousePostion 這個 Custom Hook,可以簡化程式碼如下述

  1. 把在 useEffect 做的事件訂閱/取消訂閱的邏輯都提取出來,元件上不需要關注此事務。元件只需要傳入自己的 elementId,就能取得對應的 postion State。

使用自定義的 Hook 時,可以封裝的複雜邏輯,簡化程式碼的操作,變成易於使用的 Hook Function。

  1. 每一個使用 Custom Hook 的元件內,其 state 都是各自獨立的,Hook 是一種重複使用「靜態邏輯(stateful logic)」的方式,而不是重複使用「狀態(state)」

每次你使用自定義的 Hook 時,所有內部的 state 和 effect 都是完全獨立的

Next

在此之前,已經介紹了 useStateuseEffect 這二個官方內建基本的 Hook,接下來會再陸續介紹一些官方內建的功能性 Hook。接下來介紹的 useRef 也是一款很經常在使用的官方 React Hook。

Reference

https://zh-hant.reactjs.org/docs/hooks-custom.html

https://www.codedaily.io/tutorials/Create-a-useMousePosition-Hook-with-useEffect-and-useState-in-React


上一篇
Day 10 使用 useEffect 的 Tips
下一篇
Day 12 useRef
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言