Components & Observables trong Flet
Học cách viết UI theo phong cách Declarative với Components và Observables. ⏱️ 25 phút
🎯 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
Cách cũ (Imperative)
# Thay đổi control → gọi update
def 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) ✅
# Thay đổi state → UI tự động cập nhật
@ft.component
def Counter():
count, set_count = ft.use_state(0)
return ft.Text(str(count)) # 👈 Tự động cập nhật🧩 @ft.component - Tạo Component
Component là hàm trả về UI, có thể tái sử dụng:
import flet as ft
@ft.component
def 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
- Đá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
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.observable
class AppState:
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
# Tạo instance
state = AppState()
@ft.component
def 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
@ft.observable
class 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
import flet as ft
@ft.observable
class 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.component
def 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.component
def 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ì?
| 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
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
Tuyệt! Tiếp tục với Hooks để học chi tiết về ft.use_state() và các hooks khác.
Last updated on