[Note] React: Custom Hook


Posted by urlun0404 on 2022-11-20

React官方文件開宗明義地說了:

Building your own Hooks lets you extract component logic into reusable functions.

即custom hook就是將程式碼包裝成一個自訂的hook拿來重複利用,就如同使用vanilla JavaScript會把重複程式碼封裝成一個函式重複使用。

至於custom hook是要包裝什麼樣子的程式碼?

先簡單地說,custom hook就是包裝有重複使用到hook的程式碼片段。


Rules of Hook

在寫custom hook以前,可以順便複習一個重要的React hook使用規則

React內建hooks只能在函式第一層或是costom hook使用。

這段話同時呼應前面為何會說:「custom hook就是包裝有重複使用到hook的程式碼片段。」


Custom Hook

如果程式碼當中有好幾處程式碼同時用相同或類似的邏輯使用hook,這種時候就可以考慮把這些程式碼獨立抽出包裝成custom hook。

舉例來說,我在個人網站每一個段落都用到類似的動畫 ─ 「當使用者垂直捲動捲軸移到該段落就顯示該段落的浮現動畫」,這個效果我是用Web API內建的IntersectionObserver和CSS動畫來實現(可參考我之前的IntersectionObserver使用筆記)。

先來看一段簡化過的程式碼(原始程式碼在這邊):

import { useEffect, useState, useRef } from 'react';

export default function About() {
  const [isInView, setIsInView] = useState(false);
  const observedRef = useRef<HTMLElement>(null);

  useEffect(() => {
    if (observedRef.current) {
      const observer = new IntersectionObserver(
        (entries) => {
          const [entry] = entries;
          if (entry.isIntersecting) {
            setIsInView(true);
            observer.unobserve(entry.target);
          } else {
            setIsInView(false);
          }
        },
        {
          threshold: 0.1,
          rootMargin: '-20px',
        },
      );

      observer.observe(observedRef.current);
    }
  }, [observedRef]);

  return <section id="about" ref={observedRef} className={isInView ? '' : 'hidden'}>...</sction>;

個人網站通常會分成好幾個段落,常見的有簡介(about)、作品集(projects)、聯絡資訊(contact)等,不難想像若每個段落都要做動畫,都會用到:

  • const [isInView, setIsInView] = useState(false);
  • const observedRef = useRef<HTMLElement>(null);
  • useEffect(...)
    這幾段程式碼。

差別可能只在於使用者捲到不同畫面不同位置, <section ... ref={observedRef} > 要參照(refer)並顯示於畫面的段落標籤會不一樣,這個時候就很適合自訂一個hook來包裝這段程式碼。


1. Define a Custom Hook

使用custom hook就跟平常寫function component差不多,但有以下這些差別:

  • 這個function component要以「use」開頭來命名,React看到use開頭component才會判斷這是一個custom hook;
  • return 不像一般的function component要return JSX標籤,可以回傳任何想回傳的值。

接著來把前面那段程式碼封裝成一個custom hook:

import React, { useState, useEffect } from 'react';

export default function useInView(observedRef) {
  const [isInView, setIsInView] = useState(false);

  useEffect(() => {
    if (observedRef.current) {
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          setIsInView(true);
          observer.unobserve(entries[0].target);
        } else {
          setIsInView(false);
        }
      });

      observer.observe(observedRef.current);
    }
  }, [observedRef]);

  return isInView;
}

(以上程式碼同樣有簡化過,原始程式碼在這👈)

可以看到這個custom hook ─ useInView 是以 use 開頭,可以接收一個ref物件 observedRef,並且在最後 return isInView; 這段回傳 isInView 這個state。


2. Usage of Custom Hook

而使用custom hook也和平常使用React內建的hook一樣:

import useInView from 'use-in-view';

export default function About() {
    const observedRef = useRef<HTMLElement>(null);
    const isInView = useInView(observedRef);

    return <section id="about" ref={observedRef} className={isInView ? '' : 'hidden'}>...</sction>;
}

因為每次顯示動畫要參照的段落標籤不同,每個段落還是要有自己的ref object observedRef

不過往下一行 const isInView = useInView(observedRef);,可以看到observedRef被傳入到 useInView,並且用一個 isInView 變數來接收回傳的state。

isInView 在 useInView 程式碼裡的初始值是false,不過當使用者拉動捲軸讓畫面顯示到about的段落時,isInView 的值會變成 true,並且回傳至使用這個hook的component當中,也就是 About 這個component。

看到這邊可能會問「這樣使用useInView hook的component會不會同時接收到回傳的true值」,答案是不會,custom hook就和一般內建hook一樣,每個component在使用的custom hook都是獨立的,同時會有自己的state,所以每個custom hook的state會保有自己的值,不會有一個hook的狀態值改變,其他hook的狀態值同時改變的狀況。


【補充?】
可以將宣告custom hook想成是用純JavaScript在宣告函式內的一般區域變數(local variable)一樣,都是一個新的hook變數,每個函式內區域變數都有自己的範疇(scope),因此每個宣告的新hook變數都會被侷限在所處的函式(function component)內,擁有自己的生命週期和state,並不會互有衝突。


References
Building Your Own Hooks @React Docs
React - The Complete Guide (incl Hooks, React Router, Redux)


#frontend #React #hook #custom hook #note







Related Posts

Node.js 和Node.js REPL 關係

Node.js 和Node.js REPL 關係

AA and AB Testing on Reducing Error Rate

AA and AB Testing on Reducing Error Rate

 Day 171

Day 171


Comments