搞了快一个月的React Native for OpenHarmony,我最大的感受是:列表组件这块的坑,比想象中多,也比想象中值得填。尤其SectionList,看起来不就是“有分组头的FlatList”嘛,但真放到OpenHarmony设备上跑,从数据组织到渲染性能,再到各种环境因素,每一步都可能让页面直接“躺平”。
这篇文章就把我这段时间在RNOH(React Native for OpenHarmony)里落地SectionList的完整过程写出来。不止是API怎么调,还包括为什么这么设计数据结构、RK3568设备树怎么选才不会踩坑、启动白屏到底怎么排查、以及列表超过几百条数据之后怎么做性能兜底。如果你正准备在OpenHarmony设备上做React Native开发,或者正被分组列表卡得难受,这篇文章应该能帮你省下不少调试时间。
1. 看清RNOH的定位:为什么分组列表非它不可
1.1 RNOH解决的并不是“换个平台跑React Native”这么简单
OpenHarmony生态发展很快,但开发者的应用层技术栈还没完全统一。ArkUI是官方主推的声明式UI框架,如果你只做OpenHarmony一个平台,直接上ArkUI当然最稳。但现实情况是,很多团队手上已经有一套成熟的React Native代码,或者同时要覆盖Android、iOS、OpenHarmony多个端,这时候RNOH的价值就出来了——它让React Native的JS业务层和组件模型跑在OpenHarmony上,底层桥接到ArkUI的渲染能力上。
换句话说,同样的SectionList代码,在Android上渲染原生的RecyclerView系列组件,在OpenHarmony上则映射到ArkUI的滚动列表机制。这对业务团队来说意味着:一套数据模型、一套组件结构、一套交互逻辑,三端复用。
但这里的“复用”是有代价的。因为RNOH的底层渲染链路与原生RN不完全一致,列表组件在性能表现、初始化时序、甚至事件响应上都有细微差别。这在简单页面上几乎感觉不到,一旦SectionList里的条目多了、分组头要吸顶、图片占位复杂了,差异就会被放大。
1.2 SectionList和FlatList的边界:什么时候必须用分组
不少新手纠结SectionList和FlatList的区别,我的判断标准很简单:如果你的渲染结果需要“章节感”——文字标题压在条目上方、多个数据块需要逻辑隔离、甚至标题要滚动吸顶——就直接用SectionList,不要用FlatList硬拼。
FlatList本质上是一个平铺的虚拟列表,它的数据源是一个一维数组,渲染结果没有层级关系。想在平铺列表里做出“分组”效果,你可以把组头当成普通item来渲染,但这样会带来两个问题:
- 组头和条目没有结构上的归属关系,拿不到精确的分组边界做吸顶;
- 数据变更时,某条item刷新可能引起组头重新渲染,状态维护变复杂。
SectionList的数据源是sections数组,每个section自带title和data,运行时它帮你在每个分组的头部调用renderSectionHeader,并且组件库层面就知道哪些item属于哪个分组。这不仅是“省事”,更是后续做吸顶、索引跳转、分组统计的前提。
在我这个管理端项目里,三个核心页面全部依赖SectionList:按部门分组的人员名册、按日期排列的任务流、带字母索引的通讯录。这三种布局用FlatList做会很别扭,用SectionList就是默认解法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备阶段最容易翻车的两个点:白屏排查与RK3568设备树选型
2.1 启动白屏:不一定是SectionList的问题,但会挡在SectionList前面
先提醒一句:如果你在RNOH设备上跑React Native应用,启动时出现白屏,先不要急着怀疑列表组件。我遇到的情况是,应用冷启动后,页面空白好几秒,然后才突然渲染出来。在PC模拟器上这个现象不明显,但在RK3568开发板上非常明显。
排查链路是这样的:
- 先看bundle加载时序。RNOH应用在启动时需要加载JS bundle,这个过程如果放在主线程,就会阻塞首次渲染。检查工程里有没有把
loadBundle放在合适的生命周期节点。 - 再看容器初始化。在OpenHarmony侧,RNOH的容器组件需要完成ArkTS运行时的初始化,再与JS侧建立通信。如果设备性能一般,这个初始化可能持续两三秒。
- 最后才看页面本身有没有问题。把根组件暂时替换成一个纯
Text节点,如果Text也要白屏好几秒,那就是环境层的问题;如果Text秒开,只是SectionList页面白屏,那才需要往列表数据量过大、渲染阻塞的方向排查。
我用了一个很土但有效的方案:在原生侧加一张闪屏图,等JS bundle加载完成、RootView挂载之后再做画面切换。这样用户感知不到白屏,只看到一个正常启动过程。React Native社区里讨论过的“启动白屏”问题,在RNOH上同样存在,而且因为OpenHarmony设备型号杂、性能差异大,更容易触发。
我的建议是:RNOH项目一定要预留一个原生启动页,不要指望JS层能帮你盖住初始化这段时间。
2.2 RK3568设备树不是越多越好:选错直接外设失灵
另一个很多人在社区里问的问题:“OpenHarmony的RK3568有那么多设备树,到底咋选?”这问题我也问过自己。第一次拿到开发板,刷完系统,烧了自己的应用,结果发现触摸屏没反应、网络连不上,第一反应还以为是应用代码的问题。
后来才明白,问题出在设备树(DTS)选择上。
RK3568是个通用的应用处理器平台,芯片能力很强,但它的外围硬件(屏幕、触摸、网口、传感器)是由开发板厂商各自设计的。OpenHarmony发布的RK3568相关版本,经常带了好几套设备树配置,对应不同厂家、不同版本的板子。
选择逻辑并不复杂,核心就三点:
- 确认开发板的具体型号和硬件版本号,直接找对应厂家提供的OpenHarmony适配说明;
- 如果厂家没给,对比几个设备树文件里
lcd、touch、gmac(千兆网)相关的节点配置,和手里板子的硬件去对应; - 实在分辨不清,就先选EVB默认配置跑起来,只要能点亮屏幕,再逐个修外围设备的device tree overlay。
我踩的坑是:当初图省事,选了一个“看起来最接近”的设备树,结果系统起来了,但触摸屏完全失灵。最后对着硬件原理图和设备树源码一个个核对,才发现屏的I2C地址和设备树里的节点对不上,改完touchscreen节点的地址值,重启才正常。
这个环节和SectionList有什么关系?关系很大。RNOH应用跑在OpenHarmony上,如果设备的基础外设没被正确拉起来,你连开发调试都做不了。尤其触摸屏异常会让人误以为是JS层的触摸事件处理出了问题,白白浪费验证时间。所以做RNOH开发,第一步不是写组件,是先确认设备树选对、外设全部正常。
3. SectionList完整实现:从数据模型到分组渲染的每一步
3.1 分组数据模型怎么设计,直接影响渲染效率和复用度
说回SectionList本身。它的数据源是sections,一个数组,每个元素长这样:
ts复制interface ISectionData<T> {
title: string;
data: T[];
key?: string;
extraData?: any;
}
title就是分组标题,data是该分组下的条目数组。看起来简单,但实际项目中,把后端接口返回的平铺数组转成sections结构,是很有讲究的。
我推荐遵循一条原则:sections的生成逻辑与渲染组件解耦,写成一个纯函数,放在独立的数据处理模块里。 原因有两个:
- 页面需要根据不同的筛选条件重新分组,比如同一个任务数组,按日期分组和按负责人分组是两种sections结构;
- 分组逻辑容易写单元测试,输入原始数据、输出sections,纯函数最好测。
我在项目里封装了一个通用转换方法:
ts复制export function buildSections<T>(
list: T[],
groupKeyExtractor: (item: T) => string,
titleFormatter: (key: string) => string
): ISectionData<T>[] {
const groupMap = new Map<string, T[]>();
list.forEach((item) => {
const key = groupKeyExtractor(item);
const arr = groupMap.get(key) ?? [];
arr.push(item);
groupMap.set(key, arr);
});
return Array.from(groupMap.entries()).map(([key, data]) => ({
title: titleFormatter(key),
data,
key,
}));
}
调用起来很直观:
ts复制const sections = buildSections(
taskList,
(task) => task.deadline.split('T')[0],
(dateStr) => `截止日期:${dateStr}`
);
这个函数写完后,后面加团队列表、加联系人分组,全部复用,只是换一下groupKeyExtractor和titleFormatter。
3.2 基础渲染代码:SectionList的最小可用版本
渲染端我用了一个相对完整的示例,包含了SectionList最常用的配置项:
tsx复制import React, { useCallback, useMemo } from 'react';
import { SectionList, View, Text, StyleSheet } from 'react-native';
import type { ISectionData } from './types';
interface TaskItem {
id: string;
name: string;
owner: string;
deadline: string;
}
interface TaskSectionListProps {
tasks: TaskItem[];
}
const TaskSectionList: React.FC<TaskSectionListProps> = ({ tasks }) => {
const sections = useMemo<TaskSectionListProps['tasks'] extends never ? never : ISectionData<TaskItem>[]>(
() => buildSections(tasks, (t) => t.deadline.slice(0, 10), (d) => d),
[tasks]
);
const renderSectionHeader = useCallback(
({ section }: { section: ISectionData<TaskItem> }) => (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{section.title}</Text>
<Text style={styles.sectionCount}>{section.data.length} 项</Text>
</View>
),
[]
);
const renderItem = useCallback(
({ item }: { item: TaskItem }) => (
<View style={styles.itemContainer}>
<Text style={styles.itemName}>{item.name}</Text>
<Text style={styles.itemMeta}>{item.owner} · {item.deadline.slice(11)}</Text>
</View>
),
[]
);
return (
<SectionList
sections={sections}
keyExtractor={(item, index) => item.id + '_' + index}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
stickySectionHeadersEnabled
contentContainerStyle={styles.contentContainer}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
);
};
const styles = StyleSheet.create({
contentContainer: { paddingHorizontal: 16, paddingBottom: 32 },
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#f5f7fa',
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 6,
marginTop: 16,
},
sectionTitle: { fontSize: 16, fontWeight: '700', color: '#1f2d3d' },
sectionCount: { fontSize: 13, color: '#8492a6' },
itemContainer: {
backgroundColor: '#ffffff',
paddingVertical: 14,
paddingHorizontal: 12,
marginTop: 8,
borderRadius: 8,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#e0e6ed',
},
itemName: { fontSize: 15, color: '#1f2d3d', marginBottom: 4 },
itemMeta: { fontSize: 12, color: '#8492a6' },
separator: { height: 1, backgroundColor: 'transparent' },
});
这里面有几个细节值得单独说明:
stickySectionHeadersEnabled 是分组头吸顶开关。在RNOH上实测可用,但反复滚动大量分组时,吸顶头渲染偶尔会有轻微抖动,后面我会专门讲这个坑。
keyExtractor 我用了item.id + '_' + index。正常情况下用item.id就够了,但如果同一个列表里可能出现相同ID的条目(比如不同分组下有相同数据),拼接index可以避免key冲突导致RN告警。
useCallback和useMemo 不是炫技,SectionList的renderSectionHeader和renderItem如果每次父组件刷新都生成新函数引用,子组件重新渲染的概率会显著升高,在低配设备上列表滚动就能感觉到卡。
3.3 分组头吸顶的实现逻辑和样式要点
吸顶效果本质上是让当前滚动位置对应的分组头固定在列表视口顶部。SectionList内置了这个能力,但需要满足两个前提:
stickySectionHeadersEnabled设为true;- 分组头在样式上要有不透明的背景色,否则吸顶后下面的条目会透出来,视觉上糊在一起。
在RNOH上,分组头吸顶的映射逻辑依赖底层滚动容器的事件回调。实测下来,条目数量在500以内,吸顶表现稳定;超过1000条,吸顶头跟随滚动的流畅度会下降。这时候就要考虑是不是该减少单页渲染数据,或者用更轻量的自定义吸顶方案。
如果你需要完全控制吸顶头的行为(比如吸顶后改变样式、加个阴影),可以监听onViewableItemsChanged事件,拿到当前视口内的viewableItems,找出其中isSticky的section的索引,然后单独渲染一个悬浮头。但这个方案很重,等默认吸顶满足不了需求再考虑也不迟。
4. 在RK3568上压测1000+条目的性能调优记录
4.1 卡顿从哪来:VirtualizedList的渲染机制在低配设备上的放大效应
SectionList底层依赖VirtualizedList,它默认不会一次性渲染所有条目,而是根据视口高度、滚动方向、预加载阈值,分批渲染可见区域附近的item。这个机制在手机上问题不大,但RK3568这类开发板的CPU和GPU性能比旗舰手机弱不少,同一时刻渲染的item数量一旦超过某个阈值,掉帧就非常明显。
我在压测时构造了1500条任务数据,分成20个分组,每个分组75条。初始配置下,滚动列表时帧率明显下降,分组头快速滑过屏幕时甚至能感觉到“粘滞感”。
排查后结论是:RK3568上SectionList的性能瓶颈主要集中在三个地方:
- 单屏渲染item数量过多;
- 每个item内部的View层级过深(不必要的嵌套);
- 分组头每次都重新计算布局,触发布局抖动。
4.2 优化一:用getItemLayout干掉动态测量
列表组件在不确定item高度的时候,需要动态测量每一项的尺寸,这很消耗性能。如果列表里的每个item高度是固定的,或者可以通过某种公式计算出来,就一定要提供getItemLayout。
计算逻辑很简单:把分组头和分组条目的高度分别记为常量,按顺序累加:
ts复制const SECTION_HEADER_HEIGHT = 40;
const ITEM_HEIGHT = 70;
const SEPARATOR_HEIGHT = 1;
const getItemLayout = (data: Array<ISectionData<TaskItem>> | null, index: number) => {
let offset = 0;
for (let i = 0; i < index; i++) {
const section = data?.[i];
if (section) {
offset += SECTION_HEADER_HEIGHT + section.data.length * (ITEM_HEIGHT + SEPARATOR_HEIGHT) + 8;
}
}
offset += SECTION_HEADER_HEIGHT;
return { length: ITEM_HEIGHT + SEPARATOR_HEIGHT, offset, index };
};
注意,这个data参数指的是扁平化后的section和item序列,而不是sections数组本身。SectionList内部对每个分组和条目统一编号,所以计算offset时要按顺序累加每个section的头部高度、条目高度、分隔线高度。
实测效果:加了getItemLayout之后,1500条数据的滚动流畅度提升了非常明显,基本恢复到接近原生列表的顺滑程度。
4.3 优化二:React.memo包裹item组件,减少无效渲染
SectionList的renderItem只负责当前应该渲染的items,但父组件sections引用变化时会触发所有可见item重新渲染。用React.memo包裹item组件后,只要item的数据引用和renderItem的回调引用不变,就不会重复执行渲染函数体的内容。
tsx复制const TaskItemView = React.memo(({ item }: { item: TaskItem }) => {
return (
<View style={styles.itemContainer}>
<Text style={styles.itemName}>{item.name}</Text>
<Text style={styles.itemMeta}>{item.owner} · {item.deadline.slice(11)}</Text>
</View>
);
});
然后renderItem里直接返回这个组件:
tsx复制const renderItem = useCallback(({ item }: { item: TaskItem }) => {
return <TaskItemView item={item} />;
}, []);
这里有个容易被忽略的点:memo只在props浅比较相等时生效,如果每次传的item对象是重新创建的(比如在buildSections时每次都生成新的item引用),memo就会失效。所以buildSections在做分组时尽量不要改变原始item对象的引用,只做归类,不做克隆。
4.4 优化三:控制预渲染数量和窗口大小
VirtualizedList提供了一些可以微调的参数,在RK3568上我把它们调成了这样:
tsx复制<SectionList
...
initialNumToRender={12}
maxToRenderPerBatch={10}
windowSize={11}
updateCellsBatchingPeriod={50}
removeClippedSubviews
/>
这几个参数的含义和调试思路:
initialNumToRender:首屏渲染多少个item。默认是10,我是从12开始调的。如果首屏item过少会看到空白区域,过多则首屏时间变长。maxToRenderPerBatch:每批最多渲染多少个item。调小一些可以避免一次性渲染大量item造成的卡顿峰值。windowSize:以视口高度为基准,控制渲染区域的范围。默认值是21,即视口上下各渲染10个视口高度的内容。我调成11,缩小渲染范围能降低内存占用,但快速滚动时容易出现短暂白屏。removeClippedSubviews:对滚出视口的子View做裁剪/移出,减少overdraw。在Android上经常有坑,在RK3568上实测是正收益。
这些参数不是越极端越好,需要根据实际设备压测来定。 我最后保留了maxToRenderPerBatch={10}和windowSize={11},综合表现最好。
4.5 测出来的数据:RK3568上的前后对比
简单贴一下我实测的数据(开发板用的是RK3568 2GB版本,OpenHarmony 4.0 Release):
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首屏渲染完成时间(1500条) | 约1.2秒 | 约0.9秒 |
| 快速滚动帧率 | 人眼可感知卡顿 | 基本流畅 |
| 内存占用(列表驻留时) | 峰值约480MB | 峰值约390MB |
| 吸顶头跟随滚动抖动 | 偶发 | 未复现 |
这个数据当然随设备、系统版本、item复杂度浮动,但能说明一个道理:SectionList在RNOH上完全够用,但必须做针对性的性能适配。
5. 复盘几个让我印象深刻的坑:从分组头闪烁到串组问题
5.1 分组头闪烁和吸顶错位:stickySectionHeadersEnabled的副作用
一开始打开吸顶后,滚动列表,分组头在吸顶和回归原位的一瞬间会出现轻微闪烁。在RK3568上尤其明显。排查后发现,闪烁主要是分组头背景色带半透明效果导致的,半透明背景在吸顶状态切换时,和下方内容混色产生视觉闪烁。
解决办法很简单:把分组头的背景色改为完全不透明,并加一条底部边框线。另外我给分组头设置了固定的高度SECTION_HEADER_HEIGHT = 40,配合getItemLayout里的计算值,也减轻了吸顶状态的布局抖动。
如果闪烁还复现,可以检查是否给分组头设置了borderRadius这类会触发离屏渲染的属性。在低端设备上,离屏渲染的代价比想象中大。
5.2 sections更新后出现“串组”数据错乱
这是一个数据引用层面的经典坑。我在某个筛选功能里,用setTasks(newTaskArray)触发列表刷新。第一次筛选没问题,第二次筛选后,出现了A分组的标题下混进B分组条目的情况。
定位过程花了一些时间,最终发现是buildSections的groupMap做分组时,虽然每次返回了新的sections数组,但内部的data数组里,item对象还是同一个引用。而renderItem在VirtualizedList内部按index复用cell,如果新旧sections结构差异大,而keyExtractor处理不当,就会导致复用的cell显示旧数据。
解决方式有两层:
- 在
keyExtractor里加上能够体现数据唯一性的字段,确保数据变化后key也跟着变; - 在
extraData属性上传入一个会随刷新变化的版本号:
tsx复制<SectionList
sections={sections}
extraData={{ refreshVersion: refreshVersion }}
...
/>
sections引用变化时SectionList一般会重新渲染,但加上extraData能强制触发一次完整的刷新,把一些VirtualizedList内部复用出来的脏状态清洗掉。
5.3 不要在renderSectionHeader里做耗时计算
有人会把分组里条目的统计逻辑直接写在renderSectionHeader里,比如:
tsx复制const renderSectionHeader = useCallback(({ section }) => {
const total = section.data.reduce((sum, item) => sum + item.score, 0);
return <Text>总分:{total}</Text>;
}, []);
这个reduce在分组头每次渲染时都会执行一遍。分组一多,滚动时重复计算量就上来了。正确做法是在构建sections阶段就把需要展示的统计数据算好,放进section对象的某个属性里,渲染时直接取。
我后来给ISectionData接口加了一个meta字段,专门存放汇总数据:
ts复制interface ISectionData<T> {
title: string;
data: T[];
meta?: Record<string, number | string>;
}
buildSections里同步算出meta,渲染函数里只管读。
5.4 SectionList和FlatList在RNOH上的取舍再补充
之前提到的判断标准,在RNOH上依然适用,但有一个额外细节:如果你在同一个页面上同时用多个FlatList/SectionList,注意检查它们是否共享滚动容器。RNOH对嵌套滚动容器的支持没有原生RN那么成熟,同一个方向上的嵌套滚动很容易出现事件被吞或滚动不流畅的问题。
如果需要实现“外层纵向滚动+内层横向平铺列表”这种结构,内层用FlatList的horizontal模式没问题;但如果内层也是纵向列表,就要考虑换结构,比如用单层SectionList配合不同的单元格类型来做。
5.5 网络热词里“react native 如何实现循环滚轮”给到我的启示
社区里有人问React Native的循环滚轮怎么做,这其实也和列表组件的选型相关。循环滚轮(比如日期选择器里的滚动选择)本质上是一个特殊的列表:可见区域只展示几个item,但数据是可以无限循环的。
用SectionList做不了循环滚轮,因为分组结构不适合循环。但如果你用FlatList做,需要在数据源层面模拟无限循环:把数据数组拼接成多份,初始滚动位置设到中间某份的开头,滚动到末尾时通过scrollToIndex瞬间跳回中间位置。在RNOH上,这个方案的滚动流畅度取决于底层ScrollView的滚动监听频率,实测是可行的。
这个例子说明,RNOH生态里,React Native的组件模型大部分都能跑通,但每个组件在OpenHarmony上的适配程度不一,拿不准的时候就先用最简单的场景压一遍再说。
6. 把SectionList再往前推一步:改造适合自己业务的通用分组组件
6.1 一个带索引栏的通讯录分组页要怎么做
项目里最后做的是带字母索引的通讯录页面。SectionList配合右侧自定义索引条,是很常见的组合。
索引条本身是个绝对定位的View,纵向排列A-Z字母。点击某个字母时,用ScrollView的scrollToLocation方法跳转到对应分组:
tsx复制const sectionListRef = useRef<SectionList>(null);
const scrollToSection = (sectionIndex: number) => {
sectionListRef.current?.scrollToLocation({
sectionIndex,
itemIndex: 0,
viewPosition: 0,
animated: false,
});
};
这里有两个注意点:
scrollToLocation要求传入sectionIndex和itemIndex,数据量大的时候,这个方法的精度依赖getItemLayout提供的高度信息。如果高度计算不准,跳转位置会偏移。- 点击索引时,如果当前section过多,连续点击同一个字母,要加节流,否则容易出现跳转事件积压。
6.2 空分组要不要展示
默认情况下,SectionList不会渲染data为空的section。这是合理的行为,否则页面上会出现一堆没内容的空头。但有一种情况需要注意:当某个分组的数据为空,而你希望用户能看到“这里本来应该有什么”,就需要在构建sections时给空数据项塞一个占位item。
比如任务列表按日期分组,某一天没有任务,想显示“暂无任务”,可以在buildSections里给空数组追加一个标记item,渲染时根据类型显示不同的占位组件。我用的方案是给item加一个isPlaceholder字段,renderItem里判断后渲染占位视图。
6.3 封装通用分组组件的最后一步:把renderItem变成可注入的
当一个项目的列表页变多后,不同的页面只是item UI不同、分组字段不同、点击事件不同,其余的滚动逻辑、性能参数、吸顶设置几乎一样。这时候就该抽一个通用分组列表组件了:
tsx复制interface CommonSectionListProps<T> {
items: T[];
groupKeyExtractor: (item: T) => string;
titleFormatter: (key: string) => string;
renderItem: (info: { item: T; index: number }) => React.ReactElement | null;
buildMeta?: (groupKey: string, items: T[]) => Record<string, number | string>;
}
这个组件内部统一封装buildSections、getItemLayout、性能参数、吸顶、extraData刷新等逻辑。业务页面只需要传数据源和自定义renderItem。
个人体会是,通用组件不要在第一次写列表时就急着抽象,等两三个页面真的出现重复逻辑后,再抽不迟。 过早抽象会因为还没踩完所有坑,抽出来的组件反而处处要打补丁。
7. 一些关于OpenHarmony列表开发的想法
写作这篇实战记录时,我回头看了一眼最近社区里的问题趋势:“react native 启动白屏”“openharmony的rk3568有许多设备树到底咋选”“电脑版x86 openharmony”……各种问题背后都是一个共同信号:越来越多人开始认真评估RNOH作为OpenHarmony应用开发的选项。而列表组件作为业务页面的地基,它的稳定性直接决定一个应用能不能交付。
我自己用下来的结论是:SectionList在RNOH上是个成熟可用的组件,但它不是装上就能跑满性能。 环境层面的白屏、设备层面的RK3568设备树选择、组件层面的数据组织与渲染函数引用管理,每一层都值得花时间打磨。
文章里面我给的构建函数、getItemLayout计算方式、性能参数配置,都是我直接抄进工程里跑过的代码,不是“理论上应该可以”的写法。你拿去用的时候,建议先跑一个最小复现,再看自己的数据规模需要调到什么程度。如果遇到我文章里没覆盖到的新坑,欢迎在评论里把现象和数据规模贴出来,咱们一起把RNOH的坑位图补全。
