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

Context

Context ให้ parent ทำให้ค่าหนึ่งพร้อมใช้กับทุก component ที่อยู่ข้างล่าง โดยไม่ต้องส่ง props ผ่านชั้นตรงกลาง คุณสร้าง context, ห่อ subtree ด้วย provider แล้ว descendant ตัวไหนก็อ่านค่านั้นได้ด้วย useContext

import { createContext, useContext, useState } from "react";
const ThemeContext = createContext("light");
function App() {
const [theme, setTheme] = useState("dark");
return (
// React 19: you can render <ThemeContext> directly as the provider.
// (The classic <ThemeContext.Provider> still works too.)
<ThemeContext value={theme}>
<Page />
</ThemeContext>
);
}
function Button() {
const theme = useContext(ThemeContext); // reads the nearest provider's value
return <button className={theme}>Click</button>;
}

Button ได้ theme ไม่ว่าจะอยู่ลึกแค่ไหน และไม่มี component ตรงกลางต้องส่งต่อค่านั้น นี่คือทางแก้ prop drilling เมื่อ composition เอื้อมไปถึง consumer ไม่ได้

Context ถูกออกแบบมาสำหรับค่าที่ แชร์กว้างและเปลี่ยนไม่บ่อย: theme, user/auth ปัจจุบัน, locale, config ของ design-system สิ่งที่หลาย component อ่านแต่เปลี่ยนน้อย

จุดที่ต้องระวังคือ performance: เมื่อ value ของ provider เปลี่ยน ทุก consumer จะ re-render — และ React หยุดการ re-render นั้นด้วย memo ไม่ได้ เพราะ consumer subscribe context โดยตรง

flowchart TB
  prov["value ของ Provider เปลี่ยน"] --> c1["Consumer A re-render"]
  prov --> c2["Consumer B re-render"]
  prov --> c3["Consumer C re-render"]
  note["memo หยุด context re-render ไม่ได้"] -.-> c1
value ของ context เปลี่ยนทีทำให้ทุก consumer re-render

ดังนั้นการเอา state ที่ เปลี่ยนเร็ว (เช่นค่าที่ update ทุก keystroke หรือทุก animation frame) ไปใส่ context กว้าง ๆ ตัวเดียวคือกับดัก performance — จะ re-render ทั้ง subtree ตลอดเวลา

มีสองวิธีบรรเทา:

  • Split context. แยกค่าที่ไม่เกี่ยวกันไว้คนละ provider เพื่อให้การเปลี่ยนตัวหนึ่งไม่ re-render consumer ของอีกตัว การ split ที่พบบ่อย: context หนึ่งสำหรับ state และอีกหนึ่งสำหรับ dispatch/updater (updater stable อยู่แล้ว ดังนั้น component ที่ dispatch อย่างเดียวจะไม่ re-render)
  • ทำให้ value stable. memoize object value ของ provider เพื่อไม่ให้ได้ reference ใหม่ทุกครั้งที่ parent render

ความแตกต่างสำคัญ: Context เป็นกลไก ขนส่ง ไม่ใช่ที่เก็บ state Context ย้ายค่าลงไปตาม tree ไม่ได้ถือหรือ update state ตัว state เองยังอยู่ใน useState หรือ useReducer ใน component ที่เป็น provider “ใช้ Context สำหรับ state management” จริง ๆ แล้วหมายถึง “useState/useReducer ข้างบน แล้วส่งด้วย Context”

function AuthProvider({ children }) {
const [user, setUser] = useState(null); // the state lives here
return <AuthContext value={{ user, setUser }}>{children}</AuthContext>; // Context just delivers it
}
Context แก้ปัญหาอะไร?
เกิดอะไรกับ consumer เมื่อ value ของ context provider เปลี่ยน?
ทำไม Context ถึง "ไม่ใช่ state manager"?
อะไรคือวิธีบรรเทา performance ของ context re-render ที่ดี?