React Hooks (Less Common, But Useful)
บันทึก React Hooks ที่ไม่ได้ใช้บ่อยเท่า useState กับ useEffect แต่พอถึงเวลาที่ต้องใช้แล้วมีประโยชน์มาก (หรือบางทีก็ลืม Syntax)
useRef
เอาไว้อ้างอิง (Reference) ถึง DOM Element ตรงๆ หรือเอาไว้เก็บค่าบางอย่างที่เปลี่ยนไปแล้วจะไม่ทำให้ Component Re-render
import { useRef, useEffect } from "react"
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
// ใช้เก็บค่าที่แก้แล้วไม่เกิด Re-render
const renderCount = useRef(0)
useEffect(() => {
// อ้างอิงถึง DOM เพื่อสั่ง focus
inputRef.current?.focus()
renderCount.current += 1
}, [])
return <input ref={inputRef} placeholder="พิมพ์ที่นี่..." />
}useMemo
เอาไว้จำค่าที่คำนวณยากๆ (Expensive Computation) จะได้ไม่ต้องคำนวณใหม่ทุกครั้งที่ Component Re-render จะคำนวณใหม่ก็ต่อเมื่อ Dependency เปลี่ยนเท่านั้น
import { useMemo, useState } from "react"
function FilterList({ items, filterText }) {
// สมมติว่าการ filter ใช้พลังประมวลผลเยอะมาก
const filteredItems = useMemo(() => {
console.log("Filtering items...")
return items.filter((item) => item.name.includes(filterText))
}, [items, filterText]) // เปลี่ยนเมื่อ items หรือ filterText เปลี่ยนเท่านั้น
return (
<ul>
{filteredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}useCallback
คล้ายๆ useMemo แต่แทนที่จะจำ "ค่า" มันเอาไว้จำ "ฟังก์ชัน" มักใช้คู่กับ React.memo เวลาส่งฟังก์ชันไปให้ Component ลูก เพื่อป้องกันไม่ให้ Component ลูก Re-render ฟรีๆ
import { useCallback, useState } from "react"
function ParentComponent() {
const [count, setCount] = useState(0)
// ถ้าไม่ใส่ useCallback ฟังก์ชันนี้จะถูกสร้างใหม่ทุกครั้งที่ Parent Re-render
const handleSubmit = useCallback((data) => {
console.log("Submit:", data)
}, []) // ไม่มี Dependency แปลว่าจำฟังก์ชันนี้ตลอดไป
return <ChildComponent onSubmit={handleSubmit} />
}useReducer
เอาไว้จัดการ State ที่มีความซับซ้อน หรือมีหลาย Action ที่เกี่ยวข้องกัน (คล้ายๆ Redux แต่ใช้แค่ใน Component ตัวเอง)
import { useReducer } from "react"
// 1. สร้าง Reducer Function
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 }
case "decrement":
return { count: state.count - 1 }
case "reset":
return { count: 0 }
default:
return state
}
}
function Counter() {
// 2. เรียกใช้
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
)
}useLayoutEffect
ทำงานคล้าย useEffect เลย แต่มันจะทำงานก่อนที่เบราว์เซอร์จะเพนต์จอ (Paint) ให้ผู้ใช้เห็น เหมาะสำหรับการวัดขนาดกล่อง (DOM Measurement) เพื่อป้องกันหน้าจอกะพริบ (Flicker)
ข้อควรระวัง: ใช้เฉพาะตอนที่จำเป็นจริงๆ เท่านั้น เพราะมันจะ Block การวาดหน้าจอ ทำให้เว็บดูช้าลง
import { useLayoutEffect, useRef, useState } from "react"
function Tooltip() {
const boxRef = useRef<HTMLDivElement>(null)
const [height, setHeight] = useState(0)
useLayoutEffect(() => {
// วัดขนาดให้เสร็จก่อนวาดขึ้นจอ
if (boxRef.current) {
setHeight(boxRef.current.getBoundingClientRect().height)
}
}, [])
return <div ref={boxRef}>Box Height: {height}</div>
}useTransition
เอาไว้บอก React ว่าการอัปเดต State ครั้งนี้ "รอก่อนได้" (Non-urgent) ไม่ต้องรีบทำทันที ปล่อยให้หน้าจอแสดงผลอย่างอื่นไปก่อน (เช่น ให้ผู้ใช้พิมพ์ต่อได้ ไม่ค้าง)
import { useState, useTransition } from "react"
function Search() {
const [isPending, startTransition] = useTransition()
const [query, setQuery] = useState("")
const handleChange = (e) => {
// การอัปเดตที่อยากให้ทำทันที
setQuery(e.target.value)
// การอัปเดตที่กินพลังงานเยอะ และ "รอได้"
startTransition(() => {
// เรียกฟังก์ชันค้นหาที่ใช้เวลานาน
performHeavySearch(e.target.value)
})
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading results...</p>}
</div>
)
}