แยก logic ที่มี state ออกมาเป็น hook ที่นำกลับมาใช้ซ้ำได้ ประกอบกันได้ และทดสอบได้
เปิดบทเรียนนี้ใน Kodokoncustom hook คือฟังก์ชันที่ชื่อขึ้นต้นด้วย use และที่เรียก hook อื่นๆ มันแยก logic ที่มี state ออกมา ไม่ใช่ state ที่แบ่งปันกัน: ทุก component ที่เรียกมันจะได้ useState และ useEffect ของตัวเอง แยกออกจากกันโดยสมบูรณ์ มันเป็นเครื่องมือที่ตรงที่สุดของ React ในการดึง logic ออกมา - ในขณะที่ HOC และ render props เพิ่มชั้นให้กับต้นไม้ hook แทรกเข้าไปในตัว component ประกอบเข้าด้วยกันได้ง่าย และทดสอบได้ง่าย
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;
}คำนำหน้า use ไม่ได้มีไว้ประดับ: มันคือสิ่งที่ทำให้ ESLint plugin react-hooks ตรวจสอบกฎของ hook ภายในฟังก์ชันของคุณได้ - การเรียกแบบไม่มีเงื่อนไข ที่ระดับบนสุด ระวังรูปร่างของ API ที่คืนมาด้วย: tuple แบบ [value, setValue] เมื่อการใช้งานสะท้อน useState, object ที่มี key ตั้งชื่อทันทีที่มีมากกว่าสองสิ่งให้เปิดเผย
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];
}hook ประกอบกันได้: hook หนึ่งสามารถเรียก hook อื่น รวมถึง hook ของคุณเองด้วย นั่นคือวิธีที่ business abstraction ที่อ่านง่ายเกิดขึ้น โดยสร้างบนบล็อกพื้นฐานแบบ generic สังเกตการจับคู่ของ AbortController กับฟังก์ชัน cleanup: หากไม่มีมัน request ที่ยิงไปสำหรับ input เก่ากว่าอาจตอบกลับมาหลัง request ล่าสุดและเขียนทับผลลัพธ์ของมัน - นี่คือ race condition คลาสสิกของช่องค้นหา
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;
}