setState & the Lifecycle
setState ทำอะไรกันแน่
หัวข้อที่มีชื่อว่า “setState ทำอะไรกันแน่”setState ไม่ได้ “วาดหน้าจอใหม่” ทำสิ่งเล็ก ๆ อย่างเดียว: รัน callback ของคุณ (ที่ mutate field) แล้ว mark element ของ widget นี้เป็น dirty พอถึง frame ถัดไป Flutter จะ rebuild ทุก element ที่ dirty โดยเรียก build อีกครั้ง
class Counter extends StatefulWidget { const Counter({super.key}); @override State<Counter> createState() => _CounterState();}
class _CounterState extends State<Counter> { int _count = 0;
void _increment() { setState(() { _count++; // mutate state ข้างใน callback }); // แล้ว Flutter mark element นี้เป็น dirty }
@override Widget build(BuildContext context) { return TextButton( onPressed: _increment, child: Text('count: $_count'), ); }}มีกฎสองข้อที่ตามมา: mutate state ข้างใน callback ของ setState (เพื่อให้การเปลี่ยนและการ mark dirty เกิดพร้อมกัน) และรู้ไว้ว่า setState schedule rebuild สำหรับ frame ถัดไป — ไม่ใช่ synchronous
State lifecycle
หัวข้อที่มีชื่อว่า “State lifecycle”State object มี lifecycle ที่ชัดเจน รู้ว่า hook ไหนทำอะไรช่วยกัน bug ทั้งกลุ่มได้
flowchart TB create["createState()"] --> init["initState() setup ครั้งเดียว"] init --> deps["didChangeDependencies() หลัง inherited deps พร้อม"] deps --> build["build() เรียกทุกครั้งที่ rebuild"] build --> update["didUpdateWidget() parent rebuild ด้วย config ใหม่"] update --> build build --> dispose["dispose() cleanup"]
initState— setup ครั้งเดียว: สร้าง controller, subscribe stream รันครั้งเดียว ตรงนี้ยังใช้contextกับ inherited widget อย่างเชื่อถือได้ไม่ได้didChangeDependencies— รันหลังinitStateและรันอีกทุกครั้งที่ inherited dependency เปลี่ยน เป็นที่ปลอดภัยสำหรับอ่าน data ของInheritedWidget/Providerที่ setup ต้องพึ่งbuild— เรียกทุกครั้งที่ rebuild ต้อง pure และเร็ว: ไม่มี side effect, ไม่มี network call, ไม่มี subscription แค่ describe UI จาก state ปัจจุบันdidUpdateWidget— parent rebuild แล้วส่ง widget config ใหม่ให้ State นี้; react ต่อ props ที่เปลี่ยน (เช่น re-subscribe ถ้าidเปลี่ยน)dispose— cleanup: dispose controller, cancel subscription ลืมตรงนี้คือ memory leak คลาสสิกของ Flutter
scope ของ rebuild
หัวข้อที่มีชื่อว่า “scope ของ rebuild”setState mark element ของ State นี้ เป็น dirty ดังนั้น build รันใหม่และผลิต subtree ใหม่ — แต่ Flutter ฉลาดเรื่องนี้ จะ diff widget subtree ใหม่กับ element tree เดิมและแตะเฉพาะส่วนที่เปลี่ยน ถึงอย่างนั้น build method ทั้งตัวก็รันอยู่ดี ดังนั้น setState ที่อยู่สูงใน tree แล้ว rebuild subtree ใหญ่ ๆ คือปัญหา performance จริง
บทเรียนเชิงปฏิบัติ: ดัน setState ให้ต่ำใน tree มากที่สุด แยกส่วนที่เปลี่ยนออกไปเป็น StatefulWidget เล็ก ๆ ของตัวเอง เพื่อให้เฉพาะ subtree เล็ก ๆ นั้น rebuild ไม่ใช่ทั้งหน้า (โมดูล performance เจาะลึกเรื่องนี้)