资讯动态

RcList事件处理机制与长列表性能优化实战

发布时间:2026/10/21 9:49:38 来源:尧图企业网站定制
这个标题里写“半年磨一剑”不是包装话术是真花了半年。我有一个高频刷新的长列表页面数据量从几百条涨到上万条之后List 组件的性能开始撑不住快速滑动白屏、帧率掉到 30 以下、滑动与点击事件偶发冲突。后来我把整页迁到了 RcList 组件从头把它的数据懒加载、节点复用和事件处理机制摸了一遍期间踩了不少坑也沉淀出一套相对完整的用法。这篇文章就把我这半年的实践整理出来重点讲清楚 RcList 组件的事件处理机制到底是怎么回事以及在实际项目中怎么写、怎么避坑给准备接 RcList 或者正在 RcList 里面挣扎的开发者做个参考。RcList 是 ArkUI 递归容器系列里的列表组件和 List 最大的区别在于它本质上是“按需实例化”的数据不会一次性全部创建成节点只有滚动进入可视区附近的内容才会被真实渲染超出缓存范围的节点会被回收。正因为这套渲染机制它的事件处理逻辑和传统 List 完全不是一个思路。如果你还是拿 List 的那套“全部节点都在事件随便挂”的思维来写 RcList大概率会遇到点击索引对不上、事件不触发、回调刷屏之类的问题。这篇文章会先拆解组件底层机制再逐个讲事件类型和注册方式最后给一个完整的消息中心示例把点击、滑动删除、滚动监听、加载更多都串起来。刚开始接触 RcList 的开发者我建议先把基础概念过一遍已经用了但频繁出问题的可以直接跳到第 5 节踩坑记录与排查思路对着症状找答案。文章里所有代码我都在模拟项目 X 的工程里跑过可以放心参考。1. 先说说这个项目的来龙去脉1.1 为什么从 List 迁到 RcList我手上的页面是一个消息中心一开始数据量几百条List 完全扛得住。后来接入了系统通知、运营推送、用户互动三类消息单账号最多能积攒上万条未读记录。问题就来了List 是“全量渲染”的模型不管数据有没有显示在屏幕上它都会为每条数据创建对应的组件节点。一万条数据就是一万个节点即使不做复杂布局光节点的创建、测量、布局、绘制就够主线程忙一阵了。我用性能工具抓过数据List 在 3000 条数据时滑动帧率开始跌破 45到 8000 条时快速滑动肉眼可见地一顿一顿而且内存占用持续走高因为所有节点都活着即使离开屏幕也不销毁。这在低内存设备上尤其明显应用后台切换回来甚至会出现卡死。换到 RcList 之后同样一万条数据内存基本稳定快速滑动帧率能维持在 55 以上。这里面的差距就是渲染模型决定的。RcList 的做法是视口里能看到的 item 才创建视口外再额外缓存一小批超出缓存范围的节点回收销毁。数据量从几百到上万对 RcList 来说只是“有没有更多数据可以懒加载”的区别而不是“要创建多少节点”的区别。我在迁移前做了个简单的对比测试结论很直观对比项ListRcList渲染模型全量创建节点可视区 缓存区懒加载10000 条数据内存占用持续增长明显偏高稳定在视口 缓存所需水平快速滑动帧率3000 条时明显下降10000 条仍相对稳定节点回收不回收超出缓存范围自动回收事件模型全部节点可响应事件只有已实例化节点可响应事件当然RcList 也不是银弹。它要求开发者对“懒加载”有正确的认知尤其是事件这块很多从 List 迁过来的人碰到的问题本质都是把旧思维带到了新组件上。1.2 半年时间都花在了哪里这半年我大致分成了三个阶段。第一个阶段是接入期主要解决“怎么把 List 换成 RcList 并且跑起来”。这阶段其实不难RcList 的写法和 List 很像把List换成RcList包一层ListItem数据量用dataCount告诉组件基本就能跑。真正的难点在第二阶段也就是事件期。我开始在页面上加点击事件、滑动删除、滚动加载更多结果问题一个接一个点了 A 位置的数据拿到的却是 B 的索引滑动删除按钮偶发点不中快速滚动时整个列表直接不响应点击。这些问题让我意识到必须把 RcList 的事件处理机制彻底搞清楚不能靠试错。第三阶段才是沉淀期。我把组件的事件注册方式、触发时机、与其他手势的优先级全部整理成文档形成了一套“先看是否实例化再看事件类型最后考虑手势冲突”的排查方法。这套方法论后来我用在团队里其他几个页面上基本都能快速定位问题。这篇文章就是把这套方法论和具体案例结合起来的产物。2. 核心机制懒加载与节点复用是事件机制的地基理解 RcList 的事件机制之前必须先理解它的渲染机制。这两者强耦合不先讲清楚地基后面所有“为什么”都解释不通。2.1 数据懒加载与可视区渲染RcList 使用时要传入dataCount这个值代表数据源的总条数。组件拿到总数之后并不会真的创建这么多节点而是根据当前滚动位置只创建“视口内可见的 item 视口外少量缓存 item”。举个例子如果手机屏幕能完整显示 8 条 item你设置了cachedCount: 3那么 RcList 在初始状态下大约只会创建 11 个左右节点8 个在屏幕内上方和下方各预留几个具体策略与实现版本有关但思路一致。你往下滑的时候新的 item 进入缓存区才开始实例化同时滑出很远的节点会被回收。这套机制对应到事件上会产生一个非常重要的结论RcList 里的事件不是注册在“数据”上而是注册在“节点”上。一条数据如果没有对应的节点存在它是不可能接收到任何点击或触摸事件的。所以你在处理事件回调时永远要假设“当前存在的事件节点是有限的、动态变化的”任何基于“全部数据都存在”的写法都会出问题。2.2 节点复用与状态保持RcList 的节点回收机制并不复杂但“节点复用”的逻辑需要特别留意。当一条 item 滚出缓存区后它的节点会被销毁或回收之后另一条数据滚入可视区时可能会复用刚才那个节点。这种情况下组件里如果存在“跟数据无关的临时状态”比如某个 item 的展开/收起状态、某个输入框的未提交内容、某个开关的临时开合状态就很容易丢失或者串数据。我在实际开发中遇到过用户反馈“我把第 3 条消息展开了滚走再滚回来它变成收起了”这就是节点回收导致状态丢失的典型案例。处理办法也很简单直接一切 UI 状态都跟着数据走。不要在 item 组件内部用State保存业务状态而是把状态字段放进数据模型里通过数据绑定驱动 UI。比如“是否展开”就用isExpanded字段点击时修改这个字段通知组件刷新。这样即使节点被回收再重建重新读取数据时状态也还在。 注意这是 RcList 开发里最重要的一条原则没有之一。状态放数据里索引显式绑定这两点做到位后面大部分事件问题都不会发生。2.3 缓存参数对事件感知的影响cachedCount不只是一个性能调优参数它还直接影响“事件能感知的范围”。缓存区里的 item 虽然没有完全显示在屏幕上但它们已经实例化了是可以响应事件的。我在调这个参数时发现一个现象如果cachedCount设得很大比如 10那么快速滑动时事件响应的灵敏度会提高因为更多的节点已经提前准备好用户点下去时目标节点已经存在。但如果设得太大内存和节点创建的开销会上升反而可能拖慢首屏和滑动性能。我的经验是cachedCount设成“单屏可见 item 数量 2”左右比较合适。假设一屏稳显 8 条cachedCount设 10 到 12 就行。这个值不需要非常精确但别设成 3 以下否则快速滑动时容易出现空白占位节点还没来得及实例化滚动就过去了视觉上会有一种“提前量不足”的生硬感。3. 摸清 RcList 的事件处理机制RcList 的事件体系可以粗略分成三类滚动类事件、点击触摸类事件、以及组件自身的节点滚动监听框架。这三类事件的触发逻辑和适用场景都不同一个一个拆开讲。3.1 滚动类事件onScrollIndex 与 onScrollVisibleContentChange滚动类事件是 RcList 日常开发中最高频使用的主要就是两个onScrollIndex会在滚动时上报当前显示区域第一条和最后一条 item 的索引需要注意这里的索引是 RcList 内部的数据索引从 0 开始。它适合做“当前滚动到哪了”的粗粒度监听比如显示顶部返回按钮、记录阅读位置这类需求。onScrollVisibleContentChange更加强大它会在可视区内容发生变化时回调携带可视区 item 的具体信息比如起始索引、结束索引、当前可视区和总内容区的关系等。这个事件适合做精细化业务判断比如“有没有滚到底部”“当前可视区有没有包含某一条数据”。我实际项目里用这两个事件做了两件事一是用onScrollVisibleContentChange判断滚动位置接近底部时触发加载更多二是在阅读消息详情后返回列表时用scrollToIndex把列表精确地定位到之前阅读的位置。RcList({ dataCount: this.totalCount, cachedCount: 8, scrollOptions: { itemHeight: 100 } }) .onScrollIndex((start: number, end: number) { // 记录当前可视区首尾索引 this.currentStartIndex start; this.currentEndIndex end; }) .onScrollVisibleContentChange((visibleInfo) { // 判断是否接近底部比如结束索引接近总数 - 5 时触发加载 if (visibleInfo.endIndex this.totalCount - 5) { this.loadMore(); } })这里有个细节scrollOptions.itemHeight最好给上。RcList 需要知道 item 的高度才能精确计算滚动位置、滚动条长度以及目标索引对应的偏移量。如果 item 高度不固定至少给一个估算值或常见值否则快速滑动时的索引跳变会非常吓人事件回调里的索引和实际看到的内容对不上。3.2 点击触摸类事件onClick 与 onTouchListItem上可以直接挂onClick和onTouch。onClick是语义化的点击事件适合处理“用户点了一下某个 item”的场景。它的触发条件相对严格按下和抬起都要落在同一个节点上且没有发生明显的滑动位移。onTouch则更加底层按下、移动、抬起都会回调能拿到事件坐标、压力、时间戳等信息。我在做消息中心时onClick用来处理“点击消息跳转详情”onTouch用来实现自定义滑动删除逻辑。需要注意的是如果某个 item 上同时有onClick和onTouch触摸事件会先触发然后系统根据手势判断是否满足点击条件再决定是否触发onClick。如果你在onTouch里做了一些和滑动相关的操作比如推动一个按钮显现那么这次手势大概率不会被识别为“点击”onClick就不会触发这是符合预期的不用觉得奇怪。 提示判断该用哪个事件时先想清楚需求是“想感知手指的物理动作”还是“想感知一次语义化的点击”。前者用onTouch后者用onClick。混用时要明确它们不是互斥的而是有先后关系的。3.3 事件优先级与消费顺序RcList 本身是一个可滚动的容器它有自己处理手势的逻辑比如垂直滑动、惯性滚动。如果你在ListItem上挂的onTouch或者onClick与滚动手势发生冲突系统的处理原则是先让容器判断这是不是一个滚动手势如果是就交给滚动逻辑处理子节点的事件会被压制如果不是滚动才会分发给子节点。这个逻辑从体验上讲非常合理。用户快速上下滑动列表时手指下的 item 不应该把“滑动”误判成“点击”否则列表会变得很难用。但在实际开发中有些需求偏偏要在滑动过程中感知 item 的触摸比如“滑动经过时高亮”这种交互。这种场景下直接在ListItem上挂onTouch往往拿不到完整的移动轨迹因为一旦系统判定这是滚动触摸事件就会被容器接管。我的解决办法是在RcList外层包一个Stack或Column在上面挂onTouch通过事件坐标自行计算当前滑到了哪个 item。这个方案虽然要做坐标换算但能绕开容器对子节点事件的接管逻辑拿到完整的事件流。代价是计算量稍大需要自己做索引命中判断。具体要不要这么干得看产品需求的优先级。3.4 节点滚动监听框架 nodeScrollListenerRcList 还提供了一套挂在组件上的滚动监听框架可以用来注册滚动开始、滚动中、滚动停止等回调。从事件精细度来看它比onScrollIndex更接近底层适合做与滚动动画、位置精确计算相关的功能。我主要用它来做三个事情滚动停止后统一收起已经展开的滑动删除按钮滚动过程中实时更新某个悬浮组件的偏移位置做一个“跟随列表但又有自己固定逻辑”的复杂头部区分“用户手动滚动”和“代码调用滚动”避免在代码滚动的过程中误触发业务逻辑。private nodeScrollListener { onWillScroll: (event) { // 滚动即将开始可以在这里做拦截或记录 }, onDidScroll: (event) { // 滚动中持续回调可以拿到实时偏移量 }, onScrollStart: (event) { // 滚动开始 this.isScrolling true; }, onScrollStop: (event) { // 滚动停止收起所有展开的删除按钮 this.isScrolling false; this.closeAllSwipeButtons(); } }这套监听框架和onScrollIndex的使用场景有重叠但不冲突onScrollIndex更偏向“数据层面的可视区变化”监听框架更偏向“物理滚动层面的实时反馈”。4. 应用示例消息中心实战这一节我把前面讲到的机制串起来做一个完整的小项目示例。场景是“某跨平台系统”的消息中心有三类消息支持点击查看详情支持左滑出现删除按钮支持滚动到底部自动加载更多。4.1 场景定义与数据模型先定义消息数据模型。注意我前面说的原则状态跟着数据走所以数据模型里除了业务字段还需要放一些 UI 状态字段比如isDeleteMode这种。interface MessageItem { id: string; type: system | operation | interact; title: string; content: string; timestamp: number; isRead: boolean; isShowDelete: boolean; }isShowDelete控制当前 item 是否展示删除按钮。这个状态放在数据里才能保证节点回收重建后状态不丢失。然后准备数据源用一个普通数组维护初始给 50 条模拟数据。真实项目里这些数据通常来自接口但核心逻辑一致。4.2 基础页面与懒加载配置接下来是最核心的部分搭一个 RcList 页面配好数据、缓存、滚动事件。Entry Component struct MessageCenterPage { State messageList: MessageItem[] []; State totalCount: number 0; private pageIndex: number 1; private scroller: Scroller new Scroller(); aboutToAppear(): void { // 初始化数据 this.messageList this.generateMockData(0, 50); this.totalCount this.messageList.length; } private generateMockData(startIndex: number, count: number): MessageItem[] { let result: MessageItem[] []; for (let i 0; i count; i) { result.push({ id: msg_${startIndex i}, type: i % 3 0 ? system : (i % 3 1 ? operation : interact), title: 消息标题 ${startIndex i}, content: 这是一条模拟消息内容用于演示 RcList 组件的事件处理机制。, timestamp: Date.now() - i * 1000, isRead: false, isShowDelete: false }); } return result; } build() { Column() { RcList({ dataCount: this.totalCount, cachedCount: 10, scrollOptions: { itemHeight: 90 } }) { ForEach(this.messageList, (item: MessageItem) { ListItem() { MessageRow({ item: item, index: this.messageList.indexOf(item), onItemClick: (index: number) this.onMessageClick(index), onDeleteClick: (index: number) this.deleteMessage(index) }) } }, (item: MessageItem) item.id) } .onScrollIndex((start: number, end: number) { this.currentStart start; this.currentEnd end; }) .onScrollVisibleContentChange((visibleInfo) { // 可视区末尾接近数据末尾时加载更多 if (visibleInfo.endIndex this.totalCount - 5) { this.loadMoreData(); } }) } .width(100%) .height(100%) } private onMessageClick(index: number): void { // 点击消息标记已读并跳转详情 this.messageList[index].isRead true; // 模拟跳转逻辑 } private deleteMessage(index: number): void { // 删除指定索引的消息 this.messageList.splice(index, 1); this.totalCount this.messageList.length; } private loadMoreData(): void { if (this.isLoadingMore) return; this.isLoadingMore true; // 模拟异步加载 setTimeout(() { let newData this.generateMockData(this.messageList.length, 30); this.messageList this.messageList.concat(newData); this.totalCount this.messageList.length; this.isLoadingMore false; }, 500); } }这段代码看着简单里面有几个关键点要单独说一下。第一ForEach的第三个参数也就是 key 生成器我用了item.id。RcList 的节点复用和 diff 更新都依赖 key 的唯一性如果 key 不稳定比如用数组下标删除一条数据后后面所有 item 的索引都会变很容易出现 UI 错乱和事件错位。只用 id 作为 key 比较稳。第二onScrollVisibleContentChange里做加载更多判断时我用的是“结束索引距离总数还剩 5 条”的触发条件这是为了避免用户已经滚到底部时还要再多滑一下才能触发。触发后要用isLoadingMore做防重否则回调触发频率很高很容易发重复请求。第三dataCount必须和messageList.length保持同步。这个值告诉 RcList“一共有多少个 item 可以懒加载”如果新增了数据却不更新dataCount滚到底部也不会渲染新数据反过来如果删除数据后dataCount没减组件会认为后面还有数据滚动位置会非常诡异。4.3 子组件中的事件绑定列表项我拆成了子组件MessageRow这样方便组织点击、滑动删除等逻辑。子组件里接收item和index注意index一定要从父组件显式传过来不要在子组件内部通过遍历或计算去推。原因很简单RcList 懒加载时子组件实例和数据的对应关系是通过当前渲染周期决定的如果子组件内部再自行计算索引很容易跟数据源脱节。Component struct MessageRow { item: MessageItem; index: number; onItemClick: (index: number) void () {}; onDeleteClick: (index: number) void () {}; build() { Stack({ alignContent: Alignment.End }) { // 删除按钮层 Row() { Button(删除) .onClick(() { this.onDeleteClick(this.index); }) } .width(80) .height(100%) .justifyContent(FlexAlign.Center) .backgroundColor(#FF3B30) .visibility(this.item.isShowDelete ? Visibility.Visible : Visibility.Hidden) // 内容层 Row() { Column() { Text(this.item.title) .fontSize(16) .fontWeight(FontWeight.Medium) Text(this.item.content) .fontSize(14) .fontColor(#666666) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) Text(this.formatTime(this.item.timestamp)) .fontSize(12) .fontColor(#999999) } .width(100%) .height(90) .padding({ left: 16, right: 16 }) .backgroundColor(this.item.isRead ? #FFFFFF : #F0F7FF) .onClick(() { this.onItemClick(this.index); }) .onTouch((event: TouchEvent) { this.handleTouch(event, this.index); }) } .width(100%) .height(90) } private handleTouch(event: TouchEvent, index: number): void { // 简化逻辑根据触摸横向偏移判断是否展开删除按钮 if (event.type TouchType.Move) { // 记录横向位移超过阈值后设置 isShowDelete } else if (event.type TouchType.Up) { // 判断是否需要收起 } } private formatTime(timestamp: number): string { // 格式化时间显示 return new Date(timestamp).toLocaleTimeString(); } }这里我把删除按钮的显隐直接绑定到item.isShowDelete而不是用子组件自己的State这样节点被复用和回收时状态不会乱。还有一个值得注意的细节内容层的onTouch会在用户横向滑动时被触发但系统在判定为滚动后这个事件可能被容器接管导致handleTouch里的 Move 回调不全。如果要做真正流畅的滑动删除最好是结合手势系统比如用PanGesture绑定到内容层自行管理横向偏移。示例里我为了保持代码简洁用的是简化写法真实项目里建议用 Pan 手势接住横向动作避免和 RcList 的垂直滚动互相干扰。 注意PanGesture绑定到ListItem上时要先明确手势的方向限制。垂直滑动手势留给 RcList 做列表滚动横向滑动手势做删除交互两者通过方向区分能同时存在而不打架。4.4 滚动监听与加载更多加载更多的触发我在基础页面里已经写了这里补充一个细节onScrollVisibleContentChange的回调频率很高只要可视区有任何变化就会触发。如果你的加载更多逻辑里做了比较重的操作比如网络请求前的参数组装、列表排序、数据去重建议加一层防抖。我的做法是设置一个“是否正在加载”的标志位再加一个“上次触发时间”距离上次触发不足 1 秒就不再触发。private loadMoreData(): void { if (this.isLoadingMore) return; const now Date.now(); if (now - this.lastLoadTime 1000) return; this.lastLoadTime now; this.isLoadingMore true; // 模拟异步加载数据 }滚动事件里还有一个常见的坑如果在滚动回调里直接修改messageList比如重置数组、大量插入数据页面会立刻重新计算全部可视区 item容易造成滚动位置跳动。我的建议是滚动回调里尽量只做“标记”和“记录”真正的数据操作放到滚动停止事件里做比如在nodeScrollListener.onScrollStop里再执行加载逻辑。这样既不会打断用户的滚动节奏也减少了滚动过程的计算压力。5. 踩坑记录与排查思路半年下来踩过的坑不少这里按“症状→原因→解决方案”的格式整理成速查表再挑几个典型的展开讲。5.1 高频踩坑点击索引与预期不一致症状点击屏幕第 3 条消息拿到的索引却是 0 或 10跳转详情时打开的是完全不相干的数据。原因大概率是在子组件里用“数组下标”或者“遍历时序号”作为事件回调参数没有把真实的数据索引显式传进来。RcList 懒加载后节点和数据的对应关系是动态的某个节点可能先渲染了第 10 条数据随后被复用为第 20 条数据如果回调里的 index 写死或者从其他途径推算就会错。方案数据源数组是唯一的真相索引必须在渲染每条 item 时从父组件显式传入而且确保传入的索引和数据在数组中的实际位置一致。删除操作过多时还要小心索引漂移——建议每次都根据item.id去定位数据而不是依赖之前保存的索引。5.2 高频踩坑滚动后事件不响应症状页面稳定显示时点击正常但快速滚动后立刻点击前几次点击没反应或者要按两次才触发。原因滚动停止后RcList 还在进行节点的回收和重新实例化你点下去的位置可能刚好是一个刚被回收、还没来得及重建的节点区域。此时该处没有真实的 item 节点事件自然没有接收者。方案在onScrollStop之后设一个极短的延迟再恢复交互或者给cachedCount适当加大让更多节点提前准备。前者是产品层规避后者是机制层优化。我个人偏向后者一屏 8 条时cachedCount给 10 以上快速滑动后立刻点击的响应率会明显提高。5.3 高频踩坑回调刷屏导致帧率下降症状滚动过程中onScrollVisibleContentChange或nodeScrollListener.onDidScroll频繁触发回调里又做了数组遍历、样式修改等操作滑动开始变得卡顿。原因滚动事件回调天生高频如果回调里存在重操作比如每次都对全量数据进行过滤、排序或触发State变更就会在滚动过程中产生大量布局计算。方案明确区分“滚动中”和“滚动停止”能做的事。滚动中只允许记录偏移量、索引等轻量状态涉及数据操作、网络请求、复杂计算的逻辑一律放到onScrollStop或防抖函数里。如果某个状态必须在滚动中实时更新比如悬浮头部的位置可以考虑用无状态的方式直接改组件的偏移属性避免触发整棵树的重新渲染。5.4 高频踩坑删除多条后列表位置跳动症状连续删除几条消息后列表会突然跳到顶部或者滚动位置看起来“少了几个身位”。原因这是因为删除后dataCount更新了但滚动位置还在原来的偏移量上组件会基于新的数据总量重新计算可视区导致视觉上的跳动。方案删除时记录当前可视区第一条数据的 id数据更新后再用scrollToIndex定位到原来的位置。注意这里不能用索引定位因为删除后索引已经变了要用 id 先找出新的索引再滚动。5.5 高频踩坑item 高度不一致导致滚动位置漂移症状每条消息内容长短不一高度从 80 到 140 不等快速滑动时列表位置和滚动条位置对不上scrollToIndex定位不准。原因scrollOptions.itemHeight给了固定值但实际 item 高度并不固定估算值和真实值的偏差会在滚动距离上不断累积。方案要么给 item 一个固定高度内容超出后做截断保证每条 item 高度一致要么做高度动态测算在数据加载后逐个测量并缓存。后者的实现成本更高除非业务强制要求展示完整内容否则我更推荐统一高度加截断的方案性能和稳定性都好很多。6. 关于这套机制我的最后一点体会踩了无数个坑之后我最大的感受是RcList 的事件机制并不是设计得很复杂它只是把“节点可能存在也可能不存在”这个特性贯彻得非常彻底。所有的问题只要能记得“事件是挂在节点上的节点是动态回收的”基本都能找到合理的解法。一个很实用的习惯是在写任何ListItem内部的事件回调之前先问自己三个问题——这条数据有没有对应的真实节点如果节点被回收重建状态还在不在回调里拿到的索引是不是数据源中的真实索引三个问题全部过一遍大部分隐患在写代码阶段就能排除。如果你正准备接手一个 RcList 页面我给的建议是不要一上来就抄网上零散的用法先花半小时把组件的渲染模型和事件模型在脑子里过一遍再开始动手。RcList 的学习成本比 List 高但一旦把它的事件处理逻辑理顺后面做长列表性能优化、复杂交互扩展会顺手很多。我现在这个消息中心页面跑了半年稳定性和流畅度都符合预期这套方法论也应用到了团队里其他几个长列表场景整体反馈还不错。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑