uniapp-长列表优化

最近涉及到uniapp 开发小程序和h5 做的一个长列表,遇到有几个需要注意的地方,在此写个记录提醒健忘的自己避免踩坑。

原始实现方式

首先一个正常的列表都是有一个 tab 来做头部选项卡和一个内部区域,通过手指的滑动可以切换选项卡。然后传统都是采用swiper 加  scroll-view实现(tab 因为没什么性能上的问题就不说了)。但是有时候程序并不能如你所愿,通过这两个组件实现的列表如果数据比较少的情况下确实是比较好用,但是如果出现tab每一项动态加载的数据比较多之后,通过swiper来切换,会导致页面的抖动和卡顿,这个就给用户和页面性能上带来较差的体验。

优化方式:

1、首先是scroll-view 的优化,这个组件如果超出预定区域后就会出现滚动条来进行滚动,滚动查看数据又或者在配合监听到底加载更多的时候,会出现滑动卡顿,不流畅等问题。首先我们来看下官方给出的解决方案:

scroll-view 不适合放长列表,有性能问题。长列表滚动和下拉刷新,应该使用原生导航栏搭配页面级的滚动和下拉刷新实现。包括在app-nvue页面,长列表应该使用list而不是scroll-view。

这个方案也是现在通用的解决方案,就是通过页面级的滚动方式来替换scroll-view的滚动。何为页面级,意思就是说在可视区里,通过内容撑出滚动条来进行滚动,这个还有个好处就是能在生命周期中监听的到是否拉到底部(虽然说 scroll-view也有,xixix)。最后需要处理一下tab ,如果tab 不想跟着页面级滚动走下去,可以搞个吸顶,吸顶之后,内容区域需要padding增加一下tab的高度避免挡着。

当然如果想一站式处理完美,还有各种友好的提示,可以用下mescroll 这个组件,官方给出的说明是:

mescroll.js 是在 H5端 运行的下拉刷新和上拉加载插件。原生js, 不依赖jquery,zepto支持vue超详细注释, 快速使用,轻松上手一套代码多端运行, 支持uni-app,完美运行于android, iOS, 手机各浏览器,兼容PC端主流浏览器

2、swiper的优化就比较复杂。在app端使用的话,uni使用的是nvue的原生性能,所以它会自动处理释放优化非可视区的数据。在非App端使用的模式是只缓存左右一共3列的数据,dom中的数据过多时,它会自动释放。就是说App上,只要看过这一页,再进去时内容是还在的。而在非App上,只能做到缓存3页数据,其他页即便看过,再进去也会重新加载。并且非App的这种情况下,不再提供下拉刷新。虽然插件市场也有其他前端模拟的下拉刷新,但性能不佳。一般小程序的大厂案例里,提供左右拖长列表的,都是这种做法。

如果此方法有难度实在实现不了的,可以将它做成组件后添加一下切换的过度动画来实现又或者直接不理会设计师的要求,我就是爸爸的心态,做成没有动画效果,固定的也行

<think>我们正在使用UniAppVue3来实现一个对话聊天功能,并支持模型的流式输出。根据引用的资料,我们可以结合流式API和Markdown渲染来实现。引用[1]提到了在UniApp中使用FetchAPI实现流式响应,引用[3]提供了一个Vue3+TS(uniapp)的聊天页面实现,其中使用了scroll-view来实现自动滚动到底部。主要步骤:1.使用fetch或uni.request来请求支持流式输出的API(如SSE或分块传输)。2.处理流式数据,逐步更新聊天内容。3.使用Markdown渲染组件来展示AI返回的内容(可以引用使用第三方组件或自定义)。4.实现聊天界面,包括消息列表和输入框,并确保新消息出现时自动滚动到底部。注意:由于UniApp的环境,我们需要使用UniApp提供的API(如uni.request)或者使用H5的FetchAPI(在App端可能需要使用条件编译,因为App端不支持EventSource,但H5支持)。或者考虑使用WebSocket,但这里使用SSE(Server-SentEvents)在H5端较为简单。但是,引用[1]中提到的“FetchAPI”可能指的是H5的fetch。为了跨平台兼容性,我们可以这样处理:-在H5端使用EventSource(SSE)或fetch的ReadableStream。-在非H5端(如App和小程序)则可能需要使用WebSocket或者分块传输并自己处理流(非标准流式,因为uni.request不支持流式,但可以接收分块数据,通过不断请求或长连接实现)。然而,考虑到实现的简便性,我们可以先实现H5端的流式输出,使用SSE(EventSource)。但注意小程序不支持EventSource,因此可能需要条件编译。由于项目是UniApp,我们也可以使用uni.request来接收流式数据,但uni.request不支持流式响应。所以替代方案:-使用WebSocket:全端支持,但需要服务器支持WebSocket。-使用分块请求:服务器按块返回数据,客户端通过轮询或长连接方式接收,但这样实现复杂。不过,引用[1]提到使用FetchAPI实现流式响应,在H5端我们可以使用原生的fetch,并利用ReadableStream来处理流。在非H5端,我们可能需要使用WebSocket。鉴于这个示例主要是为了演示,我们将以H5端为例,使用fetch和ReadableStream。同时,为了兼容非H5端,我们可能会提供不同的实现(条件编译),但本例仅提供H5实现。另外,引用[3]中提供了如何自动滚动到底部的实现,使用scroll-view和scroll-into-view。下面我们编写一个示例代码:注意:本示例只包含关键部分,并假设在H5环境下运行。首先,在Vue3的setup语法中:1.定义数据:消息列表(messages),当前输入内容(inputValue)。2.定义方法:发送消息(sendMessage),处理流式响应(handleStreamResponse)。3.使用scroll-view并设置scroll-into-view为最后一条消息的id,以实现自动滚动。我们将使用`marked`(一个Markdown解析库)来解析AI返回的Markdown文本。注意:在uni-app中引入第三方库可能需要考虑包体积,或者使用HBuilderX的插件市场中的Markdown渲染组件。但为了简化,我们暂时不引入marked,而是将Markdown文本作为纯文本显示,之后再替换为Markdown渲染组件。步骤:1.安装事件流处理的库(可选,也可以自己处理),这里我们使用`eventsource`(但注意EventSource是浏览器自带,在H5可直接使用)或者使用fetch的ReadableStream。2.由于我们使用fetch的ReadableStream,因此我们将分块读取数据并逐步更新消息内容。3.为了滚动到底部,每次更新消息后都需要滚动到最新消息。具体代码:我们将创建一个Vue组件。</think> 以下是一个基于 UniAppVue3 的对话聊天功能实现示例,支持流式输出和自动滚动效果: ```vue <script setup> import { ref, reactive, nextTick } from 'vue' // 消息数据结构 const messages = ref([ { id: 1, text: '你好!有什么我可以帮助的?', isUser: false } ]) const inputText = ref('') const loading = ref(false) // 创建滚动视图引用 const scrollView = ref(null) const lastMsgId = ref('msg0') // 流式获取AI回复 async function getAIResponse() { loading.value = true // 添加占位消息 const newMsgId = `msg${messages.value.length}` messages.value.push({ id: newMsgId, text: '', isUser: false }) try { // 使用Fetch API实现流式读取 const response = await fetch('https://your-api-endpoint.com/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: inputText.value }) }) if (!response.body) throw new Error('无响应体') const reader = response.body.getReader() let decoder = new TextDecoder() // 流式数据处理 while (true) { const { done, value } = await reader.read() if (done) break // 更新消息内容 const chunk = decoder.decode(value, { stream: true }) messages.value[messages.value.length - 1].text += chunk // 触发滚动更新 lastMsgId.value = newMsgId await nextTick() } } catch (error) { console.error('请求失败:', error) messages.value[messages.value.length - 1].text = '请求出错,请重试' } finally { loading.value = false inputText.value = '' } } // 发送消息 function sendMessage() { if (!inputText.value.trim() || loading.value) return messages.value.push({ id: `msg${messages.value.length}`, text: inputText.value, isUser: true }) getAIResponse() lastMsgId.value = `msg${messages.value.length - 1}` } </script> <template> <view class="container"> <!-- 消息列表 --> <scroll-view ref="scrollView" scroll-y :scroll-into-view="lastMsgId" :scroll-with-animation="true" class="message-container" > <view v-for="(msg, index) in messages" :key="msg.id" :id="`msg${index}`" class="message" :class="{'user-msg': msg.isUser, 'ai-msg': !msg.isUser}" > <!-- 使用markdown渲染组件展示内容 --> <markdown-view :content="msg.text" v-if="!msg.isUser"/> <text v-else>{{ msg.text }}</text> </view> <view v-if="loading" class="loading-indicator">思考中...</view> </scroll-view> <!-- 输入区域 --> <view class="input-area"> <input v-model="inputText" placeholder="输入消息..." @confirm="sendMessage" /> <button @click="sendMessage" :disabled="loading">发送</button> </view> </view> </template> <style> .message-container { height: 80vh; padding: 20rpx; } .user-msg { text-align: right; background-color: #e3f2fd; margin: 10rpx 0; padding: 15rpx; border-radius: 10rpx; } .ai-msg { text-align: left; background-color: #f5f5f5; margin: 10rpx 0; padding: 15rpx; border-radius: 10rpx; } .input-area { display: flex; padding: 20rpx; border-top: 1px solid #eee; } input { flex: 1; margin-right: 20rpx; border: 1px solid #ddd; padding: 10rpx; } .loading-indicator { padding: 20rpx; text-align: center; color: #888; } </style> ``` ### 关键实现点解析: 1. **流式处理机制**: - 使用 `fetch API + ReadableStream` 处理流式响应[^1] - 通过 `TextDecoder` 实时解析数据块 - 使用 `while` 循环持续读取数据流 2. **自动滚动优化**: - 利用 `scroll-view` 的 `scroll-into-view` 属性 - 动态更新 `lastMsgId` 跟踪最新消息[^3] - 配合 `nextTick()` 确保DOM更新后执行滚动 3. **数据结构设计**: - 区分用户消息和AI消息(使用 `isUser` 标识) - 为每条消息创建唯一ID用于锚点定位 4. **响应式交互**: - 添加 `loading` 状态防止重复提交 - 输入区域禁用控制与反馈 ### 使用说明: 1. 安装 `markdown-it` 渲染组件(通过npm安装) ```bash npm install markdown-it ``` 2. 创建 `markdown-view` 组件用于内容渲染: ```vue <script setup> import { ref, computed } from 'vue' import MarkdownIt from 'markdown-it' const props = defineProps(['content']) const md = new MarkdownIt() const htmlContent = computed(() => md.render(props.content)) </script> <template> <rich-text :nodes="htmlContent" /> </template> ``` ### 注意事项: 1. 小程序端需使用 `uni.request` 替代 `fetch` 2. iOS平台滚动动画需配置 `scroll-with-animation` 3. 实际API替换为您的后端服务地址 4. 复杂富文本建议使用 `uParse` 组件库
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值