Widgets and the Tree
composition ไม่ใช่ inheritance
หัวข้อที่มีชื่อว่า “composition ไม่ใช่ inheritance”Flutter สร้าง UI ด้วยการ nest widget ไม่ใช่ subclass คุณไม่ extend Button เพื่อเพิ่ม padding — แต่ wrap ด้วย Padding หนึ่งหน้าจอคือ tree ที่ลึกของ widget เล็ก ๆ ที่ทำหน้าที่เดียว ประกอบเข้าด้วยกัน
Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ const Text('Hello'), ElevatedButton(onPressed: () {}, child: const Text('Tap')), ], ), ), );}นี่คือ composition over inheritance แบบสุดโต่ง: widget เล็ก ๆ จำนวนมาก แต่ละตัวทำสิ่งเดียว nest กันจนเป็นภาพรวม Center, Padding, Column ไม่ใช่ของประดับ — widget เหล่านี้ คือ layout
สาม tree
หัวข้อที่มีชื่อว่า “สาม tree”นี่คือ model ที่อธิบายเกือบทุกอย่างเกี่ยวกับ behavior และ performance ของ Flutter ไม่ได้มี tree เดียว — มี สาม tree ที่ถูก sync ให้ตรงกัน:
flowchart LR
subgraph W["Widget tree"]
w1["Padding"] --> w2["Text"]
end
subgraph E["Element tree"]
e1["PaddingElement
(อยู่ต่อเนื่อง)"] --> e2["TextElement
(ถือ state)"]
end
subgraph R["Render tree"]
r1["RenderPadding
(layout)"] --> r2["RenderParagraph
(paint)"]
end
W -.creates.-> E
E -.creates.-> R - Widget — description ที่ immutable ที่คุณเขียน rebuild ตลอดและทิ้งไป ถูก
- Element — living instance ของ widget ณ ตำแหน่งหนึ่งใน tree element อยู่ต่อเนื่องข้าม rebuild, ถือ
Stateของ stateful widget และตัดสินว่า widget ใหม่จะ update element นี้ได้ หรือต้องสร้างใหม่แทน - RenderObject — ทำ layout, paint และ hit-testing จริง นี่คือ layer ที่แพง Flutter จึงพยายามอย่างหนักที่จะนำ render object กลับมาใช้ซ้ำ
ทำไม rebuild ถึงยังถูก
หัวข้อที่มีชื่อว่า “ทำไม rebuild ถึงยังถูก”เมื่อคุณ rebuild Flutter จะเดิน widget tree ใหม่และเทียบ widget ใหม่แต่ละตัวกับ element ที่มีอยู่ ถ้า widget ใหม่มี type และ key เดียวกัน กับตัวเก่า element จะถูก update ในที่เดิม — render object ถูกใช้ซ้ำ อัปเดตเฉพาะ property ที่เปลี่ยน เฉพาะเมื่อ type หรือ key ต่างเท่านั้น Flutter จึงรื้อ element กับ render object แล้ว build ใหม่
// การ rebuild สร้าง Text widget ใหม่ทุกครั้ง...Text(counter.toString())// ...แต่ TextElement และ RenderParagraph ข้างล่างถูกใช้ซ้ำ// มีแค่ string ที่เปลี่ยนถูกส่งลงไป นี่คือเหตุผลที่ setState ถูกการ reconciliation นี้ — widget ใหม่ที่ immutable ถูก diff กับ element ที่อยู่ต่อเนื่อง — คือห้องเครื่องของ Flutter Key (บทเรียนถัดไป) มีไว้ควบคุมการ reconcile นี้เมื่อการ match ด้วย type แบบ default ทำผิด