Bỏ qua để đến nội dung

🧩 Components & Observables

Học cách viết UI theo phong cách Declarative với Components và Observables. ⏱️ 25 phút

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
# 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")
# 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

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)
  1. Đánh dấu với @ft.component
  2. Tên hàm viết PascalCase (Greeting, UserCard…)
  3. 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.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)
@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()
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)
flowchart TD
A["Bạn cần state?"] --> |Không| B["Return UI trực tiếp"]
A --> || 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

[!TIP]

  1. Tách nhỏ components - Mỗi component làm một việc
  2. State nâng lên - Đưa state lên component cha nếu cần chia sẻ
  3. Observable cho app state - User data, settings, cart…
  4. use_state cho UI state - Form input, toggle visibility…

Tuyệt! Tiếp tục với Hooks để học chi tiết về ft.use_state() và các hooks khác.