🧩 Components & Observables
Học cách viết UI theo phong cách Declarative với Components và Observables. ⏱️ 25 phút
🎯 Mục tiêu
Phần tiêu đề “🎯 Mục tiêu”Nắm vững 2 khái niệm quan trọng nhất của Flet 0.80:
- @ft.component - Tạo UI components tái sử dụng
- @ft.observable - Quản lý state tự động cập nhật UI
🔄 Imperative vs Declarative
Phần tiêu đề “🔄 Imperative vs Declarative”Cách cũ (Imperative)
Phần tiêu đề “Cách cũ (Imperative)”# Thay đổi control → gọi updatedef increment(e): counter.value = str(int(counter.value) + 1) page.update() # 👈 Phải gọi thủ công
counter = ft.Text("0")Cách mới (Declarative) ✅
Phần tiêu đề “Cách mới (Declarative) ✅”# Thay đổi state → UI tự động cập nhật@ft.componentdef Counter(): count, set_count = ft.use_state(0) return ft.Text(str(count)) # 👈 Tự động cập nhật🧩 @ft.component - Tạo Component
Phần tiêu đề “🧩 @ft.component - Tạo Component”Component là hàm trả về UI, có thể tái sử dụng:
import flet as ft
@ft.componentdef Greeting(name: str): return ft.Container( content=ft.Column([ ft.Text(f"Xin chào, {name}! 👋", size=24), ft.Text("Chào mừng đến với Flet"), ]), padding=20, bgcolor=ft.Colors.BLUE_100, border_radius=10, )
def main(page: ft.Page): page.add( Greeting("Phúc"), Greeting("Bình"), )
ft.run(main)Quy tắc viết Component
Phần tiêu đề “Quy tắc viết Component”- Đánh dấu với
@ft.component - Tên hàm viết PascalCase (Greeting, UserCard…)
- Return một control duy nhất (dùng Column/Row để gom nhiều controls)
📊 @ft.observable - Quản lý State toàn cục
Phần tiêu đề “📊 @ft.observable - Quản lý State toàn cục”Observable là class lưu trữ state dùng chung, tự động cập nhật UI khi thay đổi:
import flet as ft
# Định nghĩa observable class@ft.observableclass AppState: count: int = 0
def increment(self): self.count += 1
def decrement(self): self.count -= 1
# Tạo instancestate = AppState()
@ft.componentdef Counter(): return ft.Row([ ft.IconButton(ft.Icons.REMOVE, on_click=lambda _: state.decrement()), ft.Text(str(state.count), size=24), # 👈 Tự động cập nhật khi count thay đổi ft.IconButton(ft.Icons.ADD, on_click=lambda _: state.increment()), ], alignment=ft.MainAxisAlignment.CENTER)
def main(page: ft.Page): page.add(Counter())
ft.run(main)Observable với Collections
Phần tiêu đề “Observable với Collections”@ft.observableclass TodoState: items: list = [] # Observable list
def add(self, text: str): self.items.append({"id": len(self.items), "text": text, "done": False})
def toggle(self, id: int): for item in self.items: if item["id"] == id: item["done"] = not item["done"] break
def remove(self, id: int): self.items = [item for item in self.items if item["id"] != id]
state = TodoState()🎯 Ví dụ: Todo App với Observable
Phần tiêu đề “🎯 Ví dụ: Todo App với Observable”import flet as ft
@ft.observableclass TodoState: items: list = []
def add(self, text: str): self.items.append({"id": len(self.items), "text": text, "done": False})
def toggle(self, id: int): for item in self.items: if item["id"] == id: item["done"] = not item["done"] break
state = TodoState()
@ft.componentdef TodoItem(item): return ft.ListTile( leading=ft.Checkbox( value=item["done"], on_change=lambda _: state.toggle(item["id"]), ), title=ft.Text( item["text"], style=ft.TextStyle( decoration=ft.TextDecoration.LINE_THROUGH if item["done"] else None ), ), )
@ft.componentdef TodoList(): input_ref, set_input = ft.use_state("")
def add_todo(_): if input_ref: state.add(input_ref) set_input("")
return ft.Column([ ft.Row([ ft.TextField( value=input_ref, hint_text="Thêm việc cần làm...", expand=True, on_change=lambda e: set_input(e.control.value), on_submit=add_todo, ), ft.IconButton(ft.Icons.ADD, on_click=add_todo), ]), ft.Column([TodoItem(item) for item in state.items]), ])
def main(page: ft.Page): page.title = "Todo App" page.add(TodoList())
ft.run(main)📐 Khi nào dùng gì?
Phần tiêu đề “📐 Khi nào dùng gì?”flowchart TD A["Bạn cần state?"] --> |Không| B["Return UI trực tiếp"] A --> |Có| C["State dùng ở đâu?"] C --> |"Chỉ 1 component"| D["ft.use_state()"] C --> |"Nhiều components"| E["@ft.observable"]
style D fill:#4CAF50,color:white style E fill:#2196F3,color:white| Trường hợp | Giải pháp |
|---|---|
| State local (1 component) | ft.use_state() |
| State chia sẻ (nhiều components) | @ft.observable class |
💡 Best Practices
Phần tiêu đề “💡 Best Practices”[!TIP]
- Tách nhỏ components - Mỗi component làm một việc
- State nâng lên - Đưa state lên component cha nếu cần chia sẻ
- Observable cho app state - User data, settings, cart…
- use_state cho UI state - Form input, toggle visibility…
⏭️ Bước tiếp theo
Phần tiêu đề “⏭️ Bước tiếp theo”Tuyệt! Tiếp tục với Hooks để học chi tiết về ft.use_state() và các hooks khác.