这一课我们把客户数据放进 State,让表格和徽章根据这些数据来渲染。这也是学习 Reflex 中最重要的概念——Var 的一课。
import dataclasses
@dataclasses.dataclass
class User:
name: str
email: str
gender: strUser(name="...", email="...", gender="...") 来创建。{"name": ...})也可以,但用 dataclass 的话,编辑器能帮你发现字段名的拼写错误,在界面代码里也能像 user.name 这样用点号访问。class State(rx.State):
users: list[User] = [
User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
]rx.State 的类中的类变量,就是状态变量(state var)。这里写的值是初始值。: list[User])是必需的。Reflex 根据类型来决定发送给浏览器的格式(JSON)以及可以使用哪些方法。第1课中我们说过“界面函数只在应用启动时执行一次”。那么在界面函数里使用 State.users,得到的是什么呢?实际打印出来看看:
>>> type(State.users).__name__
'ArrayCastedVar'
>>> print(State.users.length())
reflex___state____state__tutorial_261019___tutorial_261019____state.users_rx_state_.length得到的不是客户列表,而是一段 JavaScript 表达式。这就是 Var。界面函数执行时,还没有用户,也没有数据,所以它只是先立下一个约定:“以后在浏览器里再读取这个值”。
因此,有些 Python 语法不能用在 Var 上。下面是 Reflex 0.10 中实际出现的错误及替代写法:
| 想做的事 | 不能用的代码 | 出现的错误 | Reflex 的写法 |
|---|---|---|---|
| 计数 | len(State.users) | TypeError: object of type 'ArrayCastedVar' has no len() | State.users.length() |
| 条件分支 | "a" if State.n > 1 else "b" | VarTypeError: Cannot convert Var ... to bool | rx.cond(State.n > 1, "a", "b") |
| 循环 | for u in State.users: | VarTypeError: Cannot iterate over Var ... | rx.foreach(State.users, show_user) |
| 复杂的数据处理 | 在界面函数中计算 | — | 在事件或 @rx.var(computed var)中计算 |
反过来,Var 之间的比较和算术运算(State.page + 1)、在 f-string 中嵌入 Var(f"共 {State.total} 人")都没问题,因为这些运算本身会被翻译成 JavaScript 表达式。
讲师笔记 — 这张表是本课程最重要的内容。初学者在 Reflex 中遇到的错误,大部分都出自这里。录视频时推荐这样安排:故意写 len(State.users) 展示报错画面,抛出“为什么不行?”的问题,再用上面的 print 结果来解答。
现在把页头里的临时徽章换成真实的客户数。
rx.badge(
State.users.length(), # 用 Var 代替 "2"
variant="soft",
color_scheme="gray",
radius="full",
),def show_user(user: User):
return rx.table.row(
rx.table.cell(user.name),
rx.table.cell(user.email),
rx.table.cell(user.gender),
style={"_hover": {"bg": rx.color("gray", 3)}},
align="center",
)User,但实际传进来的是指向 User 的 Var。user.name 也会变成“读取这一行客户的 name”的表达式。正因为用 dataclass 声明了字段,才能用点号访问。style={"_hover": {...}} 是鼠标悬停时的样式。像 _hover、_focus 这样以 _ 开头的键,对应 CSS 中 :hover 之类的状态选择器。在页头下方、卡片内部,把表格作为第二个子元素放进去。
rx.box(
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
)
),
rx.table.body(rx.foreach(State.users, show_user)),
variant="ghost",
size="3",
width="100%",
),
overflow_x="auto", # 窄屏时横向滚动
width="100%",
border_top=f"1px solid {rx.color('slate', 4)}",
border_bottom=f"1px solid {rx.color('slate', 4)}",
padding_x="12px",
),root > header(标题行)+ body(数据行)> row > cell,分别对应 HTML 的 <table>、<thead>、<tbody>、<tr>、<td>。rx.foreach(列表 Var, 函数) 会在浏览器中对列表的每一项应用该函数。注意只传函数名,不要调用它(是 show_user,而不是 show_user())。for,因为它的值在编译时就已经确定了。例如:*[rx.table.column_header_cell(h) for h in ["Name", "Email", "Gender"]]在浏览器中确认 — 卡片里出现了包含 2 位客户的表格,徽章上的 2 现在来自 State.users.length()。鼠标悬停在某一行上时,背景会变成浅灰色。
localhost:3000
运行效果Your customer directory and audience breakdown.
rx.cond 实现:客户数为 0 时,不显示表格,而是显示“还没有客户”。提示:rx.cond(State.users.length() > 0, 表格, rx.text(...))len(State.users),亲自读一读错误信息。len/if/for。rx.foreach 的函数不能加括号调用。订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。