Element Tree คือเลเยอร์กลางใน Flutter ที่เชื่อมต่อ Widget Tree แบบประกาศกับ RenderObject Tree แบบคำสั่ง แตกต่างจาก widget ที่ถูกสร้างใหม่ทุกครั้งที่มีการ rebuild องค์ประกอบจะคงอยู่ระหว่างการอัปเดตและจัดการสถานะ คีย์ และวงจรชีวิต ตาม Flutter API Reference, 2025 การทำความเข้าใจ Element Tree เป็นสิ่งจำเป็นสำหรับการทำงานกับคีย์อย่างมีประสิทธิภาพ การเพิ่มประสิทธิภาพ และการดีบักพฤติกรรมที่ไม่คาดคิดของ widget
ประเด็นสำคัญ
Element Tree คือลำดับชั้นกลางใน Flutter ที่สร้างขึ้นบนพื้นฐานของ Widget Tree และจัดการการแทรก widget ในแอปพลิเคชัน อินสแตนซ์องค์ประกอบแต่ละอันสอดคล้องกับหนึ่ง widget ในทรีและเก็บข้อมูลอ้างอิงไปยังมัน ความแตกต่างหลักระหว่างองค์ประกอบและ widget คือ องค์ประกอบรักษาตำแหน่งในทรีระหว่างการสร้างใหม่ ในขณะที่ widget อาจถูกสร้างใหม่ทุกครั้งที่มีการเรียก build
หากไม่มี Element Tree Flutter จะไม่สามารถอัปเดตอินเทอร์เฟซได้อย่างมีประสิทธิภาพ หากทุกการ rebuild สร้าง RenderObject Tree ใหม่ ประสิทธิภาพจะต่ำอย่างไม่อาจยอมรับได้ Element Tree ทำหน้าที่เป็นตัวทำให้เสถียร: มันเก็บข้อมูลอ้างอิงไปยัง RenderObject และ State ระหว่างการอัปเดต ทำให้ Flutter สามารถใช้การเปลี่ยนแปลงเพียงเล็กน้อยกับทรีการเรนเดอร์
Flutter ใช้องค์ประกอบสามประเภทหลัก: StatelessElement สำหรับ StatelessWidget, StatefulElement สำหรับ StatefulWidget และ LeafRenderObjectElement, SingleChildRenderObjectElement, MultiChildRenderObjectElement สำหรับ RenderObjectWidget แต่ละประเภทมีความเชี่ยวชาญสำหรับคลาส widget ของตนและกำหนดว่าองค์ประกอบโต้ตอบกับ RenderObject อย่างไร
สถาปัตยกรรมสามชั้น ของ Flutter ประกอบด้วย Widget Tree (การกำหนดค่า), Element Tree (การจัดการ) และ RenderObject Tree (การเรนเดอร์) Element Tree คือจุดเชื่อมต่อ: มันอ่านการกำหนดค่าจาก Widget และส่งคำสั่งไปยัง RenderObject หากไม่มี Element Tree เฟรมเวิร์กจะไม่สามารถซิงโครไนซ์คำอธิบายแบบประกาศกับการเรนเดอร์จริงได้อย่างมีประสิทธิภาพ
เมื่อองค์ประกอบถูกเมาท์ในทรี มันจะตรวจสอบประเภทของ widget ถ้า widget เป็น RenderObjectWidget องค์ประกอบจะสร้าง RenderObject ที่สอดคล้องและเพิ่มลงใน RenderObject Tree ถ้า widget เป็น LeafRenderObjectWidget องค์ประกอบจะสร้าง RenderObject ใบ สำหรับ StatelessWidget และ StatefulWidget องค์ประกอบเพียงแค่จัดการการเมาท์ขององค์ประกอบลูก
abstract class Element {
Widget widget;
Element? parent;
List<Element>? children;
void mount(Element? parent, dynamic newSlot);
void update(Widget newWidget);
void unmount();
}
โค้ดที่เรียบง่ายนี้แสดงโครงสร้างพื้นฐานของ Element แต่ละองค์ประกอบเก็บข้อมูลอ้างอิงไปยัง widget ปัจจุบัน องค์ประกอบหลัก และองค์ประกอบลูก เมธอด mount, update และ unmount จัดการวงจรชีวิตขององค์ประกอบและ RenderObject ที่เกี่ยวข้อง
แต่ละองค์ประกอบ ใน Flutter ผ่านลำดับของขั้นตอนวงจรชีวิต: การสร้าง การเมาท์ การอัปเดต และการ unmount การทำความเข้าใจขั้นตอนเหล่านี้จำเป็นสำหรับการดีบักพฤติกรรมที่ไม่คาดคิด โดยเฉพาะเมื่อทำงานกับแอนิเมชัน การดำเนินการแบบอะซิงโครนัส และการจัดการสถานะ
องค์ประกอบถูกสร้างขึ้นโดยเรียกเมธอด createElement ของ widget สำหรับ StatelessWidget จะสร้าง StatelessElement; สำหรับ StatefulWidget จะสร้าง StatefulElement (ซึ่งสร้างออบเจ็กต์ State ด้วย); สำหรับ RenderObjectWidget จะสร้าง RenderObjectElement ที่สอดคล้อง การสร้างองค์ประกอบเกิดขึ้นเมื่อ widget ปรากฏครั้งแรกใน Widget Tree
ระหว่างการเมาท์ องค์ประกอบจะถูกเพิ่มลงใน Element Tree และรับองค์ประกอบหลัก สำหรับ RenderObjectElement การเมาท์ยังสร้าง RenderObject และแทรกลงใน RenderObject Tree ด้วย ถ้า widget เป็น StatefulElement ในขั้นตอนนี้เมธอด initState ของออบเจ็กต์ State จะถูกเรียก
เมื่อ widget ถูกสร้างใหม่ด้วยการกำหนดค่าใหม่ องค์ประกอบจะได้รับ widget ใหม่ผ่านเมธอด update องค์ประกอบเปรียบเทียบประเภทของ widget เก่าและใหม่: ถ้าประเภทตรงกัน องค์ประกอบจะอัปเดตการกำหนดค่า; ถ้าไม่ตรงกัน องค์ประกอบจะถูก unmount และสร้างใหม่ สิ่งนี้เรียกว่า "การเปลี่ยน widget" และเป็นสาเหตุของการสูญเสียสถานะเมื่อเปลี่ยนประเภท
เมื่อ widget ถูกลบออกจาก Widget Tree เมธอด unmount ขององค์ประกอบจะถูกเรียก องค์ประกอบถูกลบออกจาก Element Tree RenderObject ถูกลบออกจาก RenderObject Tree และสำหรับ StatefulWidget เมธอด dispose ของออบเจ็กต์ State จะถูกเรียก หลังจาก unmount องค์ประกอบสามารถนำกลับมาใช้ใหม่ได้ถ้า widget ปรากฏอีกครั้งในตำแหน่งเดียวกัน
คีย์ (Key) เป็นกลไกการระบุองค์ประกอบที่ช่วยให้ Flutter จับคู่ widget จาก Widget Tree เก่าและใหม่ไม่ใช่ตามตำแหน่ง แต่ตามตัวระบุที่ไม่ซ้ำกัน คีย์มีความสำคัญอย่างยิ่งเมื่อทำงานกับรายการแบบไดนามิกที่ลำดับขององค์ประกอบสามารถเปลี่ยนแปลงได้: การเพิ่ม การลบ หรือการจัดเรียงองค์ประกอบใหม่
หากไม่มีคีย์ Flutter จะจับคู่องค์ประกอบตาม ตำแหน่ง ในทรี: องค์ประกอบที่ตำแหน่ง 0 จากทรีเก่าจะถูกแทนที่ด้วย widget ที่ตำแหน่ง 0 จากทรีใหม่ ถ้าลำดับเปลี่ยนแปลงไป องค์ประกอบจะปะปนกันและสถานะอาจสูญหายหรือถูกผูกกับข้อมูลที่ไม่ถูกต้อง คีย์บังคับให้ Flutter ค้นหาองค์ประกอบตามตัวระบุแทนที่จะเป็นตามตำแหน่ง
ValueKey ใช้ค่าอย่างง่าย (สตริง ตัวเลข) เพื่อระบุองค์ประกอบ ObjectKey ใช้การอ้างอิงออบเจ็กต์ — เหมาะสมเมื่อองค์ประกอบไม่มีตัวระบุสตริงที่เสถียร UniqueKey สร้างตัวระบุที่ไม่ซ้ำกันทุกครั้งที่สร้าง — ใช้เมื่อแต่ละอินสแตนซ์ของ widget ต้องไม่ซ้ำกัน
Column(
children: items.map((item) => TodoItem(
key: ValueKey(item.id),
title: item.title,
isDone: item.isDone,
)).toList(),
)
ในตัวอย่างนี้ ValueKey กับ item.id รับประกันว่าแต่ละ TodoItem จะคงสถานะของมันไว้ (เช่น การโฟกัสของช่องป้อนข้อมูล) เมื่อลำดับขององค์ประกอบในรายการเปลี่ยนไป หากไม่มีคีย์ องค์ประกอบที่ตำแหน่งแรกจะได้รับสถานะขององค์ประกอบก่อนหน้าที่ตำแหน่งเดียวกัน
สถานะ (State) ใน Flutter ไม่ได้เก็บไว้ใน widget แต่อยู่ในองค์ประกอบ เมื่อ StatefulWidget ถูกสร้างใหม่และสร้างอินสแตนซ์ widget ใหม่ StatefulElement ที่สอดคล้องจะเก็บข้อมูลอ้างอิงไปยังออบเจ็กต์ State เก่า widget ใหม่เชื่อมต่อกับ State ที่มีอยู่ ทำให้ข้อมูลถูกเก็บรักษาไว้ระหว่างการสร้างใหม่
ระหว่างการสร้าง widget ใหม่ Flutter จะสร้างอินสแตนซ์ใหม่ของ StatefulWidget แต่ StatefulElement ที่สอดคล้องยังคงอยู่ใน Element Tree องค์ประกอบเรียกเมธอด update บน State โดยส่ง widget ใหม่ ดังนั้น ออบเจ็กต์ State และข้อมูลของมันจึงถูกเก็บรักษาไว้ การสูญเสียสถานะเกิดขึ้นเมื่อประเภทของ widget เปลี่ยนไป คีย์เปลี่ยนไป หรือองค์ประกอบถูกลบออกจากทรี
InheritedElement เป็นองค์ประกอบพิเศษที่ช่วยให้องค์ประกอบลูกได้รับข้อมูลจาก InheritedWidget หลักโดยไม่ต้องส่งผ่านคอนสตรัคเตอร์อย่างชัดเจน เมื่อ InheritedWidget เปลี่ยนแปลง InheritedElement จะแจ้งให้องค์ประกอบที่ขึ้นต่อกันทั้งหมดทราบ ซึ่งจะถูกสร้างใหม่ กลไกนี้เป็นพื้นฐานของ Theme, MediaQuery และ Provider
Element Tree ใช้หน่วยความจำและส่งผลต่อความเร็วในการเรนเดอร์ครั้งแรก แต่ละองค์ประกอบใช้หน่วยความจำจำนวนหนึ่ง: การอ้างอิงไปยัง widget การอ้างอิงไปยังหลัก รายการขององค์ประกอบลูก ช่อง และฟิลด์เพิ่มเติมสำหรับ RenderObjectElement การปรับ Element Tree ให้เหมาะสมจะลดเวลาเริ่มต้นและลดการใช้หน่วยความจำ
Flutter พยายาม นำองค์ประกอบกลับมาใช้ใหม่ ระหว่างการสร้างใหม่ ถ้า widget ในการกำหนดค่าใหม่มีประเภทและคีย์เดียวกัน องค์ประกอบจะไม่ถูกสร้างใหม่ — มันจะถูกอัปเดต สิ่งนี้เร็วกว่าการสร้างองค์ประกอบใหม่ด้วยการเมาท์ตามมาอย่างมาก อย่างไรก็ตาม เมื่อประเภทหรือคีย์เปลี่ยนแปลง องค์ประกอบเก่าจะถูก unmount และสร้างองค์ประกอบใหม่ตั้งแต่ต้น
RepaintBoundary สร้าง RenderRepaintBoundary แยกต่างหากใน RenderObject Tree ที่แยกส่วนหนึ่งของทรีออกจากกัน ที่ระดับ Element Tree RepaintBoundary ไม่ได้สร้างองค์ประกอบพิเศษ — มันใช้ SingleChildRenderObjectElement ความแตกต่างปรากฏที่ระดับ RenderObject: เมื่อเนื้อหาของ RepaintBoundary เปลี่ยนแปลง เฉพาะพื้นที่ที่ถูกแยกเท่านั้นที่ถูกวาดใหม่
| การดำเนินการ | ไม่มี RepaintBoundary | มี RepaintBoundary |
|---|---|---|
| การวาดใหม่ | ทั้งหน้าจอ | เฉพาะพื้นที่ที่ถูกแยก |
| เวลา | ~16 ms ที่ 60 FPS | ~2-5 ms |
| หน่วยความจำ | น้อยที่สุด | + ไม่กี่กิโลไบต์ต่อชั้น |
ดังที่ตารางแสดง RepaintBoundary ลดเวลาในการวาดใหม่ลงอย่างมากโดยการแยกพื้นที่ที่เปลี่ยนแปลง ที่ระดับ Element Tree ไม่จำเป็นต้องกำหนดค่าเพิ่มเติม — เพียงห่อ widget ที่เปลี่ยนแปลงด้วย RepaintBoundary
คำถามที่พบบ่อย
Widget Tree คือการกำหนดค่าที่ถูกสร้างใหม่ทุกครั้งที่มีการสร้างใหม่ Element Tree คือโครงสร้างถาวรที่คงอยู่ระหว่างการอัปเดตและจัดการสถานะ RenderObject และวงจรชีวิตของ widget
หากไม่มี Element Tree Flutter จะต้องสร้าง RenderObject Tree ใหม่ทุกครั้งที่สถานะเปลี่ยนแปลง ซึ่งจะทำให้เกิด ความล่าช้าอย่างมาก Element Tree จะเก็บรักษา RenderObject และ State ไว้ ทำให้สามารถใช้การเปลี่ยนแปลงเพียงเล็กน้อยเท่านั้น
องค์ประกอบถูกลบเมื่อ widget ที่สอดคล้องหายไปจาก Widget Tree หรือเมื่อ ประเภทของ widget เปลี่ยนไป (เช่น Column ถูกแทนที่ด้วย Row) หรือคีย์เปลี่ยนไป เมื่อ unmount จะเรียก dispose บน State
คีย์ เปลี่ยนอัลกอริทึมการจับคู่: แทนที่จะค้นหาองค์ประกอบตามตำแหน่ง Flutter จะค้นหาองค์ประกอบตามค่าคีย์ ซึ่งช่วยให้รักษาสถานะไว้ได้เมื่อลำดับหรือจำนวนของ widget เปลี่ยนแปลง
ได้ ผ่าน BuildContext ซึ่งเป็นนามธรรมขององค์ประกอบ เมธอด findAncestorWidgetOfExactType และ dependOnInheritedWidgetOfExactType ทำงานกับ Element Tree โดยเลื่อนขึ้นไปตามทรีขององค์ประกอบ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม