☰
React Native for OpenHarmony 实战:三方库 react-native-simple-table 的鸿蒙化适配指南
2026/10/19 5:00:17 网站建设 项目流程

本文记录把react-native-simple-table(极简表格)适配到 HarmonyOS 的完整过程。

它做的事:用"横向ScrollView+ 表头 + 纵向ScrollView"拼出一个表格,
列由columns(含title/dataIndex/width)定义,行由dataSource提供。

结论:可用。而且这是这几轮里"代码量最小"的一个库 —— 只有index.js(105 B)+src/Table.js(3282 B / 137 行)两个源文件,零依赖。

判据结果是12 PASS / 2 FAIL,而那 2 条 FAIL是我自己的断言方法错(详见第五节):
propTypes.X.isRequired在运行时根本不可检测—— 它是"选了isRequired版本的 validator 函数",而这个函数上并没有.isRequired属性。

★★★★★ 一、预检:至今最"轻"的一个库

预检项结果
老写法九项(createClass/React.PropTypes/ViewPropTypes/Text.propTypes/ 字符串 ref /this.refs/mixins/componentWillReceiveProps/componentWillMount/getDefaultProps)✅✅全部为空
ListView.DataSource/<ListView✅ 空
measure()/onLayout/useNativeDriver/NativeModules/<Modal/Animated/FlatList✅全部为空
Platform.OS✅空(零平台分支)
createClass✅精确命中 0、裸词也 0
dependencies/peerDependencies✅✅都是空⇒零依赖
外部 import只有react+prop-types✓
isRequired4 处⚠️ 全在propTypes里(L21/L22/L24/L27)⇒ 只是类型校验,不崩 ✓
ScrollView5 处✓(它就是用 ScrollView 拼的)
scripts只有test⇒ 没有prepare,不需要--ignore-scripts
规模只有index.js+src/Table.js(src/Table.js3282 B / 137 行)
index.js(105 B)import Table from './src/Table'+export default Table⇒只有默认导出

★★★★★ 二、Table是 class —— 这次按 class 断言,一次全中

先看源码那一行(坑 327 的固定动作):

// src/Table.js L17classTableextendsComponent{// src/Table.js L137exportdefaultTable

实测:

NOTE | 默认导出 Table 的 typeof = function PASS | ★★★ Table 是 class(function) NOTE | Table 有 prototype.render = function PASS | ★★★ Table 有 prototype.render(它是 class 组件) PASS | ★★ 入口没有具名的 Table 导出

⇒⇒ 这次是 class,所以断言prototype.render是 function✓(另两种形态是forwardRef与函数组件,判据不同)

判别三档(现在每次都先查这个):

写法typeofprototype.render判据
class X extends Componentfunctionfunction断言有prototype.render
const X = ({…}) => …functionundefined断言语没有prototype.render
X = React.forwardRef(…)objectundefined断言$$typeof === Symbol(react.forward_ref)

★★★★★ 三、propTypes/defaultProps:清单比对 + 精确值全中

源码:

classTableextendsComponent{staticpropTypes={columns:PropTypes.arrayOf(PropTypes.shape({title:PropTypes.string.isRequired,dataIndex:PropTypes.string.isRequired,width:PropTypes.number,})).isRequired,columnWidth:PropTypes.number,height:PropTypes.number,dataSource:PropTypes.array.isRequired,renderCell:PropTypes.func,};staticdefaultProps={columns:[],dataSource:[],columnWidth:DEFAULT_COLUMN_WIDTH,// = 60height:DEFAULT_HEIGHT,// = 240renderCell:undefined,};…}

实测(清单比对 + 精确值):

NOTE | propTypes 的键数 = 5 NOTE | propTypes 的键 = columnWidth, columns, dataSource, height, renderCell PASS | ★★★ propTypes 清单 = [columns, columnWidth, dataSource, height, renderCell] NOTE | defaultProps 的键数 = 5 NOTE | defaultProps 的键 = columnWidth, columns, dataSource, height, renderCell PASS | ★★★ defaultProps 清单 = [columns, columnWidth, dataSource, height, renderCell] NOTE | defaultProps.columnWidth = 60 PASS | ★★★ defaultProps.columnWidth = 60(DEFAULT_COLUMN_WIDTH) NOTE | defaultProps.height = 240 PASS | ★★★ defaultProps.height = 240(DEFAULT_HEIGHT) PASS | ★★ defaultProps.columns 是空数组 PASS | ★★ defaultProps.dataSource 是空数组 PASS | ★★ defaultProps.renderCell = undefined

⇒⇒defaultProps8 条全 PASS(清单一致 +60/240/ 两个空数组 /undefined)✓✓
⇒60与240是从源码常量DEFAULT_COLUMN_WIDTH/DEFAULT_HEIGHT抄的,不是猜的✓

★★★★★ 四、4 个方法全在prototype上(含 3 个_前缀私有方法)

源码里这些方法都是普通方法写法(_xxx() {}):

_renderCell(cellData,col){…}_renderHeader(){…}_renderRow(rowData,index){…}render(){…}

实测:

NOTE | prototype 上缺失的方法 = (无) PASS | ★★★ 4 个方法都在 prototype 上(含 3 个 _ 前缀私有方法)

⇒⇒ 这次是"原型方法"档(与"箭头类字段只在实例上"那一档形成对照)✓

★★★★★ 五、2 条 FAIL 是我的断言方法错:propTypes.X.isRequired不可运行时检测

我写了两条断言:

check('★★ propTypes.dataSource 是 isRequired',true,!!T.propTypes?.dataSource?.isRequired);check('★★ propTypes.columns 是 isRequired',true,!!T.propTypes?.columns?.isRequired);

实测:

NOTE | propTypes.dataSource 是否 isRequired = false FAIL | ★★ propTypes.dataSource 是 isRequired | 期望=true | 实际=false FAIL | ★★ propTypes.columns 是 isRequired | 期望=true | 实际=false

但源码里明明写了.isRequired(L24 / L27)。于是我单独验证了prop-types本身的机制:

string.isRequired 的 typeof = function string.isRequired.isRequired = undefined string.isRequired 有没有 isRequired 属性 = false String(P.string) = function () { [native code] } String(P.string.isRequired) = function () { [native code] }

⇒⇒PropTypes.X.isRequired是一个"validator 函数"(isRequired版本的校验器),
这个函数上并没有.isRequired这个属性✗

⇒⇒ 所以!!propTypes.dataSource.isRequired永远是false——我的断言在原理上就不可能通过✓

这意味着:

想验证的东西可行的做法不可行的做法
“这个 prop 是必填”读源码(唯一可靠)、或缺参时看 LogBox 是否警告❌!!propTypes.X.isRequired
“这个 prop 有校验器”typeof propTypes.X === 'function'❌(两种版本都是 function,区分不了)

⇒⇒ 教训:isRequired是"在声明时选用了 isRequired 版本的 validator",
一旦拿到propTypes.X,就
无法再判断它当初是不是 isRequired 版本** ——
因为String()两边都是function () { [native code] },也没有任何可读标记。

⇒⇒ 这是本轮最有价值的技术发现,也是我这一轮唯一的两条 FAIL(都是同一个原因)。

★★★★★ 六、渲染证据:7 处文本命中 + 行高间隔一致

页面里放了两个表格:

  • E1:3 列 × 3 行,列宽120 / 80 / 100,height={300}
  • E2:2 列 × 2 行,不传columnWidth与height⇒ 用默认的60 / 240

布局 dump 实测(E1):

✓ 「STB 名称」 w=171 h=49 x=141 y=1220 ✓ 「STB 数量」 w=171 h=49 x=441 y=1220 ✓ 「STB 备注」 w=171 h=49 x=711 y=1220 ✓ 「苹果」 w=85 h=49 x=184 y=1303 ✓ 「香蕉」 w=85 h=49 x=184 y=1381 ✓ 「樱桃」 w=85 h=49 x=184 y=1459 ✓ 「红富士」 w=127 h=49 x=733 y=1303 ⇒ 命中 7 / 9

从这些数字能读出三件事✓✓:

观察结论
三列表头的y都是 1220、x= 141 / 441 / 711表头横向排列在同一个行上✓(x递增、y相同 ⇒ 是"一行三格"而不是"三行")
三行数据的y= 1303 / 1381 / 1459y间隔恰好 78(1381−1303=78、1459−1381=78)⇒行高一致、逐行排布无重叠✓
所有h都是 49单元格高度统一 ✓
数据格x=184与表头x=141不同表头与数据是两套View(headerItem与cell,两者minHeight分别是 30 与 25)⇒ 内容居中位置略有差异 ✓

⇒⇒ 这比"文本存在"强得多:它证明表格是"表头一行 + 数据多行"的正确结构,而不是"一堆文本堆在一起" ✓

⚠️ 「STB 键」/「STB 值」(E2 的表头)没在首屏 dump 里命中 —— 它们在页面更下方,需要滚动。
本轮的采样点在第一屏,所以记"未观察到(在更下方)"。

★★ 七、其余预检结论

预检项结果
交付包内容index.js(105 B)+src/Table.js(3282 B / 137 行)+.idea/(IDE 配置,无害)+screenshots/screenshot.png(151 KB)
样式精确值headerItem.minHeight=30/cell.minHeight=25/row.backgroundColor='#fbfbfb'/borderRightColor='#dfdfdf'
ScrollView用法外层horizontal={true}+ 内层纵向 ⇒ 双向滚动
许可证Apache-2.0(不是 MIT)


八、接入与预检(要点)

# 零依赖:dependencies 与 peerDependencies 都空;也没有 prepare 脚本npminstall"git+https://atomgit.com/oh-react-native/react-native-simple-table.git#0.0.3-ohos-1.0.0"--ignore-scripts

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

类别结果
交付包内容index.js(105 B)+src/Table.js(3282 B),只有 2 个源文件
spec.json⚠️缺upstreamCommit
validation⚠️"build-pass-device-pending"
⚠️ 声明的基线reactNative: "0.88.0"+rnoh: "0.84.4"⇒两字段自相矛盾
备注库代码零改动

关于基线:本次验证在RN0.84.1+ RNOH0.84.3上完成。
本库零依赖、零平台分支、零原生代码、useNativeDriver零命中⇒结论跨基线成立✓


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

10.1 用这个库要知道的

事项说明
★★★★★Table是 classtypeof是function、prototype.render是 function;4 个方法(_renderCell/_renderHeader/_renderRow/render)全在prototype上
★★★★★defaultProps5 项、值精确columns: []/dataSource: []/columnWidth: 60/height: 240/renderCell: undefined
★★★★★propTypes.X.isRequired不可运行时检测PropTypes.X.isRequired是一个validator 函数,它本身没有.isRequired属性⇒!!propTypes.X.isRequired永远是false
★★★★ 零依赖、只有 2 个源文件index.js+src/Table.js
★★★★ 表结构外层ScrollView horizontal+ 表头一行 + 内层纵向ScrollView;列宽取col.width || columnWidth || 60
★★★ 列宽优先级col.width>columnWidth>DEFAULT_COLUMN_WIDTH(60)
★★★ 行高由cell.minHeight = 25与文字行高决定(实测h=49、行间隔 78)
★★heightprop 控制内容区高度默认 240,写到contentContainerStyle上
★★ 许可证是 Apache-2.0与多数三方库(MIT)不同
★ 不需要--ignore-scripts它没有prepare脚本

10.2 本次验证的边界

  1. ★★★ 未验证横向滚动(E1 三列总宽 300vp < 屏宽,无需横滚;更宽的表未测);
  2. ★★★ 未验证renderCell自定义单元格(只用了默认渲染);
  3. ★★ 未验证heightprop 的实际生效(E1 传了 300、E2 用默认 240,但只从文本坐标看,没量容器高度);
  4. ★★ 未验证columns[].width与columnWidth的优先级(虽然源码写法明确,但没做三组对照实测);
  5. ★★ E2 的表头(「STB 键」/「STB 值」)未在首屏 dump 命中(在页面更下方);
  6. 未验证空dataSource/ 空columns的渲染;
  7. 未在RN 0.88.0/RNOH 0.84.4上复验;
  8. 未测其它屏幕宽度;未做跨平台对照。

库代码未改动。


十一、常见问题

Q1:它在 RN 0.84 上能跑吗?

能。零依赖、零平台分支、零原生代码;Table是 class(prototype.render存在)、
defaultProps的60/240实测精确;表格的三列表头在同一行、三行数据的y间隔恰好 78;LogBox无通知。

Q2:怎么判断某个 prop 是必填(isRequired)?

只能读源码。运行时检测不出来:

// ❌ 这永远是 false!!PropTypes.string.isRequired.isRequired// undefined ⇒ false// ❌ 这也区分不了typeofPropTypes.string.isRequired// 'function'(与 PropTypes.string 相同)String(PropTypes.string.isRequired)// 'function () { [native code] }'

⇒isRequired是"在声明时选用了 isRequired 版本的 validator",一旦拿到propTypes.X,
就无法再判断它当初是不是isRequired版本。
⇒ 可行的替代:缺参时看 LogBox 是否出现 “Failed prop type” 警告。

Q3:列宽怎么定?

优先级是columns[i].width>columnWidth(prop)>DEFAULT_COLUMN_WIDTH = 60:

<Table columns={[{title: '名称', dataIndex: 'name', width: 120}, // ← 最高优先 {title: '数量', dataIndex: 'count'}]} // ← 用 columnWidth 或 60 columnWidth={80} // ← 次优先 dataSource={DATA} />

Q4:表格高度怎么定?

heightprop(默认 240),写到contentContainerStyle上:

<Table columns={…} dataSource={…} height={300} />

Q5:没有renderCell时单元格怎么渲染?

走内部_renderCell:<View style={[styles.cell, {width}]}><Text>{cellData}</Text></View>⇒数据直接当文本渲染。
要自定义就传renderCell={(cellData, col) => <YourCell/>}。

Q6:为什么表头和数据格的x不一样?

因为它们是两套View:表头headerItem(minHeight: 30)与数据格cell(minHeight: 25),
两者都alignItems: 'center',但父容器不同 ⇒ 文字居中位置略有差异。
实测表头x=141、数据格x=184(同一列)。

Q7:它用ListView吗?

不用—— 它只用ScrollView(外层横向 + 内层纵向)。预检里ListView.DataSource与<ListView都是0 命中✓


小结

react-native-simple-table是极简表格(横向 ScrollView + 表头 + 纵向 ScrollView),
只有 2 个源文件、零依赖、零平台分支。

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

  1. ★★★★★propTypes.X.isRequired在运行时【不可检测】—— 这是本轮最重要的发现。
    PropTypes.string.isRequired是一个validator 函数,它身上没有.isRequired属性:

    typeof PropTypes.string.isRequired → 'function' PropTypes.string.isRequired.isRequired → undefined 'isRequired' in PropTypes.string.isRequired → false String(PropTypes.string.isRequired) → 'function () { [native code] }'

    ⇒所以!!propTypes.dataSource.isRequired永远是false⇒ 我那 2 条 FAIL在原理上就不可能通过✗
    ⇒要验证"必填"只能读源码,或用"缺参看 LogBox 警告"。

  2. ★★★★★Table是 class,所以断言prototype.render—— 一次全中。
    源码 L17class Table extends Component⇒typeof是function且prototype.render是 function ✓
    ⇒ 这次与另两档(forwardRef是 object、函数组件prototype是 undefined)正好构成第三档对照✓

  3. ★★★★★defaultProps的清单 + 精确值全中:
    5 项键名清单比对通过;columnWidth = 60、height = 240(都来自源码常量DEFAULT_COLUMN_WIDTH/DEFAULT_HEIGHT)、
    columns/dataSource是空数组、renderCell是undefined✓

  4. ★★★★ 渲染证据靠坐标而不是"存在性":
    三列表头y都是 1220、x= 141/441/711⇒ 证明"横向一行三格";
    三行数据y= 1303/1381/1459,间隔恰好 78⇒ 证明"逐行排布、行高一致" ✓✓
    ⇒比"文本都在"强得多。

  5. ★★★ 极简库的适配成本最低:2 个源文件、零依赖、零平台分支、
    连prepare脚本都没有 ⇒预检全部为空、构建一次成功✓

  6. ★★isRequired那两条 FAIL 也说明了另一个道理:
    “源码里明明写了” ≠ “运行时能读到”—— 写断言前要确认那个信息在运行时是否还保留✓


本篇用到的库

项内容
三方库react-native-simple-table(上游0.0.3的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-simple-table
适配 TAG0.0.3-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS)
库类型极简表格(横向 ScrollView + 表头 + 纵向 ScrollView)
运行时依赖✅✅dependencies/peerDependencies都为空
导出形态只有默认导出Table(class 组件,prototype.render存在)
默认值columnWidth: 60/height: 240/columns: []/dataSource: []/renderCell: undefined
⚠️ 注意propTypes.X.isRequired不可运行时检测(它是 validator 函数,没有.isRequired属性)
许可证Apache-2.0
上游仓库https://github.com/Jeepeng/react-native-simple-table
宿主工程RNOH084Demo(测试页rnAppKey = SimpleTableTestApp)
npminstall"git+https://atomgit.com/oh-react-native/react-native-simple-table.git#0.0.3-ohos-1.0.0"--ignore-scripts# 零依赖、无 prepare 脚本
import Table from 'react-native-simple-table'; const columns = [ {title: '名称', dataIndex: 'name', width: 120}, // ← width 优先 {title: '数量', dataIndex: 'count', width: 80}, {title: '备注', dataIndex: 'note', width: 100}, ]; const dataSource = [ {name: '苹果', count: 12, note: '红富士'}, {name: '香蕉', count: 8, note: '进口'}, ]; // ① 自定义列宽 + 自定义高度 <Table columns={columns} dataSource={dataSource} height={300} /> // ② 用默认列宽(60)与默认高度(240) <Table columns={[{title: '键', dataIndex: 'k'}, {title: '值', dataIndex: 'v'}]} dataSource={[{k: 'A', v: 1}]} /> // ③ 自定义单元格 <Table columns={columns} dataSource={dataSource} renderCell={(cellData, col) => ( <View style={{width: col.width || 60}}><Text>{String(cellData)}</Text></View> )} /> // ⚠️ Table 是 class(typeof 是 function 且 prototype.render 是 function) // ⚠️ 列宽优先级:col.width > columnWidth prop > DEFAULT_COLUMN_WIDTH(60) // ⚠️ height 默认 240;写到 contentContainerStyle // ⚠️ propTypes.X.isRequired 不可运行时检测(它是 validator 函数,没有 .isRequired 属性) // ⚠️ 不用 ListView,只用 ScrollView(外层 horizontal + 内层纵向) // ⚠️ 许可证是 Apache-2.0
# 换页启动测试页 —— force-stop 不能省hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey SimpleTableTestApp

判据的复现方式

# ① 契约(12 PASS / 2 FAIL,FAIL 是 isRequired 不可检测)hdc shell"hilog -x | grep stb-test"|Select-String'PASS|FAIL|SUMMARY|NOTE'# ⇒ Table 是 class + prototype.render 是 function# ⇒ propTypes/defaultProps 清单一致;columnWidth=60 / height=240# ② 文本与坐标(表头同行、数据行间隔 78)devecocli ui layout|Select-String'STB 名称|STB 数量|STB 备注|苹果|香蕉|樱桃'# ⇒ 表头 y=1220、x=141/441/711;数据 y=1303/1381/1459(间隔 78)

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony/@rnoh/react-native-openharmony0.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
本次构建assembleHap3 分 57 秒⇒ 一次成功(打包与构建退出码都检查了)
验证规模12 PASS / 2 FAIL(2 条 FAIL 是isRequired不可检测,原理上不可能通过);defaultProps清单 + 精确值 60/240 全中;4 个原型方法全在;文本 7/9 命中 + 行 y 间隔 78;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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询