Lifting State & Composition
Lifting state up
หัวข้อที่มีชื่อว่า “Lifting state up”เมื่อสอง component ต้องใช้ state ก้อนเดียวกัน state นั้นจะอยู่ใน component ใด component หนึ่งไม่ได้ — state นั้นต้องย้าย ขึ้น ไปที่ common ancestor ที่ใกล้ที่สุด แล้วส่งลงมาเป็น props นี่คือ lifting state up และเป็นคำตอบเริ่มต้นของ “สอง component นี้ต้อง sync กัน”
// The parent owns the state; both children read/update it through props.function TemperatureApp() { const [celsius, setCelsius] = useState(0); return ( <> <CelsiusInput value={celsius} onChange={setCelsius} /> <FahrenheitLabel celsius={celsius} /> </> );}state มี single source of truth (คือ parent) และข้อมูลไหลทางเดียว: ลงมาเป็น props และย้อนขึ้นผ่าน callback สอง input ไม่มีทางขัดแย้งกันเพราะมีค่าเดียว
ปัญหา prop drilling
หัวข้อที่มีชื่อว่า “ปัญหา prop drilling”lifting ทำงานได้สวยงามจนกระทั่ง ancestor อยู่ ไกล เหนือ consumer มาก ตอนนั้น props ต้องร้อยผ่านทุก component ตรงกลางที่ไม่ได้สนใจค่านั้นเลย:
// theme is only used by Button, but every layer must pass it through.<App theme={theme}> <Page theme={theme}> <Sidebar theme={theme}> <Button theme={theme} /> {/* finally used here */} </Sidebar> </Page></App>ทุก component ตรงกลางได้ prop ที่แค่ส่งต่อ นี่คือ prop drilling — รก และเป็นฝันร้ายตอน refactor สัญชาตญาณคือกระโดดไป Context ทันที แต่มีทางแก้ที่ง่ายกว่าให้ลองก่อน
Composition: ส่ง JSX เป็น props
หัวข้อที่มีชื่อว่า “Composition: ส่ง JSX เป็น props”ก่อนไป Context ให้ลอง composition ก่อน แทนที่จะ drill ค่าผ่าน component ให้ส่ง JSX ที่ render แล้ว เข้าไปเป็น children (หรือ prop ที่ตั้งชื่อ) เพื่อให้ component ตรงกลางไม่เห็นค่านั้นเลย
// Sidebar doesn't know about `theme` — it just renders whatever children it's given.function Sidebar({ children }) { return <aside className="sidebar">{children}</aside>;}
// The Button is created where `theme` is in scope, then handed to Sidebar as content.<Page> <Sidebar> <Button theme={theme} /> {/* theme is bound here, not drilled */} </Sidebar></Page>flowchart TB
subgraph drill["Prop drilling"]
a1["App theme"] --> p1["Page theme"] --> s1["Sidebar theme"] --> b1["Button ใช้ theme"]
end
subgraph comp["Composition"]
a2["App: render Button ตรงนี้"] --> s2["Sidebar: render children"]
a2 -.ส่ง JSX.-> s2
end คุณยังส่ง JSX ผ่าน prop ที่ตั้งชื่อได้ด้วย (<Layout sidebar={<Nav />} main={<Feed />} />) component ตรงกลางจะกลายเป็น container ทั่วไป (“render อะไรก็ตามที่ถูกส่งมา”) ซึ่ง reuse ได้ดีกว่า และ กำจัด drilling ไปด้วย Context เอาไว้ใช้ตอนที่แม้แต่ composition ก็เอื้อมไม่ถึง — ข้อมูลแบบ cross-cutting จริง ๆ ที่ถูกใช้กระจัดกระจายหลายที่