Kodokon kodokon.com

Custom hooks: การแยกและนำ logic กลับมาใช้ซ้ำ

แยก logic ที่มี state ออกมาเป็น hook ที่นำกลับมาใช้ซ้ำได้ ประกอบกันได้ และทดสอบได้

8 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

custom hook คือฟังก์ชันที่ชื่อขึ้นต้นด้วย use และที่เรียก hook อื่นๆ มันแยก logic ที่มี state ออกมา ไม่ใช่ state ที่แบ่งปันกัน: ทุก component ที่เรียกมันจะได้ useState และ useEffect ของตัวเอง แยกออกจากกันโดยสมบูรณ์ มันเป็นเครื่องมือที่ตรงที่สุดของ React ในการดึง logic ออกมา - ในขณะที่ HOC และ render props เพิ่มชั้นให้กับต้นไม้ hook แทรกเข้าไปในตัว component ประกอบเข้าด้วยกันได้ง่าย และทดสอบได้ง่าย

JAVASCRIPT
import { useEffect, useState } from "react";

function useDebouncedValue(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => {
      setDebounced(value);
    }, delay);
    return () => clearTimeout(id);
  }, [value, delay]);

  return debounced;
}
cleanup ยกเลิก timer ก่อนหน้าในทุกการเปลี่ยนแปลง

คำนำหน้า use ไม่ได้มีไว้ประดับ: มันคือสิ่งที่ทำให้ ESLint plugin react-hooks ตรวจสอบกฎของ hook ภายในฟังก์ชันของคุณได้ - การเรียกแบบไม่มีเงื่อนไข ที่ระดับบนสุด ระวังรูปร่างของ API ที่คืนมาด้วย: tuple แบบ [value, setValue] เมื่อการใช้งานสะท้อน useState, object ที่มี key ตั้งชื่อทันทีที่มีมากกว่าสองสิ่งให้เปิดเผย

JAVASCRIPT
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored !== null
      ? JSON.parse(stored)
      : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}
การ initialize แบบ lazy: localStorage ถูกอ่านเฉพาะตอน mount

hook ประกอบกันได้: hook หนึ่งสามารถเรียก hook อื่น รวมถึง hook ของคุณเองด้วย นั่นคือวิธีที่ business abstraction ที่อ่านง่ายเกิดขึ้น โดยสร้างบนบล็อกพื้นฐานแบบ generic สังเกตการจับคู่ของ AbortController กับฟังก์ชัน cleanup: หากไม่มีมัน request ที่ยิงไปสำหรับ input เก่ากว่าอาจตอบกลับมาหลัง request ล่าสุดและเขียนทับผลลัพธ์ของมัน - นี่คือ race condition คลาสสิกของช่องค้นหา

JAVASCRIPT
function useUserSearch(query) {
  const debounced = useDebouncedValue(query, 400);
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (debounced === "") {
      setResults([]);
      return;
    }
    const controller = new AbortController();
    fetch("/api/search?q=" + debounced, {
      signal: controller.signal,
    })
      .then((res) => res.json())
      .then(setResults)
      .catch(() => {});
    return () => controller.abort();
  }, [debounced]);
  return results;
}
hook เชิงธุรกิจสร้างต่อยอดจาก hook แบบ generic

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. จริงๆ แล้วคำนำหน้า use ของ custom hook มีไว้เพื่ออะไร?
    • มันเป็นแค่ธรรมเนียมด้านหน้าตาที่สืบทอดมาจาก class
    • มันช่วยให้ linter ระบุว่าฟังก์ชันเป็น hook และตรวจสอบกฎของ hook ภายในมัน
    • React ปฏิเสธที่จะรันฟังก์ชันที่มี state โดยไม่มีคำนำหน้านี้
    • มัน trigger การ import อัตโนมัติจาก react
  2. สอง component เรียก useLocalStorage ด้วย key เดียวกัน พวกมันแบ่งปัน state กันหรือไม่?
    • ใช่: hook ซิงค์ผู้เรียกทั้งหมดของมันโดยอัตโนมัติ
    • ไม่ และข้อมูลถูกจัดเก็บภายใต้ storage key สองตัวที่แตกต่างกัน
    • ไม่: การเรียกแต่ละครั้งเป็นเจ้าของ React state ของตัวเอง; มีเพียงข้อมูลใน localStorage เท่านั้นที่แบ่งปันกัน
  3. ใน useDebouncedValue หน้าที่ของฟังก์ชัน cleanup ที่ useEffect คืนมาคืออะไร?
    • ยกเลิก timer ที่ค้างอยู่เพื่อให้เฉพาะค่าล่าสุดที่พิมพ์เท่านั้นที่ถูกเผยแพร่หลังจากหน่วงเวลา
    • รีเซ็ตค่า debounced ทุกครั้งที่ component ถูก unmount
    • บล็อกการ re-render ของ component ตลอดระยะเวลาของการหน่วง