PythonIDE Docs
中文
简体中文

性能与实时界面

普通 State、Timer、ReactiveState 和实时快路径的选择。

appui 的性能优化优先从界面结构、状态粒度和后台任务开始。大多数页面不需要特殊技巧,只要避免在 body() 里做重活,就能保持流畅。如果页面有高频属性更新,再看 实时快路径

#预期效果

示例会展示列表筛选、状态粒度、Timer 和高频属性更新的性能写法。

#优先优化的地方

场景推荐做法
列表数据使用 List + ForEach,并提供稳定 key
设置页使用 Form + Section
多字段状态变化使用 state.batch_update(...)
派生搜索结果使用普通函数或 computed
计时器和进度模块级 Timer,回调中更新状态
高频数值先用 State 做正确,再按需切到 ReactiveState
大文件读取放到按钮动作、任务或后台流程中
图片和网络先异步加载,再把结果写入状态

#避免在 body 中做重活

body() 应该只描述界面。不要在里面同步读取大文件、下载网络内容、递归扫描目录、创建 Timer 或做复杂排序。

python
import appui

state = appui.State(items=["main.py", "notes.md", "data.json"], selected="None")


def item_key(name):
    return name


def select_item(name):
    state.selected = name


def row_view(name):
    def select_current():
        select_item(name)

    return appui.Button(
        action=select_current,
        content=appui.Text(name).frame(max_width=appui.infinity, alignment="leading"),
    ).button_style("plain")


def body():
    return appui.NavigationStack(
        appui.List([
            appui.Section("Files", [
                appui.ForEach(state.items, row_builder=row_view, key=item_key)
            ]),
            appui.Section("State", [
                appui.LabeledContent("Selected", value=state.selected)
            ]),
        ]).navigation_title("Files")
    )


appui.run(body, state=state, presentation="sheet")

如果数据来自耗时操作,先在事件中准备好,再更新状态。

#控制状态刷新范围

把变化频繁的值放到明确字段里。一次动作改多个字段时用 batch_update,避免中间状态反复重建。

python
import appui

state = appui.State(count=0, message="Ready")


def plus():
    state.batch_update(count=state.count + 1, message="Updated")


def reset():
    state.batch_update(count=0, message="Reset")


def body():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("Counter", [
                appui.LabeledContent("Count", value=str(state.count)),
                appui.HStack([
                    appui.Button("增加", action=plus).button_style("bordered_prominent"),
                    appui.Button("重置", action=reset).button_style("bordered"),
                ], spacing=12),
            ], footer=state.message)
        ]).navigation_title("Refresh Scope")
    )


appui.run(body, state=state, presentation="sheet")

#列表和大数据

大列表优先使用原生列表组件。过滤、排序、分组这类派生数据不要在每个 row 里重复计算。

python
import appui

rows = [{"id": index, "title": f"Item {index}"} for index in range(1, 101)]
state = appui.State(query="", rows=rows, selected="None")


def set_query(value):
    state.query = value


@appui.computed(state, depends_on=["query", "rows"])
def filtered_rows():
    keyword = state.query.strip().lower()
    if not keyword:
        return state.rows
    return [row for row in state.rows if keyword in row["title"].lower()]


def row_key(row):
    return row["id"]


def select_row(row):
    state.selected = row["title"]


def row_view(row):
    def select_current():
        select_row(row)

    return appui.Button(
        action=select_current,
        content=appui.HStack([
            appui.Text(row["title"]).frame(max_width=appui.infinity, alignment="leading"),
            appui.Image(system_name="chevron.right").foreground_color("tertiaryLabel"),
        ]),
    ).button_style("plain")


def body():
    visible = filtered_rows()
    return appui.NavigationStack(
        appui.List([
            appui.Section(f"{len(visible)} results", [
                appui.ForEach(visible, row_builder=row_view, key=row_key)
            ]),
            appui.Section("Selection", [
                appui.LabeledContent("Selected", value=state.selected)
            ]),
        ])
        .searchable(text=state.query, on_change=set_query)
        .navigation_title("Large List")
    )


appui.run(body, state=state, presentation="sheet")

不要把大量行直接塞进普通 VStack 再放进 ScrollView。列表需要稳定的行身份和系统级复用能力。

#Timer 和中频刷新

Timer 放模块级,启动和停止由回调控制,不要在 body() 里创建。

python
import appui

state = appui.State(seconds=0, running=False)


def tick():
    if state.running:
        state.seconds += 1


timer = appui.Timer(interval=1.0, action=tick)


def start():
    state.running = True
    timer.start()


def stop():
    state.running = False
    timer.stop()


def body():
    return appui.NavigationStack(
        appui.Form([
            appui.Section("Timer", [
                appui.LabeledContent("Seconds", value=str(state.seconds)),
                appui.HStack([
                    appui.Button("Start", action=start).button_style("bordered_prominent"),
                    appui.Button("Stop", action=stop).button_style("bordered"),
                ], spacing=12),
            ], footer="Running" if state.running else "Stopped")
        ]).navigation_title("Timer")
    )


appui.run(body, state=state)

#高频属性更新

当页面结构稳定、只是已有控件的值持续变化时,可以使用 实时快路径模式。它适合进度、滑块、仪表盘、计时器和状态文本。

关键原则:

  • 先保证普通 State 版本正确。
  • 高频字段再改成 ReactiveState 或组件绑定。
  • 不要手动调用未公开接口。
  • 结构变化仍然交给普通刷新。
python
import appui

state = appui.ReactiveState(
    title="Meter",
    level=(0.0, 1),
    peak=(0.0, 2),
)


def update_meter(level, peak):
    state.level = level
    state.peak = peak

#重型原生视图

VideoPlayerMapViewWebView、相机预览这类视图不要放进普通列表行里滚动。把它们放在稳定区域,让下方的控制项使用 FormList

场景推荐
视频播放顶部固定 VideoPlayer,下方 Form 控制区
地图选点顶部 MapView,下方坐标和操作
WebView 帮助页WebView 独立稳定区域
图表仪表盘ChartCanvas 放在固定高度卡片中

#小结

  • body() 只做界面描述。
  • 数据准备、文件读取、网络请求放到界面构建之外。
  • 结构变化用普通状态刷新。
  • 多字段变化用 batch_update
  • 长列表用 List + ForEach + stable key
  • 高频属性变化看 实时快路径;相机、传感器、音频或系统状态流看 iOS 原生能力