PythonIDE Docs
中文
简体中文

appui 思维方式

从命令式 UI 切到 State 驱动的声明式 View 树。

appui 的核心规则很简单:body() 返回一棵 View 树,状态变化后重新计算这棵树。不要手动创建、保存、移动原生控件;只描述当前状态下界面应该是什么样。

#预期效果

运行示例后会看到计数、登录分支和导航路径随状态变化即时刷新,页面代码始终只描述当前 View 树。

#先记住这 4 条

规则含义
View 是描述TextButtonVStack 只是声明界面结构。
State 是事实源用户输入、选择、列表数据放进 State / ReactiveState
body 要可重复执行body() 里不要写网络请求、文件写入、定时器创建等副作用。
修饰符有顺序.padding().background().background().padding() 的视觉结果不同。

#从命令式切到声明式

命令式 UI 常见写法是“找到控件,然后改它”。appui 的写法是“改状态,然后让界面重新声明”。

python
import appui

state = appui.State(count=0)


def increment():
    state.count += 1


def body():
    return appui.VStack([
        appui.Text(f"Count: {state.count}")
            .font("largeTitle")
            .bold(),
        appui.Button("Add", action=increment)
            .button_style("bordered_prominent"),
    ], spacing=16).padding()


appui.run(body, state=state)

这段代码里没有“更新 label 文本”的命令。按钮只修改 state.count,下一次重建时 Text 自然显示新值。

#View 树

View 可以嵌套,也可以根据状态返回不同分支。

python
import appui

state = appui.State(logged_in=False)


def log_in():
    state.logged_in = True


def body():
    if state.logged_in:
        content = appui.Text("已登录").font("title2")
    else:
        content = appui.Button("登录", action=log_in)

    return appui.VStack([
        appui.Text("欢迎").font("largeTitle").bold(),
        content,
    ], spacing=20).padding()


appui.run(body, state=state)

条件分支应该返回完整的 View。不要在 body() 外保存某个 View 再反复修改它。

#State 先行

写界面前先列状态字段:

场景推荐状态
普通字段、表单、开关State
高频数值,如 slider、拖动、图表数据ReactiveState
不触发重建的对象句柄Ref
列表 / 字典增删改ObservableList / ObservableDict

状态写入通常放在按钮、输入框绑定、手势或定时器回调里。多个字段一起改时用 state.batch_update(...),避免中间状态触发多次重建。

#修饰符链

修饰符返回新的 View 描述,因此可以连续调用:

python
(
    appui.Text("Hello")
    .font("title")
    .foreground_color("white")
    .padding()
    .background("systemBlue")
    .corner_radius(12)
)

常用顺序是:内容样式 -> 尺寸/间距 -> 背景/边框 -> 交互/导航。遇到视觉不对,优先检查修饰符顺序。

#导航也是数据

导航栈不要当成“打开页面”的命令集合,而是一个路径状态。

python
import appui

path = appui.NavigationPath()
state = appui.State()


def open_settings():
    path.append({"tag": "settings"})


def go_back():
    path.pop()


def settings_destination(data):
    return appui.VStack([
        appui.Text("设置").font("title2"),
        appui.Button("返回", action=go_back),
    ], spacing=12).padding()


def body():
    return appui.NavigationStack(
        content=appui.VStack([
            appui.Text("首页").navigation_title("示例"),
            appui.Button("去设置页", action=open_settings),
        ], spacing=16).padding(),
        path=path,
        destinations={
            "settings": settings_destination,
        },
    )


appui.run(body, state=state)

destinations 的回调会接收 data 参数;即使暂时不用,也要写成命名函数,避免把页面构造逻辑藏在内联回调里。

#常见误区

误区改法
body() 里创建定时器或发请求放到 effect、按钮回调或生命周期回调。
把 View 保存到全局变量里再修改保存状态,不保存 View。
列表原地改了但界面不刷新使用 ObservableList,或重新赋值一个新列表。
参数名不确定以 API 参考和代码补全为准。
一个 body() 写成几百行拆成普通 Python 函数返回子 View。

#下一步