uni-app 数据绑定和事件处理,用一个筛选表单讲清楚
页面真正变复杂往往不是因为元素多而是因为用户开始操作了。输入关键词。选择分类。调整价格。点搜索。页面要跟着这些操作变化。数据变了视图要更新用户点了按钮方法要执行某个字段变了可能还要触发请求。这一篇不用零散例子讲ref、v-model、computed、watch。我们写一个商品筛选表单把它们串起来。太长不看版这一篇用商品筛选表单讲交互链路。用户输入或选择以后事件函数更新响应式数据computed负责生成展示文案和派生值点击搜索时再把页面状态整理成请求参数。能用computed表达的派生数据不要重复存一份。这个表单要做什么页面上有四件事输入关键词。选择分类。输入最低价和最高价。点击搜索得到请求参数。交互链路是这样。用户输入或选择事件函数修改响应式数据computed 生成派生结果视图自动更新点击搜索时组装参数文件位置pages/goods/filter.vue也别忘了注册到pages.json。{path:pages/goods/filter,style:{navigationBarTitleText:筛选商品}}先把响应式数据建好在 Vue3 的script setup里最常用的是ref和reactive。这里表单字段属于一个整体用reactive很顺手。script setup import { reactive, computed, watch } from vue const categories [全部, 食品, 日用品, 数码, 服饰] const form reactive({ keyword: , categoryIndex: 0, minPrice: , maxPrice: }) /scriptreactive适合管理一组相关字段比如表单。如果只是一个简单值比如count、visible、status用ref更直接。有一个新手很容易混的点ref在脚本里要.value在模板里不用。reactive对象字段正常用点操作就行。constcountref(0)count.value模板里写text{{ count }}/text不用写count.value。输入框有两种写法商品关键词可以用v-model。input classinput v-modelform.keyword placeholder输入商品关键词 /v-model适合普通输入框能少写一段事件赋值逻辑。但你仍然要理解事件写法因为 uni-app 里很多组件的值都在事件对象里。比如价格输入我们故意用input写。input classinput typedigit :valueform.minPrice placeholder最低价 inputhandleMinPriceInput /consthandleMinPriceInput(e){form.minPricee.detail.value}在 uni-app 和小程序场景里表单事件值经常在e.detail.value。你可以把它理解成组件把用户操作结果放进事件详情里再交给你的方法。视图页面状态handleInputinput用户视图页面状态handleInputinput用户输入价格触发 input 事件读取 e.detail.value 并写入 form视图更新picker 也走事件分类选择用picker。picker :rangecategories :valueform.categoryIndex changehandleCategoryChange view classpicker-value {{ currentCategory }} /view /pickerconsthandleCategoryChange(e){form.categoryIndexNumber(e.detail.value)}picker返回的是索引当前分类文本可以通过计算属性得到。constcurrentCategorycomputed((){returncategories[form.categoryIndex]})computed 适合派生数据不要重复存如果一个值可以由其他字段算出来就不要再存一份。比如当前分类文本、是否填写价格、搜索按钮文案都可以算出来。consthasPriceRangecomputed((){returnBoolean(form.minPrice||form.maxPrice)})constsearchSummarycomputed((){constparts[]if(form.keyword.trim()){parts.push(关键词${form.keyword.trim()})}if(currentCategory.value!全部){parts.push(分类${currentCategory.value})}if(hasPriceRange.value){parts.push(价格${form.minPrice||0}-${form.maxPrice||不限})}returnparts.length0?parts.join():还没有设置筛选条件})这里不要额外维护一个summary字段。否则你要在关键词变化、分类变化、价格变化时到处同步一漏就错。数据绑定里很重要的一条经验是源数据只留一份能算出来的就算出来。watch 适合副作用但不要滥用computed适合算值。watch适合做副作用。什么叫副作用比如数据变化以后你要请求接口、写缓存、打印日志、上报埋点这些都不是单纯展示。比如关键词变化后你想提示用户正在准备搜索。watch(()form.keyword,(newKeyword){if(!newKeyword.trim())returnconsole.log(关键词变化了新的关键词是,newKeyword)})但初学阶段要克制使用watch。如果你只是想显示一个由其他数据计算出来的文本用computed。如果你想在关键词变化后自动请求接口要考虑防抖或取消旧请求。否则用户连续输入a、ab、abc你可能瞬间打三次请求旧请求还可能比新请求更晚回来。这个问题会在网络请求那篇专门讲。点击搜索时组装参数搜索按钮不要直接把整个form原样扔给接口。页面状态是给页面用的请求参数是给接口用的中间最好做一次整理。constbuildSearchParams(){constparams{}constkeywordform.keyword.trim()if(keyword){params.keywordkeyword}if(currentCategory.value!全部){params.categorycurrentCategory.value}if(form.minPrice){params.minPriceNumber(form.minPrice)}if(form.maxPrice){params.maxPriceNumber(form.maxPrice)}returnparams}consthandleSearch(){constparamsbuildSearchParams()uni.showToast({title:Object.keys(params).length0?开始搜索:请先设置条件,icon:none})console.log(搜索参数,params)}这样做有两个好处。页面字段可以保留页面需要的形态比如categoryIndex。接口参数可以整理成接口需要的形态比如category、minPrice、maxPrice。这也是项目里很常见的分层页面状态归页面请求参数归请求。完整页面代码template view classpage view classpanel view classform-item text classlabel关键词/text input classinput v-modelform.keyword placeholder输入商品关键词 / /view view classform-item text classlabel分类/text picker :rangecategories :valueform.categoryIndex changehandleCategoryChange view classpicker-value {{ currentCategory }} /view /picker /view view classprice-row view classprice-item text classlabel最低价/text input classinput typedigit :valueform.minPrice placeholder0 inputhandleMinPriceInput / /view view classprice-item text classlabel最高价/text input classinput typedigit :valueform.maxPrice placeholder不限 inputhandleMaxPriceInput / /view /view view classsummary {{ searchSummary }} /view button typeprimary clickhandleSearch 搜索商品 /button /view /view /template script setup import { reactive, computed, watch } from vue const categories [全部, 食品, 日用品, 数码, 服饰] const form reactive({ keyword: , categoryIndex: 0, minPrice: , maxPrice: }) const currentCategory computed(() { return categories[form.categoryIndex] }) const hasPriceRange computed(() { return Boolean(form.minPrice || form.maxPrice) }) const searchSummary computed(() { const parts [] if (form.keyword.trim()) { parts.push(关键词${form.keyword.trim()}) } if (currentCategory.value ! 全部) { parts.push(分类${currentCategory.value}) } if (hasPriceRange.value) { parts.push(价格${form.minPrice || 0} - ${form.maxPrice || 不限}) } return parts.length 0 ? parts.join() : 还没有设置筛选条件 }) const handleCategoryChange (e) { form.categoryIndex Number(e.detail.value) } const handleMinPriceInput (e) { form.minPrice e.detail.value } const handleMaxPriceInput (e) { form.maxPrice e.detail.value } const buildSearchParams () { const params {} const keyword form.keyword.trim() if (keyword) { params.keyword keyword } if (currentCategory.value ! 全部) { params.category currentCategory.value } if (form.minPrice) { params.minPrice Number(form.minPrice) } if (form.maxPrice) { params.maxPrice Number(form.maxPrice) } return params } const handleSearch () { const params buildSearchParams() uni.showToast({ title: Object.keys(params).length 0 ? 开始搜索 : 请先设置条件, icon: none }) console.log(搜索参数, params) } watch( () form.keyword, (newKeyword) { if (!newKeyword.trim()) return console.log(关键词变化了, newKeyword) } ) /script style langscss .page { min-height: 100vh; padding: 32rpx; background: #f6f7fb; } .panel { padding: 28rpx; border-radius: 16rpx; background: #ffffff; } .form-item { margin-bottom: 28rpx; } .label { display: block; margin-bottom: 12rpx; font-size: 26rpx; color: #374151; } .input, .picker-value { min-height: 80rpx; padding: 0 24rpx; border: 1rpx solid #d1d5db; border-radius: 10rpx; font-size: 28rpx; line-height: 80rpx; } .price-row { display: flex; gap: 20rpx; } .price-item { flex: 1; } .summary { margin: 8rpx 0 28rpx; padding: 20rpx; border-radius: 10rpx; background: #f3f4f6; font-size: 26rpx; color: #4b5563; } /style这条链路比背语法重要数据绑定和事件处理的核心不是某个单独 API。它是一条链路用户操作组件。组件触发事件。事件函数读取e.detail.value或直接通过v-model更新数据。响应式数据变化。模板自动更新。computed负责派生展示。点击搜索时把页面状态整理成请求参数。这条链路顺了页面就从静态模板变成了真正能交互的应用。后面做列表、路由、请求、状态管理本质上都还在处理同一件事数据放哪里谁来改改完以后谁会受到影响。这里容易翻车在脚本里忘记.valueref在脚本里读写要用.value模板里会自动解包。把 computed 能算出的值再存一份比如筛选摘要可以由表单字段算出来就不要额外维护summary状态。watch 里直接请求接口关键词一变就请求用户连续输入时会打很多请求。网络请求篇会讲防抖、取消旧请求和请求序号校验参见08-网络请求与页面状态设计.md。页面状态和请求参数混在一起页面可以存categoryIndex但接口可能要的是category。提交前做一次buildSearchParams项目后面会清楚很多。自己试试给筛选表单加一个“只看有库存”的开关并把它放进搜索参数。给价格区间加一个校验最低价不能大于最高价。参考资料Vue Composition API 文档https://vuejs.org/api/composition-api-setup.htmlVue computed 文档https://vuejs.org/guide/essentials/computed.htmlDCloud input 组件文档https://uniapp.dcloud.net.cn/component/input.html

相关新闻

如何用Resurrectio生成ReStructuredText文档:自动化测试报告终极指南

如何用Resurrectio生成ReStructuredText文档:自动化测试报告终极指南

如何用Resurrectio生成ReStructuredText文档:自动化测试报告终极指南 【免费下载链接】resurrectio CasperJS test recorder Chrome extension 项目地址: https://gitcode.com/gh_mirrors/re/resurrectio 想要快速生成专业的自动化测试文档吗?Res…

2026/7/23 1:45:14 阅读更多 →
枚举sys模块

枚举sys模块

枚举sys模块 在内核中枚举进程的模块时,我们主要通过PEB中的_PEB_LDR_DATA中的三个链表来获取进程加载的模块,使用这种方法需要临时将当前地址空间切换到对应进程的地址空间,因为PEB是在进程虚拟地址空间中的,而不是内核地址空间。…

2026/7/23 6:37:33 阅读更多 →
课堂文化视域下学生数学情感培育的实现途径

课堂文化视域下学生数学情感培育的实现途径

《课堂文化视域下学生数学情感培育的实现途径》(庄海翔,《基础教育研究》2020.02,)本文立足课堂文化视角,针对当前数学教学重知识、轻情感,课堂 “盯盘补” 模式催生学生畏难消极情绪的问题展开研究。核心观…

2026/7/21 18:07:47 阅读更多 →

最新新闻

Stellaris UART寄存器级编程:从原理到实战的嵌入式串口通信指南

Stellaris UART寄存器级编程:从原理到实战的嵌入式串口通信指南

1. 项目概述与UART核心价值 在嵌入式开发领域,尤其是与传感器、模块或上位机打交道时,串口通信(UART)几乎是工程师的“空气和水”。它简单、可靠,不需要复杂的时钟同步线,两根线(TX和RX&#xf…

2026/7/23 15:14:13 阅读更多 →
Gemini vs Claude vs GPT-4 Turbo:实测27项任务后,这8个隐藏功能只有内测用户知道

Gemini vs Claude vs GPT-4 Turbo:实测27项任务后,这8个隐藏功能只有内测用户知道

更多请点击: https://kaifayun.com 第一章:Gemini 怎么用 Gemini 是 Google 推出的多模态大语言模型系列,支持文本、图像、音频、视频等多种输入形式。用户可通过多种方式与 Gemini 交互:网页端( gemini.google.com&a…

2026/7/23 15:14:13 阅读更多 →
内网IM如何打破“部门墙”

内网IM如何打破“部门墙”

当“部门墙”成为数字化转型的隐形天花板,越来越多企业发现,问题的根结不在沟通工具的数量,而在于连接方式的质量。一封跨部门审批邮件,动辄横跨三个部门、耗时两天,暴露出的不是个人效率问题,而是内网协同…

2026/7/23 15:14:13 阅读更多 →
CDN边缘计算优化:三重收益与节能实践

CDN边缘计算优化:三重收益与节能实践

1. 项目概述:CDN托管的商业价值重构 这个标题背后隐藏着一个极具商业洞察力的技术方案——通过CDN(内容分发网络)托管服务实现"一度电"级别的能源效率优化,进而创造三重收益模型。作为从业15年的基础设施架构师&#xf…

2026/7/23 15:14:13 阅读更多 →
强化学习开发环境全栈配置指南:从仿真到实机部署

强化学习开发环境全栈配置指南:从仿真到实机部署

1. 项目概述:强化学习开发环境全栈配置指南 在四足机器人和仿生机器人控制领域,强化学习已成为实现复杂运动控制的主流方法。这套环境配置方案源自Unitree Robotics官方推荐配置,经过我在Go1和Aliengo机器人上的实测验证,能够稳定…

2026/7/23 15:14:13 阅读更多 →
大模型入门指南:从零理解AI核心技术与应用

大模型入门指南:从零理解AI核心技术与应用

1. 大模型入门指南:从零开始理解人工智能的"大脑"作为一名长期关注AI技术发展的从业者,我经常被问到:"大模型到底是什么?为什么它突然变得这么重要?"这个问题看似简单,但要真正理解大模…

2026/7/23 15:13:13 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击: https://codechina.net 第一章:AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后,我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 12:54:44 阅读更多 →

月新闻