ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

NiceGUI 边输入边搜索(Search As You Type)实战:基于 asyncio 任务取消的实时查询

NiceGUI 边输入边搜索(Search As You Type)实战:基于 asyncio 任务取消的实时查询 NiceGUI 边输入边搜索Search As You Type实战基于 asyncio 任务取消的实时查询【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui本篇技术指南以 NiceGUI 仓库中的examples/search_as_you_type示例为核心讲解如何用ui.input的on_change事件实现边输入边搜索的实时查询界面。文章将完整拆解示例的每一行代码并结合 NiceGUI 源码解释ValueChangeEventArguments事件参数、asyncio.Task.cancel()任务取消机制、ui.image图片渲染与 QuasarQInput属性配置的底层原理让读者不仅能跑通示例还能掌握可复用到任意外部 API 搜索场景的实战方案。示例概览调什么 API、实现什么效果该示例位于 examples/search_as_you_type核心代码只有一份文件 main.py约 36 行实现了以下完整功能使用 TheCocktailDB 的公共 API无需 API Key搜索鸡尾酒用户在搜索框中每敲一个字符就触发一次查询无需点击按钮或按回车输入速度较快时自动取消上一次尚未完成的请求避免过时结果覆盖新结果搜索过程中输入框从页面中部平滑上移为结果腾出空间结果以鸡尾酒缩略图卡片的形式横向排列图片下方标注名称。运行方式与仓库其他示例一致安装依赖后直接执行即可pip install nicegui httpx python main.pyhttpx是本示例的额外依赖用于发起异步 HTTP 请求nicegui为 UI 框架本体。启动后浏览器自动打开本地服务默认http://localhost:8080在搜索框中输入字母即可看到实时结果。三个核心要素on_change 事件、asyncio 任务与外部 API示例本质上由三个相互配合的部分构成理解这三者之间的协作关系是掌握该模式的关键事件驱动ui.input的on_change回调在每次按键时触发见下文源码分析把用户的每次输入都变成一个搜索请求的触发点异步任务管理把 HTTP 协程包装成asyncio.Task从而获得取消上一次请求的能力——这是边输入边搜索体验流畅的根基外部 API 调用通过httpx.AsyncClient异步请求 TheCocktailDB 的search.php接口返回 JSON 中包含鸡尾酒名称与缩略图 URL 列表。三个要素层层嵌套事件回调里创建任务任务里发请求请求返回后把数据渲染进界面。下面逐段拆解代码。逐步拆解 main.py1. 全局状态HTTP 客户端与正在运行的查询import asyncio import httpx from nicegui import events, ui api httpx.AsyncClient() running_query: asyncio.Task | None Noneapi httpx.AsyncClient()是一个模块级复用的异步 HTTP 客户端整个应用生命周期只创建一次避免为每次搜索都建立新的连接running_query: asyncio.Task | None None记录当前正在执行的搜索任务。它是实现请求取消的关键类型注解明确表明它要么是一个asyncio.Task要么是None当前没有在跑的任务。2. 搜索回调取消旧请求、发起新请求、渲染结果async def search(e: events.ValueChangeEventArguments) - None: Search for cocktails as you type. global running_query if running_query: running_query.cancel() # cancel the previous query; happens when you type fast search_field.classes(mt-2, removemt-24) # move the search field up results.clear() running_query asyncio.create_task( api.get(fhttps://www.thecocktaildb.com/api/json/v1/1/search.php?s{e.value}) ) response await running_query if response.text : return with results: # enter the context of the results row for drink in response.json()[drinks] or []: with ui.image(drink[strDrinkThumb]).classes(w-64): ui.label(drink[strDrink]).classes(absolute-bottom text-subtitle2 text-center) running_query None这段代码是整篇文章的核心逐行解释如下事件参数e: events.ValueChangeEventArguments这是ui.input的on_change回调收到的参数类型。在源码 nicegui/events.py 中定义为ValueChangeEventArguments(UiEventArguments, Generic[ValueT])携带value新值与previous_value旧值两个字段并继承自UiEventArguments内含sender元素引用与client客户端信息。这里用e.value取得输入框当前文本作为搜索关键词。取消上一次请求回调开头检查running_query若上一次搜索任务仍在执行说明用户输入速度快于网络响应立即调用running_query.cancel()。这是 asyncio 内建的任务取消机制——被取消的任务会在下一个 await 点抛出asyncio.CancelledError。没有这一步快速输入时较早发出的慢请求会后到先显示用旧结果覆盖新结果造成体验混乱。把协程变成可取消的任务asyncio.create_task(api.get(...))将 HTTP 协程包装为asyncio.Task并立即调度执行。注释明确说明其意图store the http coroutine in a task so we can cancel it later if needed——只有 Task 对象才能被cancel()裸协程不行。这也是示例不直接用await api.get(...)的原因。界面状态调整search_field.classes(mt-2, removemt-24)把输入框的外边距从mt-24改为mt-2实现搜索结果出现后输入框上移的动效results.clear()清空上一次搜索渲染出来的图片为本次结果做准备。空响应保护if response.text : return处理 API 返回空内容的情况例如搜索词匹配不到任何鸡尾酒时。注意此时running_query并不会被重置为None——不过由于下一次输入一定会重新赋值且此处返回后任务已完成实际不会造成问题但读者可以留意这是示例作者的一个小简化。结果渲染with results:进入results行row的上下文随后遍历 API 返回的drinks列表or []防御drinks字段为None的情况with ui.image(drink[strDrinkThumb]).classes(w-64): ui.label(drink[strDrink]).classes(absolute-bottom text-subtitle2 text-center)ui.image(...)加载鸡尾酒缩略图classes(w-64)将图片宽度固定为 16remTailwind 的w-64即width: 16rem在图片内部嵌套ui.label通过absolute-bottom将名称标签绝对定位到图片底部Quasar 提供的定位工具类配合text-subtitle2 text-center设置字号与居中。收尾所有结果渲染完成后running_query None复位全局状态表示当前没有在跑的查询。3. 界面搭建输入框与结果容器search_field ui.input(on_changesearch) \ .props(autofocus outlined rounded item-aligned input-classml-3) \ .classes(w-96 self-center mt-24 transition-all) results ui.row() ui.run()ui.input(on_changesearch)创建搜索输入框并绑定回调。需要强调的是on_change在每次按键值变化时都会触发而不是等回车或失焦。这一点在源码 nicegui/elements/input.py 的文档字符串中有明确说明Theon_changeevent is called on every keystroke and the value updates accordingly. If you want to wait until the user confirms the input, you can register a custom event callback, e.g.ui.input(...).on(keydown.enter, ...)orui.input(...).on(blur, ...).这正是Search As You Type模式的根基若想改成回车后搜索或失焦后搜索源码注释也给出了现成的替代方案keydown.enter或blur事件。.props(...)传递 Quasar 原生属性ui.input基于 Quasar 的QInput组件构建见 input.py 注释.props()可以把 QInput 的任意属性直接透传给底层组件autofocus页面加载后输入框自动获得焦点用户无需点击即可直接输入outlined使用描边样式outlined 外观rounded圆角外观item-aligned内容按列表项对齐input-classml-3给原生 input 元素加ml-3左边距。这里有个值得注意的细节——由于 QInput 是原生 input 的包装组件直接对ui.input本身加样式类无法作用到内部 input必须通过input-class/input-style属性input.py 文档字符串专门强调了这一点。.classes(...)设置 Tailwind 工具类w-96宽度 24remself-center在父容器flex 布局中水平居中mt-24顶部外边距 6rem让输入框初始时位于页面中部偏下的位置留出顶部空间transition-all所有 CSS 属性变化时平滑过渡——配合回调里mt-2的切换实现输入框上移动画。results ui.row()创建结果容器。注意回调中with results:与results.clear()都依赖对这个 row 元素的引用因此必须在定义回调之后、任何搜索发生之前创建。ui.run()启动 NiceGUI 应用负责开启本地服务器与浏览器。源码级原理为什么这套写法能边输入边搜索on_change 如何做到每次按键都触发ui.input的on_change参数在Input.__init__中通过super().__init__(..., on_value_changeon_change, ...)input.py注册为值变化处理器。值变化事件由前端input.js组件在每次输入时上报后端收到后派发回调。因此回调频率与按键节奏一致天然适合实时搜索场景。asyncio.Task.cancel() 为何能阻止过时结果覆盖每次搜索把 HTTP 协程包装进 Task 后任务在事件循环中与 UI 事件并发运行。用户快速输入时旧任务可能还阻塞在await api.get(...)的网络等待上此时cancel()会向该任务注入CancelledError任务从等待中唤醒并终止await running_query随即抛出异常后续渲染代码不会执行——即旧结果永远不会进入界面。这是整个示例最关键的一处设计用任务取消换结果时序正确。补充一点背景NiceGUI 自身在 nicegui/background_tasks.py 中提供background_tasks.create()来安全创建并追踪 asyncio 任务自动注册异常处理器、防止任务被垃圾回收。本示例直接使用asyncio.create_task是为了获得任务引用以便手动cancel()两种方式各有适用场景纯后台任务用background_tasks.create()更稳妥需要主动取消的任务则适合本示例的直接管理方式。ui.image 与行布局如何渲染结果卡片ui.image的源码见 nicegui/elements/image.py它基于 QuasarQImg组件source参数支持 URL、本地路径、base64 字符串或 PIL 图像。示例传入的是 TheCocktailDB 返回的strDrinkThumb远程图片 URL前端直接加载显示。结果行results是ui.row()默认水平排列多张鸡尾酒图片卡片自然横向排开。完整可运行代码将 examples/search_as_you_type/main.py 的完整内容整理如下与仓库一致可直接复制运行#!/usr/bin/env python3 import asyncio import httpx from nicegui import events, ui api httpx.AsyncClient() running_query: asyncio.Task | None None async def search(e: events.ValueChangeEventArguments) - None: Search for cocktails as you type. global running_query # pylint: disableglobal-statement # noqa: PLW0603 if running_query: running_query.cancel() # cancel the previous query; happens when you type fast search_field.classes(mt-2, removemt-24) # move the search field up results.clear() # store the http coroutine in a task so we can cancel it later if needed running_query asyncio.create_task(api.get(fhttps://www.thecocktaildb.com/api/json/v1/1/search.php?s{e.value})) response await running_query if response.text : return with results: # enter the context of the results row for drink in response.json()[drinks] or []: # iterate over the response data of the api with ui.image(drink[strDrinkThumb]).classes(w-64): ui.label(drink[strDrink]).classes(absolute-bottom text-subtitle2 text-center) running_query None # create a search field which is initially focused and leaves space at the top search_field ui.input(on_changesearch) \ .props(autofocus outlined rounded item-aligned input-classml-3) \ .classes(w-96 self-center mt-24 transition-all) results ui.row() ui.run()迁移到其他搜索场景的改造要点该模式的核心——输入事件 可取消的异步任务 结果容器复用——与具体 API 无关可以平滑迁移到任意实时搜索场景如搜索商品、GitHub 仓库、城市天气等。改造时只需注意四点替换 API 端点与解析逻辑把api.get(...)的 URL 换成目标接口记得把e.value作为查询参数传入并修改response.json()之后的数据提取与渲染部分保持任务引用 cancel结构只要需要防抖式地丢弃过时请求running_query的全局引用与cancel()调用就不能省注意竞态细节示例在response.text 分支提前返回时未复位running_query如果目标 API 也有空响应场景建议在return前补上running_query None保持状态一致可选的防抖增强若 API 有请求频率限制可在回调开头用asyncio.sleep(0.2)之类的短延时合并连续输入再配合本示例已有的取消逻辑即可获得更平滑的节流效果。小结examples/search_as_you_type用约 36 行代码完整演示了 NiceGUI 实时搜索的标准范式ui.input(on_change...)提供逐键事件asyncio.create_taskTask.cancel()保证结果时序ui.image与ui.row完成结果展示。通过阅读 main.py 以及其底层依赖的 input.py、events.py、image.py 与 background_tasks.py读者既可以快速上手也能深入理解 NiceGUI 事件系统与 asyncio 任务管理在实际应用中的协作方式。【免费下载链接】niceguiCreate web-based user interfaces with Python. The nice way.项目地址: https://gitcode.com/GitHub_Trending/ni/nicegui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表