You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python NiceGUI类组件异常排查及组件创建方式咨询

NiceGUI Todo List类组件异常问题及组件化方案咨询

问题描述

最近在用NiceGUI,把官方Todo List示例改成类组件形式后,单个列表运行正常,但创建第二个列表时出现异常:标记任务完成时其中一个列表无响应,必须增删任务才会更新,之后另一个列表又会停止响应。

我的实现代码

from nicegui import ui
from dataclasses import dataclass
from typing import List


@dataclass
class TodoItem:
    name: str
    done: bool = False


class TodoListComponent:
    def __init__(self, title: str, items: List[TodoItem] = None):
        self.title = title
        if items:
            self.items = items
        else:
            self.items = []

    def view(self):
        with ui.card().classes('w-80 items-stretch'):
            ui.label(self.title).classes('text-semibold test-2xl')
            self.on_change()
            add_input = ui.input('New item').classes('mx-12')
            add_input.on('keydown.enter', lambda: (self.add(add_input.value),
                                                   add_input.set_value('')))

    def add(self, name, done=False):
        self.items.append(TodoItem(name=name))
        self.on_change.refresh()

    def remove(self, item):
        self.items.remove(item)
        self.on_change.refresh()

    @ui.refreshable
    def on_change(self):
        print(self.title, self.items) # 标记第一个列表的任务完成时,可能会打印第二个列表的信息
        if not self.items:
            ui.label('List is empty.').classes('mx-auto')
            return
        done_items = sum(item.done for item in self.items)
        ui.linear_progress(done_items / len(self.items), show_value=False)
        with ui.row().classes('justify-center w-full'):
            ui.label(f'Completed {done_items}')
            ui.label(f'Remaining: {len(self.items) - done_items}')
        for item in self.items:
            with ui.row().classes('items-center'):
                ui.checkbox(value=item.done, on_change=self.on_change.refresh).bind_value(item, 'done')
                ui.input(value=item.name).classes('flex-grow').bind_value(item, 'name')
                ui.button(on_click=lambda item=item: self.remove(item), icon='delete')\
                    .props('flat fab-mini color=grey')


todos = TodoListComponent('My Friday')
todos.add('Order pizza', done=True)
todos.add('New NiceGUI Release')
todos.add('Clean the house')
todos.add('Call mom')
todos.view()

todos_2 = TodoListComponent('My Monday')
todos_2.add('Order pizza', done=True)
todos_2.add('New NiceGUI Release')
todos_2.add('Clean the house')
todos_2.add('Call mom')
todos_2.view()

ui.run()

疑问

同时想咨询,能不能像JavaScript框架那样在NiceGUI里创建组件,或者Python里有其他替代方案?


解决方案

问题原因

异常的核心是@ui.refreshable装饰的on_change方法没有做实例级隔离:多个组件实例共享了refreshable函数的上下文,导致刷新操作混淆了不同实例的状态;另外checkbox的on_change回调直接绑定self.on_change.refresh,没有正确捕获当前实例的上下文,引发跨实例的状态更新混乱。

修正后的代码

from nicegui import ui
from dataclasses import dataclass
from typing import List


@dataclass
class TodoItem:
    name: str
    done: bool = False


class TodoListComponent:
    def __init__(self, title: str, items: List[TodoItem] = None):
        self.title = title
        self.items = items if items else []
        self.refreshable_container = None  # 保存当前实例的refreshable容器引用

    def view(self):
        with ui.card().classes('w-80 items-stretch'):
            ui.label(self.title).classes('text-semibold text-2xl')  # 修正原代码的test-2xl拼写错误
            self.refreshable_container = self.on_change()  # 绑定当前实例的refreshable容器
            add_input = ui.input('New item').classes('mx-12')
            add_input.on('keydown.enter', lambda: (self.add(add_input.value), add_input.set_value('')))

    def add(self, name, done=False):
        self.items.append(TodoItem(name=name, done=done))
        self.refreshable_container.refresh()

    def remove(self, item):
        self.items.remove(item)
        self.refreshable_container.refresh()

    @ui.refreshable
    def on_change(self):
        print(self.title, self.items)
        if not self.items:
            ui.label('List is empty.').classes('mx-auto')
            return
        done_items = sum(item.done for item in self.items)
        ui.linear_progress(done_items / len(self.items), show_value=False)
        with ui.row().classes('justify-center w-full'):
            ui.label(f'Completed {done_items}')
            ui.label(f'Remaining: {len(self.items) - done_items}')
        for item in self.items:
            with ui.row().classes('items-center'):
                # 用lambda捕获当前实例的refresh方法,避免上下文混淆
                ui.checkbox(value=item.done, on_change=lambda _: self.refreshable_container.refresh()).bind_value(item, 'done')
                ui.input(value=item.name).classes('flex-grow').bind_value(item, 'name')
                ui.button(on_click=lambda item=item: self.remove(item), icon='delete')\
                    .props('flat fab-mini color=grey')


todos = TodoListComponent('My Friday')
todos.add('Order pizza', done=True)
todos.add('New NiceGUI Release')
todos.add('Clean the house')
todos.add('Call mom')
todos.view()

todos_2 = TodoListComponent('My Monday')
todos_2.add('Order pizza', done=True)
todos_2.add('New NiceGUI Release')
todos_2.add('Clean the house')
todos_2.add('Call mom')
todos_2.view()

ui.run()

组件化方案及替代方案

  1. NiceGUI组件化实现

    • 你当前的类组件思路完全可行,核心是通过@ui.refreshable隔离每个组件的渲染上下文,同时让每个实例持有自己的refreshable容器引用,避免状态混淆。
    • 也可以通过ui.element自定义原生组件,或组合现有组件封装成复用性更高的类/函数,实现类似Vue/React的组件模式。NiceGUI官方也推荐用类封装复杂组件,通过view方法渲染UI,配合refreshable机制实现状态更新。
  2. Python替代方案

    • PyWebIO:轻量级Web UI框架,支持函数式和类组件,适合快速构建交互式应用。
    • Streamlit:专注数据可视化的框架,通过脚本式写法实现组件复用,适合数据分析场景。
    • FastAPI + Vue/React:如果需要复杂前端交互,可用FastAPI做后端,配合JS框架开发前端,实现前后端分离。
    • PyQt/PySide:桌面应用框架,支持面向对象的组件封装,适合桌面端GUI开发。

内容的提问来源于stack exchange,提问作者dudavik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:35:54