ข้ามไปยังเนื้อหา

Refs, Portals & Escape Hatches

99% ของ React เป็น declarative: เปลี่ยน state, describe UI, ให้ React sync DOM แต่ปัญหาจริงบางอย่างต้องให้คุณก้าว ออกนอก model นั้น — เอื้อมเข้าไปใน DOM, บังคับ timing หรือ render ไปยังที่ที่ component tree ไม่เปิดให้โดยธรรมชาติ React ให้ escape hatch ที่ตั้งใจไว้สำหรับสิ่งเหล่านี้ ทักษะคือรู้ว่ามีอยู่และใช้อย่างประหยัด

portal render children ไปยังส่วน อื่น ของ DOM tree โดยที่ยังคงอยู่ใน React tree ตรงที่คุณเขียน นี่คือคำตอบมาตรฐานสำหรับ modal, tooltip และ dropdown — UI ที่ต้องหนี overflow: hidden หรือ stacking context ของ z-index ของ parent

import { createPortal } from "react-dom";
function Modal({ children }) {
// render เข้า document.body แต่ยังเป็น React child ของที่ที่ <Modal/> ถูกใช้
return createPortal(
<div className="overlay">{children}</div>,
document.body,
);
}
flowchart TB
  react["React tree:
App -> Page -> Modal -> content"]
  dom["DOM tree:
body -> app-root ...
body -> overlay (portal target)"]
  react -->|ทางตรรกะเป็น child ของ Page| dom
  note["event ยัง bubble ผ่าน React tree
ไม่ใช่ตำแหน่ง DOM"]
portal แยกตำแหน่ง DOM ออกจากตำแหน่งใน tree

จุดละเอียดสำคัญ: portal เปลี่ยน ที่ที่ DOM node อยู่ ไม่ใช่ ที่ที่อยู่ใน React tree context ยังไหลเข้า และ event ยัง bubble ขึ้นผ่าน React parent — ดังนั้นการคลิกภายใน modal ที่ portal ยังไปถึง handler บน React ancestor เชิงตรรกะของตัวเอง

ส่วนใหญ่ data ไหลลงผ่าน props แต่บางครั้ง parent ต้อง imperatively สั่งอะไรบางอย่างบน child หรือ DOM node — focus input, เล่น video, scroll ไปยัง element ref ให้ handle นั้นกับคุณ

function SearchForm() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}

ถ้าจะเปิดเผย imperative API แบบกำหนดเอง จาก component (แทน DOM node ดิบ) ใช้ useImperativeHandle กำหนดว่า ref ของ parent เรียก method อะไรได้บ้าง เลือกใช้ props/state สำหรับสิ่งที่เป็น declarative ได้ และหยิบ imperative ref มาเฉพาะ action ที่เป็น imperative จริง (focus, scroll, media, การวัดขนาด)

React batch การ update state และ apply DOM change แบบ asynchronous เพื่อ performance บางครั้งคุณต้องการให้ DOM ถูก update แบบ synchronous — เช่นเพื่ออ่าน layout ใหม่ทันที (วัด element ที่เพิ่งเพิ่ม แล้ว scroll ไปหา) flushSync บังคับให้ state update และการ commit DOM ของตัวเองเกิดขึ้นทันที

import { flushSync } from "react-dom";
flushSync(() => setItems([...items, newItem])); // DOM ถูก update แล้วเมื่อบรรทัดนี้ return
listRef.current.scrollTop = listRef.current.scrollHeight; // ตอนนี้อ่าน/scroll ได้ปลอดภัย

flushSync ทำลาย batching และทำ performance แย่ลง ดังนั้นใช้เฉพาะเมื่อจำเป็นจริง ๆ ที่ต้องอ่านหรือทำอะไรกับ DOM ก่อน browser paint

`createPortal` ทำอะไร?
การคลิกภายใน modal ที่ portal — event bubble ไปที่ไหน?
ควรหยิบ imperative ref มาใช้แทน props/state เมื่อไร?
`flushSync` มีไว้ทำอะไร?