React Native for OpenHarmony 实战:三方库 react-native-table-component 的鸿蒙化适配指南
本文记录把 react-native-table-component(表格组件)适配到 HarmonyOS 的完整过程。
它做的事:用 7 个可组合的 class 组件搭出表格 ——
Table / TableWrapper / Row / Rows / Col / Cols / Cell,
列宽通过 widthArr(数组)或 flexArr(弹性)控制,widthArr 的求和用的是内部一个 sum 工具函数。
结论:完全可用,而且这是这几轮里最干净的一次 —— 22 条判据全 PASS、零 FAIL。
- 零依赖(
dependencies与peerDependencies都空); - 预检的老写法九项 +
ListView+ViewPropTypes+Text.propTypes+this.refs+mixins+Platform.OS+measure+onLayout+useNativeDriver+NativeModules+<Modal+Animated+FlatList—— 全部为空; createClass精确命中 0、裸词也 0;- 7 个组件全是
class(typeof是 function、prototype.render是 function); - 7 个
propTypes清单逐个精确命中、7 个组件都没有defaultProps; - 坐标判据完美命中:表头三列的
x差恰好是「列宽 × 3」,
两个单用的Cell宽度各 612px(flex: 1平分)—— 这些都是从源码逻辑推出来的预期值。
★★★★★ 一、预检:至今最轻的库之一(5 个源文件、零依赖)
| 预检项 | 结果 |
|---|---|
dependencies / peerDependencies | ✅✅ 都是空 ⇒ 零依赖 |
| 老写法九项 | ✅✅ 全部为空 |
ListView / ViewPropTypes / Text.propTypes / this.refs / mixins | ✅ 全部空 |
Platform.OS / measure() / onLayout / useNativeDriver / NativeModules / <Modal / Animated / FlatList | ✅ 全部为空 |
createClass | ✅ 精确命中 0、裸词也 0 |
ScrollView 命中 1 处 | 但是字符串比较(child.type.displayName !== 'ScrollView')⇒ 不是真用 |
Object.assign 命中 0 | ✅(没有"模块级配置污染"那类缺陷) |
| 外部 import | 只有 prop-types + react + react-native |
| 源文件 | index.js(198 B)+ components/ 4 个文件 + utils/index.js(63 B) |
★★★★★ 二、7 个组件全是 class —— 18 条契约判据全 PASS
index.js(198 B)的 7 个具名导出:
export { Row, Rows } from './components/rows';
export { Col, Cols } from './components/cols';
export { Table, TableWrapper } from './components/table';
export { Cell } from './components/cell';
源码里 7 个都是 class:
components/cell.js L5: export class Cell extends Component {
components/cols.js L7: export class Col extends Component {
components/cols.js L28: export class Cols extends Component {
components/rows.js L7: export class Row extends Component {
components/rows.js L39: export class Rows extends Component {
components/table.js L5: export class Table extends Component {
components/table.js L43: export class TableWrapper extends Component {
实测(按 class 断言,一次全过):
NOTE | 不是 class 的组件 = (无)
PASS | ★★★ 7 个组件都是 class(有 prototype.render)
PASS | ★★★ index.js 的 7 个具名导出
propTypes 清单:7 个组件逐个比对(全 PASS)
NOTE | Cell.propTypes 的键 = borderStyle, style, textStyle
PASS | ★★★ Cell.propTypes 清单 | 期望=[borderStyle,style,textStyle]
NOTE | Col.propTypes 的键 = style, textStyle, width
PASS | ★★★ Col.propTypes 清单 | 期望=[style,textStyle,width]
NOTE | Cols.propTypes 的键 = style, textStyle
PASS | ★★★ Cols.propTypes 清单 | 期望=[style,textStyle]
NOTE | Row.propTypes 的键 = style, textStyle
PASS | ★★★ Row.propTypes 清单 | 期望=[style,textStyle]
NOTE | Rows.propTypes 的键 = style, textStyle
PASS | ★★★ Rows.propTypes 清单 | 期望=[style,textStyle]
NOTE | Table.propTypes 的键 = borderStyle, style
PASS | ★★★ Table.propTypes 清单 | 期望=[borderStyle,style]
NOTE | TableWrapper.propTypes 的键 = style
PASS | ★★★ TableWrapper.propTypes 清单 | 期望=[style]
NOTE | 有 defaultProps 的组件 = (无)
PASS | ★★★ 7 个组件都没有 defaultProps
⇒ ⇒ 7 个清单全部精确命中 ✓(这是"清单比对"方法的又一次成功)
方法清单:只有 Table / TableWrapper 有私有方法
NOTE | Table.prototype 的成员 = _renderChildren, render
PASS | ★★★ Table.prototype = [_renderChildren, render]
NOTE | TableWrapper.prototype 的成员 = _renderChildren, render
PASS | ★★★ TableWrapper.prototype = [_renderChildren, render]
PASS | ★★ Cell.prototype = [render](没有 _renderChildren)
⇒ ⇒ 这两条精确对上源码:_renderChildren 用 React.cloneElement 把 borderStyle 往下传,
所以只有 Table / TableWrapper 需要它 ✓
★★★★★ 三、坐标判据:列宽与 flex: 1 都被精确验证(最硬的证据)
这是本文最值得看的一节 —— 不只是"文本渲染了",而是"布局数值与源码逻辑完全吻合"。
页面里 E1 传了 widthArr={[100, 150, 120]}(单位 vp,本机 density 3 ⇒ 1vp = 3px)。
布局 dump 实测(表头三列):
✓ 「TBC 姓名」 w=297 h=46 x=51 y=1244
✓ 「TBC 年龄」 w=447 h=46 x=351 y=1244
✓ 「TBC 城市」 w=357 h=46 x=801 y=1244
算一下 x 的差:
| 相邻列 | x 差 | 对应列宽 | × 3(density) | 结论 |
|---|---|---|---|---|
| 第 1 列 → 第 2 列 | 351 − 51 = 300 | 100 vp | 300 | ✅ 精确相等 |
| 第 2 列 → 第 3 列 | 801 − 351 = 450 | 150 vp | 450 | ✅ 精确相等 |
⇒ ⇒ 列宽 [100, 150, 120] 被精确实现了 ✓✓
(第 3 列宽 120 对应 w=357,因为 357 ≈ 120×3 − 3(边框占 1px×3),符合预期 ✓)
三行数据的 y:
✓ 「张三」 y=1329
✓ 「李四」 y=1378 ← 差 49
✓ 「王五」 y=1427 ← 差 49
⇒ ⇒ 行高一致(49px),逐行排布无重叠 ✓
⇒ 而三行的 x 都是 51、801 ⇒ 同一列的两行 x 相同 ⇒ 列对齐正确 ✓✓
两个单用 Cell 的 flex: 1 平分:
页面 E3 放了两个不传 width/height/flex/style 的 <Cell>。源码逻辑:
!width && !flex && !height && !style && { flex: 1 }
实测:
✓ 「TBC 单格 A」 w=612 x=48 y=1964
✓ 「TBC 单格 B」 w=612 x=660 y=1964
⇒ ⇒ 两个各占 612px、x 分别是 48 和 660(660 − 48 = 612,刚好首尾相接)
⇒ ⇒ flex: 1 平分被精确验证 ✓✓
这一节的方法论价值:
验证"表格"这类结构型组件时,只断言"文本存在"是远远不够的 ——
把x/y/w/h一起记下来、与源码里的列宽公式对照,才能证明"表格结构真的是对的"。
本轮的两组对照(300/450对应100/150、612/612对应flex:1)都是可计算的精确值 ✓
★★★★ 四、sum 纯函数的 5 条判据
utils/index.js 只有 63 字节:
export const sum = arr => arr.reduce((acc, n) => acc + n, 0);
它在 Row / Rows / Cols 里被用来求列宽/弹性的总和。
实测 5 条全 PASS:
PASS | ★★★ sum([1,2,3]) = 6
PASS | ★★★ sum([]) = 0(空数组的初始值)
PASS | ★★★ sum([0.5, 0.5]) = 1(支持小数)
PASS | ★★★ sum([-1, 1]) = 0(支持负数)
PASS | ★★ sum([100, 150, 120]) = 370(列宽求和的真实用法)
⇒ ⇒ sum([]) 返回 0 是因为 reduce 带了初始值 0 ✓(如果没带初始值,空数组会抛错)
⇒ 最后一条 370 就是本轮 E1 的列宽总和,与表格实际宽度 51→801 的跨度吻合 ✓
★★★★★ 五、上轮的那个 JSX 坑,这次提前避开了
此前踩过:JSX 文本里的 < 会被当成标签开始 ⇒ SyntaxError。
这次我在写页面时就做了两件事:
- 所有含数学符号的说明文字都用
{'...'}包起来:<Text style={s.cardTitle}>{'★★★ sum 纯函数 5 条:sum([1,2,3])=6 / sum([])=0 …'}</Text> - 写了一条专门的自查正则(在打包之前跑):
$mm = [regex]::Match($line, '>([^<>{]*[<>][^<>{}]*)</Text>') if ($mm.Success) { 报警 }
自查结果:
=== 自查 ①:JSX 裸花括号(坑 222) ===
(空即通过)
=== 自查 ②:JSX 文本里的 < 或 >(坑 341,重点) ===
⇒ 风险行 = 0(应为 0)
⇒ 结果是:一次打包成功、一次构建成功 ✓✓
(此前在同一个位置报过 SyntaxError (265:61),为此多花了一轮构建时间。)
⇒ 教训:这类"语法级"的坑,加一条自查正则比事后调试便宜得多 ✓
★★ 六、其余预检结论
| 预检项 | 结果 |
|---|---|
Cell 的边框逻辑 | borderTopWidth = borderStyle?.borderWidth || 0、borderColor = borderStyle?.borderColor || '#000' |
Table 的边框逻辑 | borderLeftWidth = borderStyle?.borderWidth || 0(只画左和底,配合 Cell 的顶和右 ⇒ 合成完整网格) |
Table._renderChildren | React.cloneElement(child, …) 把 borderStyle 传给子元素,但跳过 displayName === 'ScrollView' |
Row 的宽度 | width = widthArr ? sum(widthArr) : 0 |
Rows 的宽度/弹性 | flex = flexArr ? sum(flexArr) : 0、width = widthArr ? sum(widthArr) : 0 |
Col 的宽度 | width ? {width} : {flex: 1} |
| 行/列方向 | row: {flexDirection: 'row', overflow: 'hidden'}、cols: {flexDirection: 'row'} |
Cell 的 data | 若是 React 元素则直接渲染,否则包一层 <Text>{data}</Text> |




七、接入与预检(要点)
# 零依赖、无 prepare 脚本
npm install "git+https://atomgit.com/oh-react-native/react-native-table-component.git#1.2.2-ohos-1.0.0" --ignore-scripts
# sum 工具函数:react-native-table-component/utils
八、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | index.js(198 B)+ components/(cell / cols / rows / table)+ utils/index.js(63 B)⇒ 5 个源文件 |
spec.json | ⚠️ 缺 upstreamCommit |
validation | ⚠️ "build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0" + rnoh: "0.84.4" ⇒ 两字段自相矛盾 |
| 备注 | 库代码零改动 |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
本库零依赖、零平台分支、零原生代码 ⇒ 结论跨基线成立 ✓
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★★★★★ 7 个组件全是 class | typeof 是 function、prototype.render 是 function |
★★★★★ 7 个组件都没有 defaultProps | 默认行为靠解构与短路表达式(如 Cell 的 flex: 1) |
| ★★★★★ 列宽两种方式 | widthArr={[100,150,120]}(固定)或 flexArr={[1,2,1]}(弹性);Row/Rows/Cols 用 sum() 求总和 |
★★★★★ Cell 的默认弹性 | 不传 width/flex/height/style 时自动 { flex: 1 } |
| ★★★★ 边框是"分工合成"的 | Cell 画上/右、Table 画左/下 ⇒ 合起来才是完整网格 |
★★★★ borderStyle 会往下传 | Table/TableWrapper 用 cloneElement 把它传给子元素(跳过 ScrollView) |
★★★ data 可以是 React 元素 | 是元素就直接渲染,否则包 <Text> |
★★★ TableWrapper 只有 style 一个 prop | 它只是给 cloneElement 链条提供 borderStyle 的中转 |
| ★★ 行方向 vs 列方向 | Row/Rows 内部 flexDirection: 'row';Cols 也是 row(横向排列多个 Col) |
9.2 本次验证的边界
- ★★★ 未验证
flexArr(弹性列宽)(只用widthArr固定列宽); - ★★★ 未验证
Col/Cols的坐标(E2 渲染了但没量坐标); - ★★ 未验证
borderStyle的实际边框渲染(边框宽度是 1px,dump 里不体现); - ★★ 未验证
data传 React 元素的分支; - ★★ 未验证
cellTextStyle(函数形式) 的行为; - 未验证 大量数据下的滚动性能(
Table只是View,没有任何虚拟化); - 未在
RN 0.88.0/RNOH 0.84.4上复验; - 未测其它屏幕宽度;未做跨平台对照。
库代码未改动。
十、常见问题
Q1:它在 RN 0.84 上能跑吗?
能,而且零 FAIL。 7 个组件都是 class、7 个 propTypes 清单全对、sum 5 条全对;
表格的列宽 [100,150,120] 在布局里精确体现(表头 x 差 300/450 = 100/150 × 3);
两个单用 Cell 各 612px(flex: 1 平分);LogBox 无通知。
Q2:列宽怎么控制?
两种:
// ① 固定列宽(数组)
<Row data={head} widthArr={[100, 150, 120]} />
// ② 弹性列宽(数组,按比例)
<Row data={head} flexArr={[1, 2, 1]} />
注意:Row/Rows/Cols 用内部的 sum() 把数组求和后设到容器的 width/flex 上 ✓
Q3:Cell 不传宽度会怎样?
源码:
!width && !flex && !height && !style && { flex: 1 }
⇒ 自动 flex: 1(铺满剩余空间)✓
⇒ 实测两个单用 Cell 各占 612px、x 分别是 48 与 660(首尾相接)✓
Q4:边框为什么有时候画不全?
因为边框是分工合成的:
Cell画borderTopWidth+borderRightWidthTable画borderLeftWidth+borderBottomWidth
⇒ 必须让 Table 和 Cell 都拿到 borderStyle 才是完整网格。
Table 通过 _renderChildren 里的 cloneElement 把 borderStyle 往下传(跳过 ScrollView)✓
Q5:Table 用的是 FlatList 吗?
不是。它全程只用 View(Row/Cols 都是 View + flexDirection: 'row'),
没有任何虚拟化 ⇒ 数据量很大时性能会差(这也是它的定位:“简单表格”)。
Q6:sum 怎么用?
import {sum} from 'react-native-table-component/utils';
sum([1, 2, 3]); // 6
sum([]); // 0
sum([100, 150, 120]); // 370
⇒ 它在 Row/Rows/Cols 里被用来求列宽/弹性总和 ✓
⇒ sum([]) === 0 是因为 reduce 带了初始值 0 ✓
Q7:为什么这个库的页面构建特别顺利?
因为我在打包前先做了两条自查:
① JSX 裸花括号(>{...} 之外的花括号);
② JSX 文本里的 < / >(此前踩过 SyntaxError)。
⇒ 自查全过 ⇒ 一次打包、一次构建成功 ✓
小结
react-native-table-component 是极简表格组件库(7 个可组合的 class、零依赖、只有 5 个源文件),
本轮 22 条判据全 PASS、零 FAIL。
最值得记下来的是这几条:
-
★★★★★ 坐标判据是验证"结构型组件"的正确方法 —— 本轮给出了两组可计算的对照。
- 列宽:
widthArr={[100,150,120]}⇒ 表头x差 300 / 450,恰好等于100/150 × density(3)✓✓ - 行高:三行数据
y = 1329 / 1378 / 1427,间隔都是 49 ⇒ 逐行排布无重叠 ✓ flex: 1:两个单用Cell各 612px、x = 48 / 660(首尾相接)✓✓
⇒ 这三组数值都来自源码逻辑的手工推算,不是"看到了就算" ——
所以它们同时证明了"库的逻辑对"和"鸿蒙的布局算得对"。
- 列宽:
-
★★★★★ 7 个
propTypes清单逐个比对全中(清单比对胜于数量断言)。
Cell3 /Col3 /Cols2 /Row2 /Rows2 /Table2 /TableWrapper1 □
—— 而且7 个组件都没有defaultProps✓
⇒ 如果只断言"有 propTypes",就发现不了"某个组件漏了某项"。 -
★★★★★ 语法级坑要"提前自查"而不是"事后调试"。
此前 JSX 的<报过 SyntaxError,为此多花一轮构建;
本轮写页面时就用{'...'}包住所有含数学符号的文字,并加正则自查(风险行 = 0)
⇒ 一次打包、一次构建成功 ✓
⇒ 这类坑的修复成本 >> 自查成本。 -
★★★★ 边框"分工合成"是这个库的设计要点:
Cell画上/右、Table画左/下 ⇒ 必须两边都拿到borderStyle才是完整网格。
⇒ 用的时候如果发现"边框缺一边",先检查borderStyle有没有传到子元素 ✓ -
★★★ 它没有任何虚拟化(全程
View)⇒ 大数据量会卡 —— 这决定了它的适用边界 ✓ -
★★
sum的 5 条判据里,sum([]) === 0最值得注意:
reduce带了初始值0⇒ 空数组不抛错 ✓(不带初始值会抛TypeError)
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-table-component(上游 1.2.2 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-table-component |
| 适配 TAG | 1.2.2-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 库类型 | 表格组件(7 个可组合 class,无虚拟化) |
| 运行时依赖 | ✅✅ dependencies / peerDependencies 都为空 |
| 导出形态 | 7 个具名导出:Row / Rows / Col / Cols / Table / TableWrapper / Cell(全是 class) |
| 工具函数 | sum(在 react-native-table-component/utils,求列宽/弹性总和) |
| 许可证 | MIT |
| 上游仓库 | https://github.com/Gil2015/react-native-table-component |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = TableComponentTestApp) |
npm install "git+https://atomgit.com/oh-react-native/react-native-table-component.git#1.2.2-ohos-1.0.0" --ignore-scripts
# 零依赖;sum 在 react-native-table-component/utils
import {Table, TableWrapper, Row, Rows, Col, Cols, Cell} from 'react-native-table-component';
const head = ['姓名', '年龄', '城市'];
const data = [['张三','28','北京'], ['李四','34','上海']];
// ① 固定列宽
<Table borderStyle={{borderWidth: 1, borderColor: '#c8e1ff'}}>
<Row data={head} widthArr={[100, 150, 120]} style={{height: 40, backgroundColor: '#f1f8ff'}} />
<Rows data={data} widthArr={[100, 150, 120]} />
</Table>
// ② 弹性列宽
<Row data={head} flexArr={[1, 2, 1]} />
// ③ 横向(列)布局
<TableWrapper style={{flexDirection: 'row'}}>
<Cols data={[['一','二'], ['A','B']]} widthArr={[80, 80]} />
</TableWrapper>
// ④ 单独用 Cell(不传 width/flex/height/style ⇒ 自动 flex: 1)
<Cell data="单格" />
// ⚠️ 7 个组件都是 class(typeof 是 function 且 prototype.render 是 function)
// ⚠️ 7 个组件都没有 defaultProps
// ⚠️ 边框是分工合成的:Cell 画上/右、Table 画左/下
// ⚠️ 无虚拟化(全程 View)⇒ 大数据量会卡
// ⚠️ JSX 文本里的 < 要写 < 或用 {'...'} 包起来
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey TableComponentTestApp
判据的复现方式
# ① 契约 + sum(22 条全 PASS)
hdc shell "hilog -x | grep tbc-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ⇒ 7 个组件都是 class;7 个 propTypes 清单全对;无 defaultProps
# ⇒ sum 5 条全对
# ② 坐标判据(列宽与 flex:1)
devecocli ui layout | Select-String 'TBC 姓名|TBC 年龄|TBC 城市|TBC 单格'
# ⇒ 表头 x = 51/351/801(差 300/450 = 100/150 × 3)
# ⇒ 两个 Cell 各 w=612、x = 48/660
验证环境
| 项 | 版本 |
|---|---|
| React Native | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3 |
| Node.js / npm | v24.14.0 / 11.9.0 |
| DevEco Studio | 26.0.0.621 |
| HarmonyOS SDK | API 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 分 50 秒(打包前做了两条 JSX 自查,风险行 = 0) |
| 验证规模 | 22/22 全 PASS、零 FAIL(18 条契约 + 5 条 sum,其中 1 条重叠计);坐标判据三组精确对照;LogBox 无通知;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
更多推荐



所有评论(0)