本文记录把 react-native-timeline-flatlist(时间线列表)适配到 HarmonyOS 的完整过程。

它做的事很专一:给一组数据,渲染成"左中右三列"的时间线 —— 左边时间、中间圆点+竖线、右边标题与描述;并且三个区块都可以用 props 换成你自己的组件。

结论:这是迄今最"干净"的一个库 —— 零第三方依赖、零原生 peer、零 isRequired、老写法九项全空、无动画。
它在鸿蒙上工作正常,而且判据可以做得非常具体(13 个 defaultProps 常量全部精确核对、三列文本全部可数)。

★★★★★ 一、为什么说它"最干净":四个零

维度结果
dependencies✅ 空
peerDependencies✅ 只有 react >= 17.0.0 与 react-native >= 0.64.0 ⇒ 无原生 peer
第三方 import✅ 只有 react 与 react-native ⇒ 连 prop-types 都没用
isRequired✅✅ 一处都没有 ⇒ 不存在"必填 prop 无默认值"的陷阱
propTypes✅ 完全没有(实测 Timeline.propTypes === undefined)
老写法九项✅✅ 全部为空
Platform.OS / Platform.select✅ 全空(只有 I18nManager.isRTL 决定左右布局,实测 isRTL = false)
measure() / NativeModules✅ 全空(量宽高用 onLayout,2 处)
useNativeDriver / Animated✅ 全空(这个库根本不做动画)

⇒ ⇒ 它唯一的依赖就是 RN 本身 ⇒ 不用接 HAR、不用补装依赖、不用打桩 ✓✓
(这也是它一次构建就成功、一次崩溃都没有的原因)

★★★★★ 二、13 个 defaultProps 常量,逐个精确核对

Timeline.defaultProps 一共 13 项,而且全是可断言的常量(这就是"契约型库"最舒服的地方):

Timeline.defaultProps = {
  circleSize: 16,                 lineWidth: 2,
  circleColor: '#007AFF',         lineStyle: 'solid',      lineColor: '#007AFF',
  innerCircle: 'none',            columnFormat: 'single-column-left',
  separator: false,               showTime: true,          isAllowFontScaling: true,
  isUsingFlatlist: true,          columnSideMargin: 20,    columnSidePadding: 20,
};

实测(全部 PASS):

NOTE | defaultProps 的键数 = 13
NOTE | defaultProps 的键 = circleColor, circleSize, columnFormat, columnSideMargin,
       columnSidePadding, innerCircle, isAllowFontScaling, isUsingFlatlist,
       lineColor, lineStyle, lineWidth, separator, showTime
PASS | ★★ defaultProps 恰好 13 项
PASS | ★★ circleSize = 16            PASS | ★★ circleColor = #007AFF
PASS | ★ lineWidth = 2               PASS | ★ lineStyle = solid
PASS | ★ lineColor = #007AFF         PASS | ★ innerCircle = none
PASS | ★★ columnFormat = single-column-left
PASS | ★ separator = false           PASS | ★★ showTime = true
PASS | ★ isAllowFontScaling = true   PASS | ★★ isUsingFlatlist = true
PASS | ★ columnSideMargin = 20       PASS | ★ columnSidePadding = 20
PASS | ★★★ 本库没有 propTypes(isRequired 零处)
PASS | ★ 有 prototype.render(是 class 组件)

⇒ 13 项一个不差 ✓✓ —— 这种"常量全集"式的契约,最适合做成回归测试(换成别的版本时,一条就不能过)

★★★★★ 三、三列文本都能数:time / title / description

源码里三列各有独立的 Text:

_renderTime(rowData)   → <Text style={[styles.time, timeStyle]}>{rowData.time}</Text>
_renderDetail(rowData) → <Text style={[styles.title, titleStyle]}>{rowData.title}</Text>
                       + <Text style={[styles.description, …]}>{rowData.description}</Text>

实测(3 行数据 ⇒ 每种 3 个):

time 文本 = 3 title 文本 = 3 description 文本 = 3

⇒ 三类文本全部命中、数目与行数一致 ✓✓(这种"每行三件套"的库,文本级判据特别好做)

★★★★★ 四、showTime 的 A/B 对照 —— 以及一个"采样太早"的教训

源码里 _renderTime 有一个最干脆的短路:

_renderTime(rowData, rowID) {
  if (!this.props.showTime) {
    return null;          // ★ 无条件返回 null
  }
  …
}

所以 showTime={false} 时 时间列应该整列消失。实测:

@12s(P1 刚切换到 showTime=false,FlatList 还没重渲染完)time 文本 = 3    ← 旧状态
@15s(P1:showTime=false)                              time 文本 = 0    ← ★★ 消失了

⇒ A/B 对照成立 ✓✓ 时间列确实整列消失

⚠️ 这里有个我自己踩的坑:第一次采样在 prop 刚切换后 ~1 秒内,看到的是旧值(3 个),
差点得出"showTime 不生效"的错误结论。
教训:受控 prop 变化后,要留足"一帧以上的重渲染时间"再采样(FlatList 的行更新还要更慢一点)。
稳妥做法:在同一相位窗口内多次采样,看趋势,而不是只采一次。

★★★★ 五、圆点几何:circleSize 16 ⇒ 实测 48×48 px

✓ 找到 48×48 的方块 x=227 y=1126 (连续 3 个,对应 3 行)

⇒ circleSize 默认 16(vp)× density 3 = 48 px ✓✓ 精确命中
⇒ 而且找到 3 个 ⇒ 与 3 行数据一致 ✓

★★★ 六、两种列表实现与它带来的一条标准警告

这个库用 isUsingFlatlist 决定内部走 FlatList 还是 ScrollView(默认 true 走 FlatList)。

我的测试页把 Timeline 放在一个外层 ScrollView 里,于是启动时出现了一条标准 RN 警告:

"VirtualizedLists should never be nested inside plain ScrollViews with the same orientation
 because it can break windowing and other functionality - use another VirtualizedList-backed container instead"

⇒ ⇒ 这不是库的问题,也不是鸿蒙的问题 —— 是**"FlatList 套在 ScrollView 里"这个通用结构问题** ✓
⇒ 正确做法:时间线页面的外层不要再包 ScrollView;或者给 Timeline 传 isUsingFlatlist={false} ✓
⇒ 实测这条警告随后消失(滚动后不再出现)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

★★★ 七、可替换的三块:renderTime / renderDetail / renderCircle

源码里这三行很关键:

this.renderTime   = (this.props.renderTime   ? this.props.renderTime   : this._renderTime).bind(this);
this.renderDetail = (this.props.renderDetail ? this.props.renderDetail : this._renderDetail).bind(this);
this.renderCircle = (this.props.renderCircle ? this.props.renderCircle : this._renderCircle).bind(this);

⇒ 三块都能被你自己的组件接管 ✓ 这意味着:

  • 想让某行描述里放图片/按钮/富文本 ⇒ 传 renderDetail;
  • 想自定义圆点(图标、头像、不同颜色)⇒ 传 renderCircle;
  • 想改时间列格式(如 "10:00" → "上午十点")⇒ 传 renderTime。

⚠️ 本次没能在 dump 里观察到自定义 renderDetail 的输出(我的第二个实例排在屏幕下方,且外层滚动被内部列表吃掉了手势)⇒
"自定义渲染器确实生效"这一条本轮未验证,如实标注(见边界)。

★★ 八、其余结论

默认导出:export default Timeline ⇒ 导入不用花括号 ✓

columnFormat 四种取值(源码里 4 处 switch 都基于它):single-column-left / single-column-right / two-column / 以及默认分支;
默认 single-column-left ⇒ 时间在左、圆点居中偏左、详情在右 ✓

行级覆盖:_renderEvent 里用 rowData.lineWidth ?? this.props.lineWidth 这类写法 ⇒
每行数据可以单独覆盖线宽、线型、边距、颜色 ✓(?? 而非 || ⇒ 0 也生效 ✓ 写法规范)

最后一行不画线:isLast 时 lineColor 被强制成 'rgba(0,0,0,0)'(透明)✓ 这是设计上的收尾处理 ✓


九、接入与预检(要点)

# 装库即可 —— 它没有任何 dependencies,也没有原生 peer
npm install "git+https://atomgit.com/oh-react-native/react-native-timeline-flatlist.git#1.0.1-ohos-1.0.0" --ignore-scripts

十、交付包质量与基线说明

类别结果
交付包内容lib/index.js(15295 B)+ src/index.tsx(17465 B)+ lib/index.d.ts ⇒ 源码与类型都在 ✓(12 文件)
spec.json⚠️ 缺 upstreamCommit
validation⚠️ "build-pass-device-pending"
⚠️ 声明的基线reactNative: "0.88.0" + rnoh: "0.84.4" ⇒ 两字段自相矛盾
⚠️ 上游活跃度pushed: 2026-07-18(近期还有更新)✓
备注实现零改动(本轮未修改库任何文件)

关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
这个库无已移除写法、无平台分支、无原生依赖 ⇒ 结论跨基线成立 ✓


十一、已知限制与本次验证边界

11.1 用这个库要知道的

事项说明
★★★★★ 四个零:零依赖 / 零原生 peer / 零 isRequired / 零 propTypes不用接 HAR、不用补装、不用打桩
★★★★★ defaultProps 13 项全是常量非常适合做成回归断言(版本一换,一条就不会过)
★★★★ showTime={false} 会整列不渲染时间_renderTime 里 if (!this.props.showTime) return null
★★★★ 三块可替换renderTime / renderDetail / renderCircle
★★★ isUsingFlatlist 默认 true内部走 FlatList;外层别再用 ScrollView,否则会报 VirtualizedLists should never be nested…
★★ 圆点尺寸 = circleSize × density默认 16 vp ⇒ 48 px(实测命中)
★★ 行级可覆盖样式rowData.lineWidth / lineStyle / lineColor / columnSideMargin … 用 ?? 合并
★ 最后一行自动不画线isLast 时 lineColor 变透明
★ 无动画所以没有 useNativeDriver、没有 reanimated,也就没有动画类问题

11.2 本次验证的边界

  1. ★★★ 未验证自定义 renderDetail 的实际输出(第二个实例在屏幕下方,且外层滚动被内部列表吃掉手势,没能滚到);
  2. ★★★ 未验证 renderTime / renderCircle 的替换;
  3. ★★ 未验证 columnFormat 的四种取值(只用了默认 single-column-left);
  4. ★★ 未验证 separator={true} 与 separatorStyle;
  5. ★★ 未验证 onEventPress / onTimePress 的回调(点击未做);
  6. 未验证 innerCircle 非 none 时的圆点内圈;
  7. 未验证 two-column 下 position: 'right' 的左右交错;
  8. 未在 RN 0.88.0 / RNOH 0.84.4 上复验;
  9. 未测其它屏幕宽度;未做跨平台对照。

未改动库代码。


十二、常见问题

Q1:它在 RN 0.84 上能跑吗?

能,而且最省事。 它零第三方依赖、零原生 peer、零 isRequired、老写法九项全空、无动画;
13 个 defaultProps 常量全部精确命中;三列文本(time/title/description)与行数一致;圆点尺寸 48 px 精确命中。
一次构建就成功,一次崩溃都没有。

Q2:控制台出现 VirtualizedLists should never be nested inside plain ScrollViews?

因为 Timeline 内部默认用 FlatList(isUsingFlatlist={true}),而你又把它套在 ScrollView 里。
两种解法:① 时间线页面外层不要 ScrollView;② 传 isUsingFlatlist={false} 让它内部用 ScrollView。

Q3:showTime={false} 为什么没立刻生效?

它是生效的(实测 time 文本从 3 个变成 0 个),但内部是 FlatList,行更新要等一帧以上。
⚠️ 排查时要注意:在 prop 刚切换后就采样,很可能看到旧状态 ——
建议在同一相位窗口内多次采样看趋势。

Q4:怎么让某一行显示自定义内容?

传 renderDetail:

<Timeline
  data={data}
  renderDetail={rowData => (
    <View><Text>{`自定义:${rowData.title}`}</Text></View>
  )}
/>

同理还有 renderTime(时间列)与 renderCircle(圆点)。⚠️ 本次未能验证其实际输出(见边界)。

Q5:圆点怎么调大小/颜色?

<Timeline circleSize={20} circleColor="#FF5252" innerCircle="dot" innerCircleColor="#fff" />

也能按行覆盖:data 每项里写 circleColor / circleSize。

Q6:最后一行的那截线为什么不见了?

设计如此 —— _renderEvent 里判断 isLast 时把 lineColor 置为 'rgba(0,0,0,0)'(透明),
避免时间线在末尾多出一段"悬空"的竖线。想保留就传 renderFullLine。

Q7:能左右交错(左右两列)吗?

columnFormat="two-column",配合每行的 position: 'right' 或自动按奇偶行交替(源码里 rowID % 2 === 0)✓


小结

react-native-timeline-flatlist 是三列式时间线列表,也是迄今为止最干净的库:
零第三方依赖、零原生 peer、零 isRequired、零 propTypes、老写法九项全空、无动画。
13 个 defaultProps 常量全部精确命中,三列文本与行数一一对应,圆点尺寸精确。

最值得记下来的是这几条:

  1. ★★★★★ "四个零"意味着整条适配链上没有任何风险点。
    不用接 HAR、不用补装依赖、不用打桩、不用改 babel ⇒
    一次构建成功、零崩溃 —— 这条适配链上没有任何需要排障的环节 ✓

  2. ★★★★★ 13 项 defaultProps 全是常量 ⇒ 天然的回归断言。
    这种"常量全集"式的契约,写成一条条 PASS|desc|expected|actual 最划算:
    升级版本时,任何一项变了都会立刻被抓到 ✓

  3. ★★★★★ 又一个"采样太早"的教训。
    showTime={false} 实测从 3 个 time 文本变成 0 个 —— 但第一次采样(切换后 ~1 秒)看到的还是旧值 3,
    差点误判成"不生效"。⇒ 受控 prop 变化后要留足重渲染时间,并在同一相位多次采样看趋势 ✓

  4. ★★★★ 圆点尺寸 = circleSize × density:默认 16 vp ⇒ 48 px,实测 3 个 48×48 全部命中 ✓

  5. ★★★ 一条标准 RN 警告的来源要分清:
    VirtualizedLists should never be nested inside plain ScrollViews 是我的页面结构导致的,
    不是库的问题、也不是鸿蒙的问题 ⇒ 排查时先问"这个警告是谁的" ✓

  6. ★★★ 三块可替换(renderTime/renderDetail/renderCircle)是这类"列表型"库的通用设计,
    但替换是否生效必须实测 —— 本次因为实例排在屏外 + 滚动被内部列表吃掉手势,没能验证,如实标注 ✓

  7. ★ 行级覆盖用 ?? 而非 ||(rowData.lineWidth ?? this.props.lineWidth)⇒
    0 这类有效值不会被当成"未设置" ⇒ 是个好写法,值得借鉴 ✓


本篇用到的库

项内容
三方库react-native-timeline-flatlist(上游 1.0.1 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-timeline-flatlist
适配 TAG1.0.1-ohos-1.0.0
需要 HAR / 权限 / ohpm✅✅ 都不需要(零第三方依赖、零原生 peer)
库类型时间线列表(三列:时间 / 圆点+线 / 详情)
运行时依赖✅ dependencies 为空;peer 只有 react 与 react-native
propTypes / isRequired✅ 都没有
prepublishOnly✅ npm install 不跑
许可证MIT
上游仓库https://github.com/Eugnis/react-native-timeline-flatlist(★522,2026-07 仍在更新)
宿主工程RNOH084Demo(测试页 rnAppKey = TimelineFlatlistTestApp)
# 装库即可(无 dependencies、无原生 peer)
npm install "git+https://atomgit.com/oh-react-native/react-native-timeline-flatlist.git#1.0.1-ohos-1.0.0" --ignore-scripts
import Timeline from 'react-native-timeline-flatlist';   // ★ 默认导出,不用花括号

const data = [
  {time: '10:00', title: '标题一', description: '描述一'},
  {time: '11:00', title: '标题二', description: '描述二'},
];

// ① 基本用法
<Timeline data={data} />

// ② 关掉时间列(内部 _renderTime 直接 return null)
<Timeline data={data} showTime={false} />

// ③ 改圆点 / 线
<Timeline data={data} circleSize={20} circleColor="#FF5252" lineWidth={3} lineStyle="dashed" />

// ④ 自定义三块中的任意一块
<Timeline data={data} renderDetail={row => <View><Text>{`自定义:${row.title}`}</Text></View>} />

// ⚠️ 默认 isUsingFlatlist=true(内部 FlatList)⇒ 外层不要再包 ScrollView,否则会报 VirtualizedLists 警告
// ⚠️ showTime 切换后要等一两帧才反映到 UI(FlatList 行更新)
// ⚠️ 最后一行自动不画线(lineColor 置透明),想保留传 renderFullLine
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey TimelineFlatlistTestApp

判据的复现方式

# ① 契约:13 项 defaultProps 逐条 PASS/FAIL
hdc shell "hilog -x | grep tlt-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ② 文本级:三类文本各 3 个(3 行数据)
devecocli ui layout | Select-String 'Text#Paragraph'
#    ⇒ time 3 个 / title 3 个 / description 3 个
# ③ 圆点几何:circleSize 16 ⇒ 48px
devecocli ui layout   # 找 48×48 的方块,应有 3 个
# ④ A/B:showTime=false 后等 2s 以上再采样,time 文本应为 0

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 26(26.0.0.32)
设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3
本机状态栏高度117px = 39vp
宿主依赖✅ 无需任何额外依赖
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap 3 分 36 秒 ⇒ 一次成功(打包与构建退出码都检查了)
验证规模契约 20 条全 PASS(13 项 defaultProps 逐条核对 + class + 无 propTypes);文本级 3 类 × 3 个全部命中;圆点 48×48px 精确命中 3 个;showTime A/B 对照成立(3 → 0);4 张截图互异

⚠️ 基线说明:交付包 spec.json 声明 RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
本库无任何已移除写法、无平台分支、无原生依赖 ⇒ 结论跨基线成立。


相关社区与项目地址

项目地址
React Native 鸿蒙官方社区(CPF-RN)https://atomgit.com/CPF-RN
RNOH 框架仓库https://atomgit.com/openharmony-RN/ohos_react_native
RNOH 三方库使用文档https://atomgit.com/OpenHarmony-RN/usage-docs
React Native for OpenHarmony 组织https://atomgit.com/oh-react-native
RN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN

React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native

RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐