State คือออบเจกต์กลางในการจัดการข้อมูลใน Flutter ที่เชื่อมโยงกับ StatefulWidget และรับผิดชอบในการจัดเก็บข้อมูลที่เปลี่ยนแปลงได้และการสร้างอินเทอร์เฟซ ตามเอกสารทางการของ Flutter (Flutter.dev, 2026) State มีอยู่ตลอดวงจรชีวิตของวิดเจ็ตและอยู่รอดจากการสร้างใหม่ ทำให้มั่นใจได้ถึงความสอดคล้องของข้อมูลระหว่างการอัปเดต UI แตกต่างจากวิดเจ็ต本身 State สามารถปรับเปลี่ยนฟิลด์ของมันและเริ่มต้นการสร้างใหม่ผ่านการเรียก setState
ประเด็นสำคัญ
State คือออบเจกต์ในสถาปัตยกรรม Flutter ที่จัดเก็บข้อมูลที่เปลี่ยนแปลงได้ของ StatefulWidget และกำหนดว่าข้อมูลนี้จะแสดงในอินเทอร์เฟซอย่างไร แต่ละ StatefulWidget เมื่อถูกแทรกเข้าไปในทรี จะสร้างออบเจกต์ State หนึ่งอันอย่างแม่นยำผ่านเมธอด createState State มีอยู่อย่างอิสระจากวิดเจ็ต: หากพาเรนต์สร้าง StatefulWidget ใหม่ด้วยพารามิเตอร์ใหม่ State จะยังคงเหมือนเดิมและได้รับวิดเจ็ตที่อัปเดตผ่านคุณสมบัติ widget
ตามภาพรวมสถาปัตยกรรม Flutter (Google, 2026) การแยก Widget และ State เป็นการตัดสินใจทางสถาปัตยกรรมที่จงใจเพื่อให้เฟรมเวิร์กสามารถนำองค์ประกอบของทรีกลับมาใช้ใหม่ได้ วิดเจ็ต (คำอธิบายที่มีน้ำหนักเบา) สามารถถูกสร้างและทำลายได้หลายครั้ง แต่ State (ออบเจกต์หนักที่มีข้อมูล) ยังคงอยู่ในหน่วยความจำตราบเท่าที่องค์ประกอบยังอยู่ในทรี ซึ่งป้องกันการสูญเสียข้อมูลระหว่างการสร้างใหม่บ่อยครั้งของวิดเจ็ตพาเรนต์
State implements อินเทอร์เฟซ StatefulWidget ผ่านเจเนอริก: class _MyState extends State<MyWidget> เจเนอริกผูก State กับประเภท StatefulWidget ที่เฉพาะเจาะจง ให้การเข้าถึงฟิลด์อย่างปลอดภัยผ่านคุณสมบัติ widget
ออบเจกต์ State ถูกเก็บไว้ใน StatefulElement — ชั้นกลางระหว่าง Widget และ RenderObject StatefulElement สร้าง State ผ่าน createState เก็บการอ้างอิงไว้ และส่ง State เป็นเจ้าของ Element จะถูกทำลายเมื่อวิดเจ็ตถูกลบออกจากทรีเท่านั้น — จนกว่าจะถึงเวลานั้น State จะอยู่ในหน่วยความจำ
วงจรชีวิต ของ State เป็นแบบกำหนดการณ์และประกอบด้วยลำดับการเรียกที่เข้มงวด การเข้าใจลำดับนี้เป็นพื้นฐานสำหรับการจัดการทรัพยากรที่ถูกต้องและการป้องกันหน่วยความจำรั่ว
initState ถูกเรียกเป็นอันดับแรกเมื่อ State ถูกสร้าง ในเมธอดนี้ ตัวควบคุม การสมัครรับสตรีม ตัวจับเวลา และค่าเริ่มต้นของฟิลด์จะถูกเริ่มต้น การเรียก super.initState() ในบรรทัดแรกเป็นสิ่งจำเป็น ในขั้นตอน initState ทรีของวิดเจ็ตยังไม่ได้ถูกเชื่อมต่ออย่างสมบูรณ์ ดังนั้นเมธอดเช่น MediaQuery.of(context) อาจทำงานไม่ถูกต้อง
didChangeDependencies ถูกเรียกหลังจาก initState และทุกครั้งที่การพึ่งพา InheritedWidget เปลี่ยนแปลง ที่นี่ ไม่ใช่ใน initState ที่ควรเรียก MediaQuery.of(context) หรือ Theme.of(context) เพราะเมื่อถึงเวลานี้ทรีถูกเชื่อมต่อแล้ว เมธอดนี้ยังถูกเรียกด้วยหากวิดเจ็ตย้ายไปยังบริบทอื่นที่ InheritedWidget ให้ค่าอื่น
build คือเมธอดหลักของ State ที่ส่งคืนทรีของวิดเจ็ต มันถูกเรียกหลังจาก initState หลังจาก didChangeDependencies และหลังจากแต่ละ setState เมธอด build ไม่ควรมีผลข้างเคียง — มันอธิบายอินเทอร์เฟซตามค่าปัจจุบันของฟิลด์ State เท่านั้น
didUpdateWidget ถูกเรียกเมื่อพาเรนต์สร้าง StatefulWidget ใหม่ด้วยพารามิเตอร์ใหม่ State เข้าถึงวิดเจ็ตเก่าผ่าน oldWidget และสามารถเปรียบเทียบกับวิดเจ็ตใหม่ได้ หากพารามิเตอร์เปลี่ยนแปลง สามารถอัปเดตสถานะ โหลดข้อมูลใหม่ หรือเริ่มการเคลื่อนไหวใหม่ได้
dispose คือเมธอดสุดท้ายที่ทรัพยากรทั้งหมดถูกปล่อย: ตัวควบคุม การสมัครรับข้อมูล ตัวจับเวลา หลังจาก dispose State ถูกทำเครื่องหมายว่าตาย: mounted ส่งคืน false การเรียก setState จะโยนข้อยกเว้น การเรียก super.dispose() ในบรรทัดสุดท้ายของเมธอดเป็นสิ่งจำเป็น
| เมธอด | เมื่อถูกเรียก | จำเป็นต้องมี super |
|---|---|---|
| initState | เมื่อสร้าง State | ใช่ ในบรรทัดแรก |
| didChangeDependencies | หลังจาก initState และเมื่อ InheritedWidget เปลี่ยนแปลง | ใช่ |
| build | หลังจาก initState, didChangeDependencies, setState | ไม่ |
| didUpdateWidget | เมื่อได้รับวิดเจ็ตใหม่จากพาเรนต์ | ใช่ |
| setState | เมื่อนักพัฒนาเรียก | ไม่ |
| dispose | เมื่อลบออกจากทรี | ใช่ ในบรรทัดสุดท้าย |
กลไกการทำงานของ State ขึ้นอยู่กับหลักการสำคัญสามประการ: การเชื่อมโยงกับ Element การตอบสนองผ่าน setState และการเข้าถึงพาเรนต์ผ่านคุณสมบัติ widget เมื่อ Flutter สร้างทรีขององค์ประกอบและพบ StatefulElement มันจะเรียก createState ของวิดเจ็ตที่เกี่ยวข้อง State ที่ถูกสร้างจะถูกเก็บไว้ในองค์ประกอบและมีอยู่จนกว่าองค์ประกอบจะถูกลบ
เมื่อ setState ถูกเรียก State จะทำเครื่องหมายตัวเองว่าสกปรกและกำหนดเวลาการสร้างใหม่สำหรับเฟรมถัดไป สำคัญ: setState ไม่เรียก build ทันที — มันลงทะเบียนความจำเป็นในการสร้างใหม่เท่านั้น Flutter รวบรวมองค์ประกอบสกปรกทั้งหมดในเฟรมปัจจุบันและสร้างใหม่เป็นชุด ซึ่งเพิ่มประสิทธิภาพ หลังจากเรียก build แล้ว State จะกลับสู่สถานะสะอาด
คุณสมบัติ widget ช่วยให้ State อ่านพารามิเตอร์ที่ส่งไปยังคอนสตรัคเตอร์ของ StatefulWidget เนื่องจาก StatefulWidget ไม่สามารถเปลี่ยนแปลงได้ (เช่น StatelessWidget) ฟิลด์ของมันจึงไม่เปลี่ยนแปลง — เมื่อพารามิเตอร์เปลี่ยน พาเรนต์จะสร้างวิดเจ็ตใหม่ และ State รับมันผ่าน didUpdateWidget ซึ่งรับประกันว่า State จะทำงานกับข้อมูลปัจจุบันของพาเรนต์เสมอ
ตัวอย่างพื้นฐานของ State ที่มีฟิลด์ถูกปรับเปลี่ยนโดยตัวจับเวลา แสดง initState setState และ dispose:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
ตัวอย่างการใช้คุณสมบัติ widget เพื่อเข้าถึงพารามิเตอร์พาเรนต์และตอบสนองต่อการเปลี่ยนแปลงผ่าน didUpdateWidget:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
ในตัวอย่างที่สอง State ติดตามการเปลี่ยนแปลงของพารามิเตอร์อินพุต name และจัดรูปแบบการแสดงผลใหม่เมื่อมีการเปลี่ยนแปลงจริงเท่านั้น หากไม่มีการตรวจสอบ widget.name != oldWidget.name เมธอดจะถูกเรียกทุกครั้งที่พาเรนต์สร้างใหม่ แม้ว่าชื่อจะไม่เปลี่ยนแปลง — ซึ่งเป็นงานที่ไม่จำเป็นสำหรับเฟรมเวิร์ก
State และ StatefulWidget เป็นสองคลาสที่แตกต่างกันในสถาปัตยกรรม Flutter ที่มีบทบาทต่างกัน StatefulWidget เป็นตัวหุ้มที่ไม่สามารถเปลี่ยนแปลงได้ซึ่งอธิบายการกำหนดค่าวิดเจ็ตและสร้าง State State เป็นออบเจกต์หนักที่จัดเก็บข้อมูลที่เปลี่ยนแปลงได้ จัดการการสมัครรับข้อมูล และสร้าง UI การแยกนี้ช่วยให้ Flutter สามารถทำลายและสร้างวิดเจ็ตโดยไม่สูญเสียสถานะ
ฟิลด์ทั้งหมดของ StatefulWidget ต้องเป็น final และกำหนดในคอนสตรัคเตอร์ — ฟิลด์เหล่านี้ไม่เปลี่ยนแปลงหลังการสร้าง ในทางกลับกัน State สามารถปรับเปลี่ยนฟิลด์ได้ตลอดเวลา แต่การเปลี่ยนแปลงทั้งหมดต้องนำหน้าด้วยการเรียก setState เพื่อให้ Flutter ทราบถึงความจำเป็นในการสร้างใหม่ นี่คือความแตกต่างหลัก: StatefulWidget คือ “สิ่งที่จะแสดง” State คือ “วิธีการแสดงและข้อมูลใดที่จะใช้”
ตามการวิเคราะห์ซอร์สโค้ด Flutter (Flutter SDK, 2026) StatefulWidget มีฟิลด์บังคับเพียงฟิลด์เดียว — createState ในขณะที่ State สามารถเข้าถึง BuildContext สมัครรับสตรีม จัดการการเคลื่อนไหวและตัวควบคุม แนะนำให้รักษา StatefulWidget ให้เรียบง่ายที่สุด โดยย้ายตรรกะทั้งหมดไปยัง State
การแยก Widget และ State เป็นการตัดสินใจทางสถาปัตยกรรมที่รับประกันความไม่เปลี่ยนแปลงได้ของการกำหนดค่า หาก StatefulWidget เก็บสถานะด้วยตัวเอง สถานะจะสูญหายทุกครั้งที่พาเรนต์สร้างใหม่ โดยการย้ายสถานะไปยังออบเจกต์แยกต่างหาก Flutter รับประกันว่าข้อมูลจะอยู่รอดจากการสร้างใหม่ ในขณะที่วิดเจ็ตยังคงมีน้ำหนักเบาและเปรียบเทียบได้
ออบเจกต์ State ถูกแยก — ไม่มีการเข้าถึงโดยตรงไปยัง State ของวิดเจ็ตอื่น สำหรับการแลกเปลี่ยนข้อมูลระหว่างวิดเจ็ต ใช้ InheritedWidget หรือเครื่องมือจัดการสถานะภายนอก: Provider, Riverpod, Bloc, Redux แต่ละวิธีแก้ปัญหาต่างกัน: InheritedWidget ทำงานผ่านทรีของวิดเจ็ต Provider ผ่านคอนเทนเนอร์ DI Bloc ผ่านสตรีมเหตุการณ์
การเลือกเครื่องมือขึ้นอยู่กับขนาดของโครงการ สำหรับแอปพลิเคชันขนาดเล็ก InheritedWidget และ State ในเครื่องก็เพียงพอ สำหรับโครงการขนาดกลางและใหญ่ แนะนำให้ใช้ Riverpod หรือ Bloc — พวกมันรับประกันความสามารถในการทดสอบ การคาดการณ์ได้ และการแยกตรรกะออกจาก UI State จะถูกใช้สำหรับข้อมูลในเครื่องของวิดเจ็ตเท่านั้น (โฟกัส สกอลล์ การเคลื่อนไหว)
ตามแบบสำรวจชุมชน Flutter 2025 (Flutter Foundation, ธันวาคม 2025) Riverpod เป็นโซลูชันการจัดการสถานะที่ได้รับความนิยมมากที่สุดในโครงการใหม่ (38%) ตามด้วย Bloc (31%) และ Provider (22%) ทั้งสามเครื่องมือเข้ากันได้กับ State และไม่ต้องการละทิ้งวงจรชีวิตมาตรฐาน
ข้อผิดพลาด แรก คือการลืมตรวจสอบ mounted ก่อน setState ในคอลแบ็กแบบอะซิงโครนัส เมื่อวิดเจ็ตถูกลบออกจากทรี (เช่น ผู้ใช้ออกจากหน้าจอ) แต่การดำเนินการแบบอะซิงโครนัส (คำขอ HTTP) ยังคงทำงานอยู่ หลังจากเสร็จสิ้น State ก็ตายแล้ว การเรียก setState ใน State ที่ตายจะโยนข้อยกเว้น การตรวจสอบ if (mounted) setState(...) แก้ปัญหาได้
ข้อผิดพลาดที่สองคือการเริ่มต้นการพึ่งพา InheritedWidget ใน initState แทน didChangeDependencies ใน initState บริบทยังไม่ได้ถูกเชื่อมต่อ ดังนั้น MediaQuery.of(context) จะโยนข้อยกเว้น การพึ่งพา InheritedWidget ทั้งหมดควรตั้งค่าใน didChangeDependencies หรือใน build
ข้อผิดพลาดที่สามคือการเปลี่ยนแปลงฟิลด์โดยไม่เรียก setState หากนักพัฒนาเปลี่ยนฟิลด์ State โดยไม่มี setState Flutter จะไม่ทราบเกี่ยวกับการเปลี่ยนแปลงและ UI จะไม่อัปเดต ตัวอย่างเช่น: _list.add(item) โดยไม่มี setState((){}) หลังจากนั้นจะเปลี่ยนแปลงลิสต์ แต่หน้าจอจะยังคงเหมือนเดิม
รูปแบบความปลอดภัยสำหรับการดำเนินการแบบอะซิงโครนัสใน State:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
การตรวจสอบ mounted รับประกันว่า setState ถูกเรียกบน State ที่มีชีวิตเท่านั้น ป้องกันข้อยกเว้น “setState called after dispose”
คำถามที่พบบ่อย
StatefulWidget คือการกำหนดค่าวิดเจ็ตที่ไม่สามารถเปลี่ยนแปลงได้ ในขณะที่ State คือออบเจกต์ที่เปลี่ยนแปลงได้ซึ่งจัดเก็บข้อมูลและจัดการวงจรชีวิต วิดเจ็ตสามารถสร้างใหม่ได้ แต่ State ไม่สามารถ StatefulWidget สร้าง State ผ่าน createState
อย่างแม่นยำ หนึ่ง อัน เมธอด createState ถูกเรียกหนึ่งครั้งเมื่อ StatefulWidget ถูกแทรกเข้าไปในทรีครั้งแรก แม้ว่าพาเรนต์จะสร้างใหม่หลายครั้ง ออบเจกต์ State จะยังคงเหมือนเดิมจนกว่าประเภทหรือ Key ของวิดเจ็ตจะเปลี่ยน
mounted คือแฟล็กบูลีนที่แสดงว่า State อยู่ในทรีของวิดเจ็ตหรือไม่ หลังจากเรียก dispose แล้ว mounted จะกลายเป็น false ใช้สำหรับตรวจสอบก่อน setState ในคอลแบ็กแบบอะซิงโครนัสเพื่อหลีกเลี่ยงข้อยกเว้น
ไม่ State ถูกผูกกับ StatefulWidget ที่เฉพาะเจาะจงผ่านเจเนอริกเสมอ: State<T extends StatefulWidget> การสร้าง State โดยตรง โดยไม่มีการเชื่อมโยงกับวิดเจ็ต เป็นไปไม่ได้ในเชิงสถาปัตยกรรม
ข้อยกเว้น จะถูกโยน: “setState called after dispose” หลังจาก dispose State ถือว่าตาย และความพยายามใดๆ ในการสร้าง UI ใหม่ผ่าน setState เป็นสิ่งต้องห้าม วิธีแก้คือตรวจสอบ mounted ก่อนแต่ละ setState
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม