Three Trees

Flutter ใช้ 3 trees ทำงานภายใน — เข้าใจสิ่งนี้ทำให้เข้าใจว่าทำไม setState() ถึงเร็ว และทำไม Keys ถึงสำคัญ

3 Trees คืออะไร

Widget Tree          Element Tree         Render Tree
(โค้ดที่เราเขียน)    (Flutter จัดการ)      (วาดจริงบนจอ)

1. Widget Tree — สิ่งที่เราเขียน

  • Immutable — ถูกสร้างใหม่ทุกครั้งที่ setState()
  • เป็นแค่ configuration/blueprint บอกว่า UI ควรหน้าตายังไง
  • ถูก (cheap) ในการสร้าง — Flutter ออกแบบให้สร้างใหม่ได้บ่อยๆ

2. Element Tree — ตัวกลาง

  • Flutter จัดการเอง — ไม่ถูกสร้างใหม่ ทุกครั้ง
  • จับคู่ (match) Widget กับ Render Object
  • เมื่อ Widget Tree สร้างใหม่ → Element Tree เปรียบเทียบ กับ Widget ใหม่ แล้วอัปเดตเฉพาะส่วนที่เปลี่ยน

3. Render Tree — สิ่งที่วาดจริง

  • วาด pixel จริงบนหน้าจอ
  • แก้เฉพาะส่วนที่เปลี่ยน — ไม่ redraw ทั้งจอ
  • แพง (expensive) ในการสร้าง — Flutter จึง reuse ให้มากที่สุด

Flow เมื่อ setState()

setState() ถูกเรียก
    ↓
Widget Tree สร้างใหม่ทั้งต้น (ราคาถูก)
    ↓
Element Tree เปรียบเทียบ Widget ใหม่ กับ Widget เก่า
    ↓
ส่วนที่เหมือนกัน → reuse Element + Render Object เดิม
ส่วนที่ต่าง → อัปเดต Render Object
    ↓
จอแสดงผลเฉพาะส่วนที่เปลี่ยน

ผลลัพธ์: แม้ build() จะทำงานบ่อย แต่จอจริงอัปเดตแค่ส่วนน้อย → เร็ว

Widget Optimization — StatefulWidget ให้เล็กที่สุด

setState() เรียก build() ของ ทั้ง Widget → ถ้า StatefulWidget ใหญ่ จะ rebuild ลูกทุกตัวโดยไม่จำเป็น

ก่อน (rebuild ทั้งหมด):              หลัง (rebuild แค่ส่วนที่มี state):
BigStatefulWidget                    BigStatelessWidget
├── Title                            ├── Title
├── Description                      ├── Description
├── Button ← state อยู่ตรงนี้        └── DemoButtons (StatefulWidget เล็กๆ)
└── ConditionalText                      ├── Button
                                         └── ConditionalText

วิธีทำ: ย้าย state ไป Widget ลูกแยก → เปลี่ยน parent เป็น StatelessWidget → rebuild แค่ลูกตัวเล็ก

Keys — ช่วย Element Tree จับคู่ถูกต้อง

State ผูกกับ Element ไม่ใช่ Widget

State object ไม่ได้ผูกกับ Widget โดยตรง — แต่ ผูกกับ Element ที่เชื่อมไปยัง Widget อีกที

Flutter จับคู่ Widget กับ Element ด้วย type + ตำแหน่ง → ถ้า list reorder แล้ว type เดิม ตำแหน่งเดิม → Element (และ State) อยู่ที่เดิม แต่ Widget สลับไปแล้ว:

ก่อน reorder:
  Widget A  ←→  Element 0  ←→  State A (checked ✅)
  Widget B  ←→  Element 1  ←→  State B
  Widget C  ←→  Element 2  ←→  State C

หลัง reorder (ไม่มี Key):
  Widget C  ←→  Element 0  ←→  State A (checked ✅) ❌ C ได้ state ของ A!
  Widget B  ←→  Element 1  ←→  State B
  Widget A  ←→  Element 2  ←→  State C

แก้ด้วย Key

Key = ป้ายชื่อ ให้ Flutter จับคู่ด้วย type + key แทน type + ตำแหน่ง:

CheckableTodoItem(
  key: ValueKey(todo.text),  // ← Key ช่วยจับคู่ถูกต้อง
  todo: todo,
)
หลัง reorder (มี Key):
  Widget C (key:C) ←→ Element C ←→ State C  ✅
  Widget B (key:B) ←→ Element B ←→ State B  ✅
  Widget A (key:A) ←→ Element A ←→ State A  ✅ (checked ย้ายมาด้วย!)

ValueKey vs ObjectKey

ValueKey(todo.id)    // ใช้ค่าเดียว — เบากว่า (แนะนำ)
ObjectKey(todo)      // ใช้ทั้ง object — หนักกว่า

ค่าที่ใส่ต้อง unique และ ผูกกับ data โดยตรง (ห้ามใช้ random ที่เปลี่ยนทุกครั้ง)

เมื่อไหร่ต้องใส่ Key

  • StatefulWidget ใน list ที่มีการเพิ่ม/ลบ/เรียงใหม่ → ต้องใส่
  • StatelessWidget ใน list → ไม่จำเป็น (ไม่มี state ให้สับสน)
  • Widget ที่ไม่อยู่ใน list → ไม่จำเป็น
  • super.key ควรรับใน constructor ทุก Widget → พร้อมใส่ Key ได้ทุกเมื่อ

var vs final vs const (มุมมอง Performance)

varfinalconst
Widget Treeสร้างใหม่ทุกครั้งสร้างใหม่ทุกครั้งสร้างครั้งเดียว reuse
Performanceปกติปกติดีที่สุด

const widget ไม่ถูกสร้างใหม่เมื่อ setState() → ประหยัดทั้ง memory และ CPU

Key Points

  • Flutter ใช้ 3 trees: Widget (blueprint), Element (ตัวกลาง), Render (วาดจริง)
  • setState() สร้าง Widget Tree ใหม่ แต่ reuse Element + Render Tree → เร็ว
  • State ผูกกับ Element ไม่ใช่ Widget — เวลา reorder ถ้าไม่มี Key state จะผิดตัว
  • Keys ช่วย Element Tree จับคู่ Widget ถูกตัวเมื่อ list เปลี่ยน (ValueKey เบากว่า ObjectKey)
  • StatefulWidget ควรเล็กที่สุด — extract ส่วนที่มี state ออกเป็น Widget แยก
  • const widgets ถูก reuse โดยสมบูรณ์ → performance ดีที่สุด
  • Flutter — framework ที่ใช้ Three Trees
  • Widgets — Widget Tree คือ tree ของ Widgets
  • State Management — setState triggers Widget Tree rebuild