Ex4nicegui
An extension library for nicegui. It has built-in responsive components and fully implements data-responsive interface programming.
Install / Use
/learn @CrystalWindSnake/Ex4niceguiREADME
ex4nicegui
<div align="center">简体中文| English
</div>对 nicegui 做的扩展库。内置响应式组件,完全实现数据响应式界面编程。


教程
头条文章-秒杀官方实现,python界面库,去掉90%事件代码的nicegui
微信公众号-秒杀官方实现,python界面库,去掉90%事件代码的nicegui
📦 安装
pip install ex4nicegui -U
入门
我们从一个简单的计数器应用开始,用户可以通过点击按钮让计数增加或减少。

下面是完整代码:
from nicegui import ui
from ex4nicegui import rxui
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count)
ui.button(icon="add", on_click=counter.increment)
ui.run()
现在看更多细节。ex4nicegui 遵从数据驱动方式定义界面。状态数据定义应用程序中所有可以变化的数据。
下面是 Counter 状态数据定义:
class Counter(rxui.ViewModel):
count: int = 0
- 自定义类需要继承
rxui.ViewModel - 这里定义了一个变量
count,表示计数器的当前值,初始值为 0
接着,在类中定义一系列操作数据的方法:
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
- 这些都是实例方法,可以修改
count变量的值
然后,在界面代码中,实例化 Counter 的对象。
counter = Counter()
我们通过 rxui.label 组件绑定 count 变量。把操作数据的方法绑定到按钮点击事件上。
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count)
ui.button(icon="add", on_click=counter.increment)
- 我们需要使用
rxui命名空间下的label组件,而不是nicegui命名空间下的label组件。 rxui.label组件绑定counter.count变量,当counter.count变化时,rxui.label组件自动更新。ui.button组件绑定counter.decrement和counter.increment方法,点击按钮时调用相应方法。
在复杂项目中,
Counter定义的代码可以放到单独的模块中,然后在界面代码中导入。
注意,当类变量名前面带有下划线时,数据状态不会自动更新。
class Counter(rxui.ViewModel):
count: int = 0 # 响应式数据,能自动同步界面
_count: int = 0 # 这里的下划线表示私有变量,不会自动同步界面
二次计算
接着前面的例子,我们再添加一个功能。当计数器的值小于 0 时,字体显示为红色,大于 0 时显示为绿色,否则显示为黑色。
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count).bind_color(counter.text_color)
ui.button(icon="add", on_click=counter.increment)
颜色值是依据计数器当前值计算得到的。属于二次计算。通过定义普通的实例函数即可。
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
然后,通过 rxui.label 组件的 bind_color 方法绑定 text_color 方法,使得颜色值自动更新。
rxui.label(counter.count).bind_color(counter.text_color)
二次计算缓存
现在,我们在计数器下方使用文字,显示当前计数器的颜色文本值。
...
# 数据状态代码
class Counter(rxui.ViewModel):
...
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count).bind_color(counter.text_color)
ui.button(icon="add", on_click=counter.increment)
rxui.label(lambda: f"当前计数器值为 {counter.count}, 颜色值为 {counter.text_color()}")
- 当二次计算非常简单时,可以直接使用 lambda 表达式
上面的代码中,有两个地方使用了 counter.text_color 方法。当 counter.count 变化时,counter.text_color 会执行两次计算。第二次计算是多余的。
为了避免多余的计算,我们可以把 counter.text_color 缓存起来。
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
@rxui.cached_var
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
rxui.cached_var装饰器可以把函数结果缓存起来,避免多余的计算。
列表
下面的示例,展示了如何使用列表。
class AppState(rxui.ViewModel):
nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
def __init__(self):
super().__init__()
self.nums = [1, 2, 3]
def append(self):
new_num = max(self.nums) + 1
self.nums.append(new_num)
def pop(self):
self.nums.pop()
def reverse(self):
self.nums.reverse()
def display_nums(self):
return ", ".join(map(str, self.nums))
# 界面代码
state = AppState()
with ui.row(align_items="center"):
ui.button("append", on_click=state.append)
ui.button("pop", on_click=state.pop)
ui.button("reverse", on_click=state.reverse)
rxui.label(state.display_nums)
如果你需要在定义列表时,初始化列表,建议在 __init__ 中设置。
class AppState(rxui.ViewModel):
nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
def __init__(self):
super().__init__()
self.nums = [1, 2, 3]
...
另一种方式是使用 rxui.list_var
class AppState(rxui.ViewModel):
# nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
nums = rxui.list_var(lambda: [1, 2, 3])
...
rxui.list_var参数是一个返回列表的函数
列表循环
定义列表后,我们可以用 effect_refreshable.on 装饰器,在界面中展示列表数据。
下面的例子中,界面会动态展示下拉框选中的图标
from ex4nicegui import rxui, effect_refreshable
class AppState(rxui.ViewModel):
icons = []
_option_icons = ["font_download", "warning", "format_size", "print"]
state = AppState()
# 界面代码
with ui.row(align_items="center"):
@effect_refreshable.on(state.icons)
def _():
for icon in state.icons:
ui.icon(icon, size="2rem")
rxui.select(state._option_icons, value=state.icons, multiple=True)
其中,@effect_refreshable.on(state.icons) 明确指定了依赖关系。当 state.icons 变化时,_ 函数会重新执行。
@effect_refreshable.on(state.icons)
def _():
# 这里的代码会在 state.icons 变化时重新执行
...
注意,每次执行,里面的内容都会被清除。这是数据驱动版本的
ui.refreshable
原则上,可以不通过 .on 指定监控的数据,只要函数中使用到的"响应式数据",都会自动监控
@effect_refreshable # 没有使用 .on(state.icons)
def _():
# 这里读取了 state.icons,因此会自动监控
for icon in state.icons:
ui.icon(icon, size="2rem")
建议总是通过
.on指定依赖关系,避免预料之外的刷新
数据持久化
ViewModel 使用代理对象创建响应式数据,当需要保存数据时,可以使用 rxui.ViewModel.to_value 转换成普通数据.
下面的例子,点击按钮将显示 my_app 的状态数据字典。
from nicegui import ui
from ex4nicegui import rxui
class MyApp(rxui.ViewModel):
a = 0
sign = "+"
b = 0
def show_data(self):
# >> {"a": 0, "sign": '+, "b": 0}
return rxui.ViewModel.to_value(self)
def show_a(self):
# >> 0
return rxui.ViewModel.to_value(self.a)
my_app = MyApp()
rxui.number(value=my_app.a, min=0, max=10)
rxui.radio(["+", "-", "*", "/"], value=my_app.sign)
rxui.number(value=my_app.b, min=0, max=10)
ui.button("show data", on_click=lambda: ui.notify(my_app.show_data()))
结合 rxui.ViewModel.on_refs_changed ,可以在数据变化时,自动保存数据到本地。
from nicegui import ui
from ex4nicegui import rxui
from pathlib import Path
import json
class MyApp(rxui.ViewModel):
a = 0
sign = "+"
b = 0
_json_path = Path(__file__).parent / "data.json"
def __init__(self):
super().__init__()
@rxui.ViewModel.on_refs_changed(self)
def _():
# a, sign, b 任意一个值变化时,自动保存到本地
self._json_path.write_text(json.dumps(self.show_data()))
def show_data(self):
return rxui.ViewModel.to_value(self)
...
apis
ViewModel
在 v0.7.0 版本中,引入 ViewModel 类,用于管理一组响应式数据。
下面是一个简单的计算器示例:
- 当用户修改数值输入框或符号选择框,右侧会自动显示计算结果
- 当结果小于 0 时,结果显示为红色,否则为黑色
from ex4nicegui import rxui
class Calculator(rxui.ViewModel):
num1 = 0
sign = "+"
num2 = 0
@rxui.cached_var
def result(self):
# 当 num1,sign,num2 任意一个值发生变化时,result 也会重新计算
return eval(f"{self.num1}{self.sign}{self.num2}")
# 每个对象拥有独立的数据
calc = Calculator()
with ui.row(align_items="center"):
rxui.number(value=calc.num1, label="Number 1")
rxui.select(value=calc.sign, options=["+", "-", "*", "/"], label="Sign")
rxui.number(value=calc.num2, label="Number 2")
ui.label("=")
rxui.label(calc.result).bind_color(
lambda: "red" if calc.r
Related Skills
node-connect
352.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
111.1kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
111.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
352.0kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
