本文记录把 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 = 300100 vp300✅ 精确相等
第 2 列 → 第 3 列801 − 351 = 450150 vp450✅ 精确相等

⇒ ⇒ 列宽 [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。

这次我在写页面时就做了两件事:

  1. 所有含数学符号的说明文字都用 {'...'} 包起来:
    <Text style={s.cardTitle}>{'★★★ sum 纯函数 5 条:sum([1,2,3])=6 / sum([])=0 …'}</Text>
    
  2. 写了一条专门的自查正则(在打包之前跑):
    $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._renderChildrenReact.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 个组件全是 classtypeof 是 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 本次验证的边界

  1. ★★★ 未验证 flexArr(弹性列宽)(只用 widthArr 固定列宽);
  2. ★★★ 未验证 Col / Cols 的坐标(E2 渲染了但没量坐标);
  3. ★★ 未验证 borderStyle 的实际边框渲染(边框宽度是 1px,dump 里不体现);
  4. ★★ 未验证 data 传 React 元素的分支;
  5. ★★ 未验证 cellTextStyle(函数形式) 的行为;
  6. 未验证 大量数据下的滚动性能(Table 只是 View,没有任何虚拟化);
  7. 未在 RN 0.88.0 / RNOH 0.84.4 上复验;
  8. 未测其它屏幕宽度;未做跨平台对照。

库代码未改动。


十、常见问题

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 + borderRightWidth
  • Table 画 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。

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

  1. ★★★★★ 坐标判据是验证"结构型组件"的正确方法 —— 本轮给出了两组可计算的对照。

    • 列宽:widthArr={[100,150,120]} ⇒ 表头 x 差 300 / 450,恰好等于 100/150 × density(3) ✓✓
    • 行高:三行数据 y = 1329 / 1378 / 1427,间隔都是 49 ⇒ 逐行排布无重叠 ✓
    • flex: 1:两个单用 Cell 各 612px、x = 48 / 660(首尾相接)✓✓
      ⇒ 这三组数值都来自源码逻辑的手工推算,不是"看到了就算" ——
      所以它们同时证明了"库的逻辑对"和"鸿蒙的布局算得对"。
  2. ★★★★★ 7 个 propTypes 清单逐个比对全中(清单比对胜于数量断言)。
    Cell 3 / Col 3 / Cols 2 / Row 2 / Rows 2 / Table 2 / TableWrapper 1 □
    —— 而且7 个组件都没有 defaultProps ✓
    ⇒ 如果只断言"有 propTypes",就发现不了"某个组件漏了某项"。

  3. ★★★★★ 语法级坑要"提前自查"而不是"事后调试"。
    此前 JSX 的 < 报过 SyntaxError,为此多花一轮构建;
    本轮写页面时就用 {'...'} 包住所有含数学符号的文字,并加正则自查(风险行 = 0)
    ⇒ 一次打包、一次构建成功 ✓
    ⇒ 这类坑的修复成本 >> 自查成本。

  4. ★★★★ 边框"分工合成"是这个库的设计要点:
    Cell 画上/右、Table 画左/下 ⇒ 必须两边都拿到 borderStyle 才是完整网格。
    ⇒ 用的时候如果发现"边框缺一边",先检查 borderStyle 有没有传到子元素 ✓

  5. ★★★ 它没有任何虚拟化(全程 View)⇒ 大数据量会卡 —— 这决定了它的适用边界 ✓

  6. ★★ sum 的 5 条判据里,sum([]) === 0 最值得注意:
    reduce 带了初始值 0 ⇒ 空数组不抛错 ✓(不带初始值会抛 TypeError)


本篇用到的库

项内容
三方库react-native-table-component(上游 1.2.2 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-table-component
适配 TAG1.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 文本里的 < 要写 &lt; 或用 {'...'} 包起来
# 换页启动测试页 —— 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 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 分 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐