Agent Skills
› flet-dev/flet
› imperative-to-declarative-flet
imperative-to-declarative-flet
GitHub将命令式 Flet Python 应用重构为声明式组件模式,使用组件装饰器、状态钩子和可观察模型,消除手动页面更新。
Trigger Scenarios
需要将现有 Flet 应用从命令式转换为声明式风格
优化 Flet 应用的状态管理和 UI 渲染逻辑
Install
npx skills add flet-dev/flet --skill imperative-to-declarative-flet -g -y
SKILL.md
Frontmatter
{
"name": "imperative-to-declarative-flet",
"metadata": {
"short-description": "Port Flet app to declarative"
},
"description": "Convert an imperative Flet Python app in which controls are mutated and then page.update is called to declarative style using flet.component, flet.observable and state hooks."
}
Imperative → Declarative (Flet Python)
Port an existing imperative Flet app to “components mode” with @ft.component, hooks, and (optionally) @ft.observable models.
Outcomes
- Original folder unchanged; new sibling folder created.
- Entry point uses
page.render(App)(components mode). - UI is derived from state; minimal/zero manual
page.update()for normal UI updates.
Choose a state strategy
- Use
@ft.observablefor nested app data you mutate in place (boards/lists/cards, drag+drop reorder). - Use
ft.use_statefor local ephemeral UI state (hover flags, input text, dialog selection). - Avoid storing live
Control/component objects in state; store ids/enums and create controls during render.
Workflow
1) Create a new declarative copy
- Copy the existing app folder to a new one (e.g.
trolli→trolli-declarative-*). - Keep
assets/with the new folder.
2) Switch to components mode entrypoint
- Create a root component
@ft.component def App(): .... - Run with:
ft.run(lambda page: page.render(App), assets_dir=...)
- Set page globals either:
- in a
main(page)function beforepage.render(App), or - in
ft.on_mounted(...)(works, but ordering can be less obvious).
- in a
3) Centralize routing in App
- Keep
app.route: stras source of truth. - In one place, define:
route_change(e)to normalize/redirect/validate routes and setapp.route- a render-time
match app.route(or a derivedactive_screen) to pickcontent
- Prefer:
route_changemutates route state;App()render chooses UI based on that state.
4) Componentize UI
- Move UI chunks into
src/components/*.py. - Each component:
- takes only the model(s) it needs
- uses hooks for local UI state
- mutates observable models for app data changes
5) Dialogs: reduce page.update() usage
If a dialog mutates existing controls and calls page.update(), convert it to:
@ft.componentdialog content withft.use_stateforerror,selected_color, etc.- event handlers call setters (no explicit updates)
- show it via
page.show_dialog(ft.AlertDialog(content=DialogContent(...)))
6) Assets/fonts checklist
- Prefer an absolute
assets_dirderived from__file__. - If running via
flet run, be aware it can setFLET_ASSETS_DIRand overrideassets_dir=. - Font registration uses the dict key as the font family name:
page.fonts = {"Pacifico": "Pacifico-Regular.ttf"}- use
font_family="Pacifico".
Common pitfalls (and fixes)
- Event handler typing is invariant (
Event[Sub]≠Event[Base]):- If
on_clickis declared onButton, annotateeasft.Event[ft.Button](or usedef handler(): ...).
- If
TemplateRouteparams are dynamic:raw = getattr(troute, "id", None)thenisinstance(raw, str)beforeint(raw).
controls=[*[...], ...]can confuse type checkers with components:- build lists in two steps and annotate/cast, or return raw controls from non-component factories.
Version History
- 8f74fe9 Current 2026-08-20 12:12


