Hooks trong Flet
Học cách sử dụng Hooks để quản lý state local trong components. ⏱️ 15 phút
🎯 Mục tiêu
Nắm vững:
- ft.use_state() - State local trong component
- Khi nào dùng hooks vs observables
🪝 ft.use_state() - State Hook cơ bản
use_state() tạo state local cho component, tự động re-render khi state thay đổi:
import flet as ft
@ft.component
def Counter():
# Khai báo state với giá trị khởi tạo
count, set_count = ft.use_state(0)
return ft.Row([
ft.IconButton(ft.Icons.REMOVE, on_click=lambda _: set_count(count - 1)),
ft.Text(str(count), size=24),
ft.IconButton(ft.Icons.ADD, on_click=lambda _: set_count(count + 1)),
], alignment=ft.MainAxisAlignment.CENTER)
def main(page: ft.Page):
page.add(Counter())
ft.run(main)Cách hoạt động
count, set_count = ft.use_state(0)
# │ │ │
# │ │ └── Giá trị khởi tạo
# │ └── Hàm để cập nhật state
# └── Giá trị hiện tại của state📝 use_state với các kiểu dữ liệu
String
@ft.component
def NameInput():
name, set_name = ft.use_state("")
return ft.Column([
ft.TextField(
value=name,
label="Tên của bạn",
on_change=lambda e: set_name(e.control.value),
),
ft.Text(f"Xin chào, {name}!" if name else "Nhập tên của bạn"),
])Boolean
@ft.component
def ToggleCard():
is_visible, set_visible = ft.use_state(True)
return ft.Column([
ft.Switch(
label="Hiển thị nội dung",
value=is_visible,
on_change=lambda _: set_visible(not is_visible),
),
ft.Text("Nội dung secret! 🤫") if is_visible else ft.Container(),
])List
@ft.component
def TagList():
tags, set_tags = ft.use_state([])
tag_input, set_input = ft.use_state("")
def add_tag(_):
if tag_input and tag_input not in tags:
set_tags([*tags, tag_input]) # Tạo list mới
set_input("")
def remove_tag(tag):
set_tags([t for t in tags if t != tag])
return ft.Column([
ft.Row([
ft.TextField(
value=tag_input,
hint_text="Thêm tag...",
on_change=lambda e: set_input(e.control.value),
on_submit=add_tag,
),
ft.IconButton(ft.Icons.ADD, on_click=add_tag),
]),
ft.Row([
ft.Chip(
label=ft.Text(tag),
on_delete=lambda _, t=tag: remove_tag(t),
)
for tag in tags
], wrap=True),
])Dict/Object
@ft.component
def UserForm():
user, set_user = ft.use_state({"name": "", "email": ""})
def update_field(field, value):
set_user({**user, field: value}) # Spread operator để tạo object mới
return ft.Column([
ft.TextField(
label="Tên",
value=user["name"],
on_change=lambda e: update_field("name", e.control.value),
),
ft.TextField(
label="Email",
value=user["email"],
on_change=lambda e: update_field("email", e.control.value),
),
ft.Text(f"User: {user}"),
])⚠️ Quy tắc quan trọng
[!CAUTION] Không mutate state trực tiếp! Luôn tạo giá trị mới:
# ❌ SAI - Mutate trực tiếp items.append(new_item) set_items(items) # ✅ ĐÚNG - Tạo list mới set_items([*items, new_item])
🔄 Nhiều states trong một component
@ft.component
def LoginForm():
# Nhiều states độc lập
username, set_username = ft.use_state("")
password, set_password = ft.use_state("")
is_loading, set_loading = ft.use_state(False)
error, set_error = ft.use_state("")
def handle_login(_):
set_loading(True)
set_error("")
# ... login logic
return ft.Column([
ft.TextField(label="Username", value=username,
on_change=lambda e: set_username(e.control.value)),
ft.TextField(label="Password", value=password, password=True,
on_change=lambda e: set_password(e.control.value)),
ft.Text(error, color=ft.Colors.RED) if error else ft.Container(),
ft.FilledButton(
"Login",
on_click=handle_login,
disabled=is_loading,
),
])📐 use_state vs Observable
| Đặc điểm | use_state | @ft.observable |
|---|---|---|
| Phạm vi | Component local | Toàn app |
| Chia sẻ | Không | Có |
| Định nghĩa | Trong component | Ngoài component |
| Use case | Form input, toggle, UI state | User data, cart, settings |
🎯 Ví dụ thực tế: Counter với Reset
import flet as ft
@ft.component
def CounterWithReset():
count, set_count = ft.use_state(0)
return ft.Card(
content=ft.Container(
content=ft.Column([
ft.Text("Counter", size=20, weight=ft.FontWeight.BOLD),
ft.Text(str(count), size=48),
ft.Row([
ft.IconButton(ft.Icons.REMOVE_CIRCLE, on_click=lambda _: set_count(count - 1)),
ft.IconButton(ft.Icons.ADD_CIRCLE, on_click=lambda _: set_count(count + 1)),
ft.IconButton(ft.Icons.REFRESH, on_click=lambda _: set_count(0)),
], alignment=ft.MainAxisAlignment.CENTER),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
padding=20,
),
)
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(CounterWithReset())
ft.run(main)⏭️ Bước tiếp theo
Đã nắm vững Hooks! Hãy ứng dụng vào Mini Project: Counter App để thực hành.
Last updated on