build and BuildContext
contract ของ build
หัวข้อที่มีชื่อว่า “contract ของ build”build มีหน้าที่เดียว: คืน widget tree ที่บรรยาย UI สำหรับ state ปัจจุบัน ควรเป็น function ที่ pure และเร็วของ field กับ state ของ widget เท่านั้น ไม่มากไปกว่านั้น
กฎที่สำคัญ: build ถูกเรียกได้บ่อยมาก — ทุกเฟรมของ animation, ทุก setState, ทุกครั้งที่ ancestor rebuild ดังนั้น build ต้องถูกและปราศจาก side effect
@overrideWidget build(BuildContext context) { // ✅ ดี: แค่บรรยาย UI จาก state ปัจจุบัน return Text('Count: $_count');}@overrideWidget build(BuildContext context) { // ❌ แย่: side effect ใน build อันนี้ยิงทุก rebuild _apiClient.fetchUser(); // network call — leak, ยิงซ้ำ ๆ _controller = AnimationController(vsync: this); // สร้างใหม่ทุก build! return Text('...');}side effect — network call, สร้าง controller, subscribe stream — ควรอยู่ใน initState (ครั้งเดียว) หรือ event handler ไม่ใช่ใน build มอง build เป็นการ render แบบอ่านอย่างเดียว
BuildContext คืออะไรจริง ๆ
หัวข้อที่มีชื่อว่า “BuildContext คืออะไรจริง ๆ”ทุก build ได้รับ BuildContext ไม่ใช่ถุงของ global แต่เป็น handle ไปยังตำแหน่งของ widget นี้ใน element tree (จริง ๆ แล้ว BuildContext คือ element) ตำแหน่งนั้นแหละที่ทำให้ widget มองขึ้นไปด้านบนของ tree เพื่อหา inherited data ได้
@overrideWidget build(BuildContext context) { // context รู้ว่าเราอยู่ตรงไหน จึงค้น ancestor ได้: final theme = Theme.of(context); // Theme ที่ใกล้ที่สุดเหนือเรา final media = MediaQuery.of(context); // MediaQuery ที่ใกล้ที่สุดเหนือเรา return Text('Screen is ${media.size.width} wide', style: theme.textTheme.bodyMedium);}Theme.of(context) เดินขึ้นจาก element นี้เพื่อหา Theme ancestor ที่ใกล้ที่สุด นี่คือเหตุผลที่ context สำคัญ: การเรียก .of(context) เดียวกัน คืนผลต่างกันขึ้นกับว่า widget อยู่ตรงไหนใน tree
flowchart BT here["widget นี้ (context = element ของตัวเอง)"] --> p1["Padding"] p1 --> p2["Theme (เจอ!)"] p2 --> root["MaterialApp"] here -.->|"Theme.of(context) ค้นขึ้นบน"| p2
pitfall คลาสสิกของ context
หัวข้อที่มีชื่อว่า “pitfall คลาสสิกของ context”เพราะ .of(context) ค้น ขึ้นบน บั๊กที่พบบ่อยคือใช้ context ที่อยู่ เหนือ widget ที่คุณกำลังหา เช่น เรียก Scaffold.of(context) ด้วย context ของ build method เดียวกับที่ สร้าง Scaffold นั้นจะล้มเหลว — context นั้นอยู่เหนือ Scaffold ไม่ใช่ใต้ Scaffold วิธีแก้คือใช้ Builder (หรือ widget ลูก) เพื่อให้ได้ context ที่อยู่ ใต้ เป้าหมาย