FastHTML 最佳实践
FastHTML 应用程序与使用 FastAPI/React、Django 等框架的应用程序不同。不要假设 FastHTML 的最佳实践与其他框架相同。最佳实践体现了 fast.ai 的哲学:减少繁文缛节,利用智能默认值,编写既简洁又清晰的代码。以下是一些人和语言模型有时会忽略的特殊机会。
数据库表的创建
之前
todos = db.t.todos
if not todos.exists():
todos.create(id=int, task=str, completed=bool, created=str, pk='id')之后
class Todo: id:int; task:str; completed:bool; created:str
todos = db.create(Todo)FastLite 的 create() 是幂等的——它会在需要时创建表,并无论如何都会返回表对象。使用数据类(dataclass)风格的定义更简洁,也更符合 Python 风格。id 字段自动成为主键。
路由命名约定
之前
@rt("/")
def get(): return Titled("Todo List", ...)
@rt("/add")
def post(task: str): ...之后
@rt
def index(): return Titled("Todo List", ...) # Special name for "/"
@rt
def add(task: str): ... # Function name becomes route使用不带参数的 @rt,让函数名定义路由。特殊名称 index 会映射到 /。
优先使用查询参数而非路径参数
之前
@rt("/toggle/{todo_id}")
def post(todo_id: int): ...
# URL: /toggle/123之后
@rt
def toggle(id: int): ...
# URL: /toggle?id=123在 FastHTML 中,查询参数更符合语言习惯,并能避免在路径中重复参数名称。
善用返回值
之前
@rt
def add(task: str):
new_todo = todos.insert(task=task, completed=False, created=datetime.now().isoformat())
return todo_item(todos[new_todo])
@rt
def toggle(id: int):
todo = todos[id]
todos.update(completed=not todo.completed, id=id)
return todo_item(todos[id])之后
@rt
def add(task: str):
return todo_item(todos.insert(task=task, completed=False, created=datetime.now().isoformat()))
@rt
def toggle(id: int):
return todo_item(todos.update(completed=not todos[id].completed, id=id))insert() 和 update() 都会返回受影响的对象,从而实现函数式链式调用。
使用 .to() 生成 URL
之前
hx_post=f"/toggle?id={todo.id}"之后
hx_post=toggle.to(id=todo.id).to() 方法能以类型安全的方式生成 URL,并且对重构友好。
PicoCSS 免费附带
之前
style = Style("""
.todo-container { max-width: 600px; margin: 0 auto; padding: 20px; }
/* ... many more lines ... */
""")之后
# Just use semantic HTML - Pico styles it automatically
Container(...), Article(...), Card(...), Group(...)fast_app() 默认包含 PicoCSS。请使用 Pico 能自动设置样式的语义化 HTML 元素。对于更复杂的 UI 需求,请使用 MonsterUI(类似 shadcn,但专为 FastHTML 设计)。
智能默认值
之前
return Titled("Todo List", Container(...))
if __name__ == "__main__":
serve()之后
return Titled("Todo List", ...) # Container is automatic
serve() # No need for if __name__ guardTitled 已经将内容包裹在 Container 中,而 serve() 会在内部处理主程序入口的检查。
FastHTML 处理可迭代对象
之前
Section(*[todo_item(todo) for todo in all_todos], id="todo-list")之后
Section(map(todo_item, all_todos), id="todo-list")FastHTML 组件直接接受可迭代对象——无需使用 * 进行解包。
函数式编程模式
列表推导式很好用,但对于简单的转换,map() 通常更简洁,特别是与 FastHTML 的可迭代对象处理相结合时。
精简代码
之前
@rt
def delete(id: int):
# Delete from database
todos.delete(id)
# Return empty response
return ""之后
@rt
def delete(id: int): todos.delete(id)- 当代码能够自解释时,省略注释。
- 不要返回空字符串——默认会返回
None。 - 一行代码只表达一个思想。
所有修改操作都使用 POST
之前
hx_delete=f"/delete?id={todo.id}"之后
hx_post=delete.to(id=todo.id)FastHTML 路由默认只处理 GET 和 POST 请求。只使用这两种 HTTP 方法更符合语言习惯,也更简单。
现代 HTMX 事件语法
之前
hx_on="htmx:afterRequest: this.reset()"之后
hx_on__after_request="this.reset()"这样做是可行的,因为:
hx-on="event: code"已被弃用;推荐使用hx-on-event="code"。- FastHTML 会将
_转换为-(因此hx_on__after_request变为hx-on--after-request)。 - 在 HTMX 中,
::可以用作:htmx:的简写。 - HTMX 原生接受
-来替代:(所以-htmx-的作用与:htmx:相同)。 - HTMX 接受例如
after-request作为驼峰式命名afterRequest的替代方案。