Widget Tree คือโครงสร้างลำดับชั้นของวิดเจ็ตใน Flutter ที่กำหนดเค้าโครงของส่วนติดต่อผู้ใช้ แต่ละองค์ประกอบของส่วนติดต่อ ตั้งแต่ปุ่มไปจนถึงทั้งหน้าจอ แสดงโดยวิดเจ็ตแยกต่างหากที่ซ้อนอยู่ภายในคอนเทนเนอร์หลัก Flutter อัปเดต Widget Tree ทุกครั้งที่มีการเปลี่ยนแปลงสถานะ — เฟรมเวิร์กเปรียบเทียบต้นไม้ใหม่และเก่าและใช้การเปลี่ยนแปลงน้อยที่สุด ตามFlutter Team, 2025 โครงสร้างต้นไม้ที่มีประสิทธิภาพส่งผลโดยตรงต่อความลื่นไหลของแอนิเมชันและความตอบสนองของส่วนติดต่อ
ประเด็นสำคัญ
Widget Tree คือคำอธิบายเชิงประกาศของส่วนติดต่อผู้ใช้ใน Flutter ที่สร้างขึ้นเป็นต้นไม้ของวิดเจ็ตที่ซ้อนกัน แต่ละวิดเจ็ตกำหนดส่วนหนึ่งของ UI: การกำหนดค่า พารามิเตอร์การแสดงผล และพฤติกรรมเมื่อมีการโต้ตอบ นักพัฒนาอธิบายว่าส่วนติดต่อควรมีลักษณะอย่างไรในสถานะปัจจุบันของแอปพลิเคชัน และ Flutter ทำหน้าที่แปลงคำอธิบายนั้นเป็นพิกเซลบนหน้าจอ
แตกต่างจากเฟรมเวิร์กเชิงคำสั่งที่นักพัฒนาจัดการองค์ประกอบส่วนติดต่อโดยตรง Flutter ใช้แนวทางเชิงประกาศ เมื่อสถานะแอปพลิเคชันเปลี่ยนแปลง ต้นไม้ Widget Tree ใหม่จะถูกสร้างขึ้น และเฟรมเวิร์กคำนวณความแตกต่างระหว่างต้นไม้เก่าและใหม่ ซึ่งช่วยลดจำนวนการดำเนินการเรนเดอร์และทำให้โค้ดคาดเดาได้มากขึ้น
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Widget Tree")),
body: Center(
child: Column(
children: [
Text("สวัสดี, Flutter"),
ElevatedButton(
onPressed: () {},
child: Text("กดฉัน"),
),
],
),
),
),
);
}
}
ในตัวอย่างนี้ Widget Tree ประกอบด้วย MaterialApp, Scaffold, AppBar, Center, Column, Text และ ElevatedButton แต่ละวิดเจ็ตเหล่านี้เป็นโหนดในต้นไม้ เมื่อสถานะแอปพลิเคชันเปลี่ยนแปลง Flutter จะเรียกเมธอด build อีกครั้งและเปรียบเทียบผลลัพธ์กับต้นไม้ก่อนหน้า
Widget Tree เริ่มต้นด้วยวิดเจ็ตรากที่ส่งไปยังเมธอด runApp วิดเจ็ตรากมักจะเป็น MaterialApp, CupertinoApp หรือ WidgetsApp — มันกำหนดการตั้งค่าระดับโลกของแอปพลิเคชัน จากราก ต้นไม้แตกกิ่งเป็นวิดเจ็ตลูก ซึ่งแต่ละตัวสามารถมีลูกหลานของตัวเองได้
วิดเจ็ตใน Flutter แบ่งออกเป็น single-child (รับลูกหนึ่งตัวผ่านพารามิเตอร์ child) และ multi-child (รับรายการลูกผ่าน children) ตัวอย่าง single-child: Center, Padding, SizedBox, Container Multi-child: Column, Row, Stack, ListView, GridView ความแตกต่างนี้ส่งผลต่อโครงสร้างของ Widget Tree: วิดเจ็ต multi-child สร้างต้นไม้ที่กว้างขึ้น ในขณะที่ single-child สร้างต้นไม้ที่ลึกขึ้น
BuildContext คือตำแหน่งของวิดเจ็ตใน Widget Tree แต่ละวิดเจ็ตมี BuildContext ของตัวเอง ซึ่งส่งไปยังเมธอด build และใช้เพื่อเข้าถึงวิดเจ็ตแม่ ธีม MediaQuery และ InheritedWidget อื่นๆ BuildContext ทำหน้าที่เป็นสะพานเชื่อมระหว่างวิดเจ็ตและองค์ประกอบใน Element Tree
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
return Container(
color: theme.colorScheme.primary,
child: Text(
"ความกว้างหน้าจอ: ${mediaQuery.size.width}"
),
);
}
}
ในตัวอย่างนี้ BuildContext ใช้เพื่อรับธีมและขนาดหน้าจอ Flutter จะขึ้นไปตาม Widget Tree ไปยัง Theme และ MediaQuery ที่ใกล้ที่สุด ซึ่งเป็น InheritedWidget ซึ่งแสดงให้เห็นว่าบริบทเชื่อมต่อวิดเจ็ตกับตำแหน่งในลำดับชั้นอย่างไร
เมื่อแอปพลิเคชัน Flutter เริ่มต้น ฟังก์ชัน runApp จะถูกเรียก ซึ่งรับวิดเจ็ตรากและเริ่มสร้าง Widget Tree กระบวนการประกอบด้วยสามขั้นตอน: การสร้างการกำหนดค่าวิดเจ็ต การสร้าง Element Tree และการสร้าง RenderObject Tree สำหรับการเรนเดอร์จริง
ฟังก์ชัน runApp สร้างองค์ประกอบรากผ่าน WidgetsFlutterBinding ซึ่งเชื่อมต่อเฟรมเวิร์กกับเอนจินกราฟิก วิดเจ็ตรากถูกวางในต้นไม้ และ Flutter เรียกเมธอด build เพื่อเติมวิดเจ็ตลูก แต่ละการเรียก build จะสร้างกราฟย่อยใหม่ของ Widget Tree
หลังจากสร้าง Widget Tree แล้ว Flutter จะดำเนินการเค้าโครงเริ่มต้น — คำนวณขนาดและตำแหน่งของวิดเจ็ตทั้งหมด กระบวนการนี้เริ่มต้นจากรากและแพร่กระจายลงไปตามต้นไม้ วิดเจ็ตแต่ละตัวได้รับข้อจำกัดจากแม่และส่งคืนขนาดที่คำนวณได้ หากขนาดไม่ตรงกัน Flutter จะสร้างข้อผิดพลาดของเค้าโครง
หลังจากเค้าโครงเสร็จสมบูรณ์ Flutter จะดำเนินการเรนเดอร์วิดเจ็ตแต่ละตัว RenderObject แปลงคำอธิบายส่วนติดต่อเป็นคำสั่งกราฟิกที่ดำเนินการโดย GPU ผ่าน Skia หรือ Impeller กระบวนการทั้งหมด — จาก Widget Tree ไปจนถึงพิกเซล — ทำซ้ำทุกครั้งที่มีการเปลี่ยนแปลงสถานะด้วยความเร็วสูงสุด 120 เฟรมต่อวินาที
StatelessWidget คือวิดเจ็ตที่ไม่มีสถานะภายในที่เปลี่ยนแปลงได้ ลักษณะของมันถูกกำหนดโดยพารามิเตอร์อินพุตที่ส่งผ่านคอนสตรัคเตอร์เท่านั้น หากพารามิเตอร์ไม่เปลี่ยนแปลง StatelessWidget จะไม่ถูกสร้างใหม่ ซึ่งทำให้มีน้ำหนักเบาในแง่ของประสิทธิภาพ
ใช้ StatelessWidget สำหรับองค์ประกอบส่วนติดต่อคงที่: ไอคอน ป้ายข้อความ ตัวแบ่งตกแต่ง และปุ่มธรรมดาที่ไม่มีตรรกะภายใน ตามเอกสารของ Flutter ประมาณ 70% ของวิดเจ็ตในแอปพลิเคชันทั่วไปสามารถเป็น StatelessWidget ซึ่งช่วยลดภาระของตัวเก็บขยะและเร่งการ rebuild
StatefulWidget สร้างออบเจ็กต์ State ที่คงอยู่ระหว่างการสร้างวิดเจ็ตใหม่ เมื่อสถานะเปลี่ยนแปลง (ผ่าน setState) Flutter จะทำเครื่องหมายวิดเจ็ตเป็น “สกปรก” และสร้างใหม่ในเฟรมถัดไป StatefulWidget ช่วยให้มีองค์ประกอบที่โต้ตอบได้: ฟิลด์อินพุต แอนิเมชัน ตัวจับเวลา และรายการแบบไดนามิก
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("นับ: $_count"),
ElevatedButton(
onPressed: () {
setState(() => _count++);
},
child: Text("เพิ่ม"),
),
],
);
}
}
ในตัวอย่างนี้ StatefulWidget ใช้ setState เพื่ออัปเดตตัวนับ เมื่อสถานะถูกอัปเดต Flutter จะสร้างเฉพาะส่วนที่เปลี่ยนแปลงของ Widget Tree ใหม่ — CounterWidget และลูกหลานของมัน วิดเจ็ตแม่จะไม่ถูกสร้างใหม่ ซึ่งเป็นข้อได้เปรียบหลักของโมเดลเชิงประกาศของ Flutter
Widget Tree คือชั้นการกำหนดค่า ในขณะที่ Element Tree คือตัวเชื่อมระหว่างวิดเจ็ตและการเรนเดอร์จริง แต่ละวิดเจ็ตใน Widget Tree สร้างองค์ประกอบใน Element Tree ซึ่งเก็บการอ้างอิงถึงวิดเจ็ตและจัดการวงจรชีวิตของมัน สถาปัตยกรรมนี้ช่วยให้ Flutter จัดการการเปลี่ยนแปลงได้อย่างมีประสิทธิภาพ
เมื่อ Flutter พบวิดเจ็ตครั้งแรก มันจะเรียกเมธอด createElement ซึ่งสร้างองค์ประกอบที่เกี่ยวข้อง สำหรับ StatelessWidget จะสร้าง StatelessElement; สำหรับ StatefulWidget จะสร้าง StatefulElement ซึ่งสร้างออบเจ็กต์ State ด้วย องค์ประกอบจะคงอยู่ระหว่างรอบการสร้างใหม่ แม้ว่าวิดเจ็ตจะถูกสร้างขึ้นใหม่
Key คือตัวระบุที่ช่วยให้ Flutter จับคู่วิดเจ็ตจาก Widget Tree เก่าและใหม่ หากวิดเจ็ตมี Key Flutter จะใช้มันเพื่อค้นหาองค์ประกอบที่เกี่ยวข้องแทนตำแหน่งในต้นไม้ คีย์จำเป็นเมื่อทำงานกับรายการแบบไดนามิกที่ลำดับขององค์ประกอบอาจเปลี่ยนแปลง
ListView(
children: items.map((item) => ListItem(
key: ValueKey(item.id),
data: item,
)).toList(),
)
หากไม่มี Key Flutter จะจับคู่องค์ประกอบตามตำแหน่ง ซึ่งอาจนำไปสู่การรักษาสถานะที่ไม่ถูกต้องเมื่อลำดับเปลี่ยนไป ValueKey ที่มีตัวระบุเฉพาะช่วยให้แน่ใจว่าองค์ประกอบแต่ละรายการคงสถานะไว้โดยไม่คำนึงถึงตำแหน่งในรายการ
โครงสร้างของ Widget Tree ส่งผลโดยตรงต่อประสิทธิภาพของแอปพลิเคชัน Flutter ต้นไม้ที่ลึกที่มีวิดเจ็ตซ้อนกันจำนวนมากต้องใช้เวลามากขึ้นสำหรับขั้นตอนเค้าโครงและเพิ่มการใช้หน่วยความจำ Flutter DevTools มีเครื่องมือสำหรับวิเคราะห์ Widget Tree และระบุคอขวด
แต่ละระดับการซ้อนกันเพิ่มการคำนวณเพิ่มเติมระหว่างเค้าโครงและการวาด แทนที่จะซ้อนกันเป็นลูกโซ่ลึก ให้ใช้โครงสร้างที่เรียบขึ้น ตัวอย่างเช่น Row กับ Expanded สามารถแทนที่ Containers ที่ซ้อนกันหลายตัวกับ Align ตาม Flutter Team การเพิ่มประสิทธิภาพต้นไม้สามารถลดเวลาเค้าโครงได้ถึง 40%
Flutter DevTools มีเครื่องมือ “Widget Inspector” ซึ่งแสดง Widget Tree ปัจจุบันแบบเรียลไทม์ นักพัฒนาสามารถเลือกวิดเจ็ตใดก็ได้บนหน้าจอและดูตำแหน่งในต้นไม้ พารามิเตอร์ และข้อจำกัดของเค้าโครง ซึ่งช่วยระบุการซ้อนกันที่ไม่คาดคิด การ rebuild มากเกินไป และปัญหาขนาด
RepaintBoundary คือวิดเจ็ตที่แยกส่วนหนึ่งของ Widget Tree สำหรับการเรนเดอร์อิสระ หากเนื้อหาภายใน RepaintBoundary เปลี่ยนแปลง เฉพาะพื้นที่ของมันเท่านั้นที่ถูกวาดใหม่ ไม่ใช่ทั้งหน้าจอ ใช้ RepaintBoundary สำหรับแอนิเมชัน รายการ และองค์ประกอบอื่นๆ ที่อัปเดตบ่อยครั้ง
RepaintBoundary(
child: CustomPaint(
painter: MyPainter(),
child: SizedBox(
width: 200,
height: 200,
),
),
)
ในตัวอย่างนี้ RepaintBoundary แยก CustomPaint ออกเป็นพื้นที่เรนเดอร์แยกต่างหาก เมื่อแอนิเมชันภายในพื้นที่นี้ถูกอัปเดต เฉพาะวิดเจ็ต CustomPaint เท่านั้นที่ถูกวาดใหม่ ในขณะที่ส่วนที่เหลือของหน้าจอยังคงไม่เปลี่ยนแปลง ซึ่งมีประโยชน์โดยเฉพาะอย่างยิ่งในส่วนติดต่อที่ซับซ้อนที่มีองค์ประกอบเคลื่อนไหวหลายรายการ
คำถามที่พบบ่อย
Widget Tree คือคำอธิบายส่วนติดต่อเชิงประกาศที่ถูกสร้างขึ้นใหม่ทุกครั้งที่ rebuild Element Tree คงอยู่ระหว่างการอัปเดตและจัดการวงจรชีวิต สถานะ และการจับคู่วิดเจ็ตกับ RenderObject จริง
ไม่มีข้อจำกัดเกี่ยวกับจำนวนวิดเจ็ต แต่ในทางปฏิบัติ ต้นไม้ที่มีวิดเจ็ตนับพันอาจทำให้ขั้นตอนเค้าโครงช้าลง Flutter ได้รับการปรับให้เหมาะสมสำหรับต้นไม้ที่มีหลายพันโหนด; สำหรับจำนวนที่มากขึ้น แนะนำให้ใช้การเสมือนจริงผ่าน ListView.builder
ใช้ Flutter DevTools — แท็บ “Widget Inspector” เรียกใช้แอปพลิเคชันในโหมดดีบัก เปิด DevTools ในเบราว์เซอร์ และเลือกวิดเจ็ตใดก็ได้บนหน้าจอเพื่อดูตำแหน่งใน Widget Tree
Rebuild คือกระบวนการสร้างการกำหนดค่าวิดเจ็ตใหม่เมื่อสถานะเปลี่ยนแปลง Flutter เรียกเมธอด build อีกครั้งสำหรับวิดเจ็ตที่เปลี่ยนแปลง เปรียบเทียบ Widget Tree ใหม่กับต้นไม้ก่อนหน้า และใช้การเปลี่ยนแปลงน้อยที่สุดกับ Element Tree
ลดความลึกของการซ้อนกัน ใช้ const วิดเจ็ต สำหรับองค์ประกอบคงที่ ใช้ RepaintBoundary เพื่อแยกแอนิเมชัน และหลีกเลี่ยง StatefulWidget ที่มากเกินไปในที่ที่ StatelessWidget เพียงพอ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม