🪝 Hooks
Học cách sử dụng Hooks để quản lý state local trong components. ⏱️ 15 phút
🎯 Mục tiêu
Phần tiêu đề “🎯 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
Phần tiêu đề “🪝 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.componentdef 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
Phần tiêu đề “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
Phần tiêu đề “📝 use_state với các kiểu dữ liệu”String
Phần tiêu đề “String”@ft.componentdef 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
Phần tiêu đề “Boolean”@ft.componentdef 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(), ])@ft.componentdef 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
Phần tiêu đề “Dict/Object”@ft.componentdef 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
Phần tiêu đề “⚠️ 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ếpitems.append(new_item)set_items(items)# ✅ ĐÚNG - Tạo list mớiset_items([*items, new_item])
🔄 Nhiều states trong một component
Phần tiêu đề “🔄 Nhiều states trong một component”@ft.componentdef 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
Phần tiêu đề “📐 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 |
flowchart LR subgraph "Component A" S1["use_state"] end
subgraph "Component B" S2["use_state"] end
subgraph "Toàn App" O["@observable<br/>AppState"] end
O --> A O --> B
style S1 fill:#4CAF50,color:white style S2 fill:#4CAF50,color:white style O fill:#2196F3,color:white🎯 Ví dụ thực tế: Counter với Reset
Phần tiêu đề “🎯 Ví dụ thực tế: Counter với Reset”import flet as ft
@ft.componentdef 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
Phần tiêu đề “⏭️ 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.