React Native for OpenHarmony 实战:三方库 react-native-render-html 的鸿蒙化适配指南
本文记录把 react-native-render-html(把 HTML 渲染成 RN 组件树)适配到 HarmonyOS 的完整过程。
它做的事很专一:给一段 HTML 字符串,渲染成原生组件 —— 支持 p / h1~h6 / ul / ol / li / a / b / img / table 等等,还能用 tagsStyles 逐标签改样式、用 renderers 换掉某个标签的实现。
结论:可以用,而且验证方式特别"硬" —— HTML 渲染出来的文本、以及 dump 里的标签名,都能直接核对。
它有两个值得记下来的特点:依赖链有 22 个包(但全是纯 JS)、contentWidth 并不是布局约束。
★★★★★ 一、依赖:file: 接入不会装库自己的依赖(这次又踩到,但立刻处理了)
库里声明了 9 个 dependencies:
"ramda": "^0.27.2", "urijs": "^1.19.6", "prop-types": "^15.5.7",
"@types/ramda": "^0.27.40", "@types/urijs": "^1.19.15", "stringify-entities": "^3.1.0",
"@jsamr/counter-style": "^2.0.1", "@jsamr/react-native-li": "^2.3.0",
"@native-html/transient-render-engine": "11.2.3" ← ★ 精确锁定版本,它是核心渲染引擎
第一次 npm install 的结果:
added 1 package in 2s ← 只加了 file: 接入的那一个
ramda / urijs / stringify-entities / @jsamr/* / @native-html/transient-render-engine 全部 存在=False
⇒ file: 方式接入时,npm 不会安装库自己的 dependencies ✓(在另外两个库上也遇到过同一件事)
⇒ 修法:把库的 dependencies 原样合并进宿主的 package.json,再 npm install:
新增 8 个依赖:@jsamr/counter-style@^2.0.1 / @jsamr/react-native-li@^2.3.0 /
@native-html/transient-render-engine@11.2.3 / @types/ramda@^0.27.40 /
@types/urijs@^1.19.15 / ramda@^0.27.2 / stringify-entities@^3.1.0 / urijs@^1.19.6
npm install ⇒ added 22 packages in 32s
ramda 0.27.2 ✓ urijs 1.19.11 ✓ stringify-entities 3.1.0 ✓ prop-types 15.8.1 ✓
@jsamr/counter-style 2.0.2 ✓ @jsamr/react-native-li 2.3.1 ✓
@native-html/transient-render-engine 11.2.3 ✓(精确版本命中)
★★★ 好消息:整条依赖链全是纯 JS
核心引擎 @native-html/transient-render-engine@11.2.3 自己的依赖:
"@native-html/css-processor": "1.11.0", "csstype": "^3.0.9", "domelementtype": "^2.2.0",
"domhandler": "^4.2.2", "domutils": "^2.8.0", "htmlparser2": "^7.1.2", "ramda": "^0.27.2"
⇒ htmlparser2 / domhandler / domutils / csstype / ramda —— 都是纯 JS ✓
⇒ 没有 harmony/ 目录需求、没有原生模块 ✓ ⇒ 这条链不需要任何 HAR ✓
⚠️ 一个背景事实:上游仓库(
meliorence/react-native-render-html)已经归档(archived: true)。
好在这个库零原生依赖、依赖链也全在 npm 上,所以仍然可以直接用 ✓
★★★★★ 二、文本级判据:HTML 渲染出来的内容可以直接核对
这是这个库最好验证的地方:它渲染出的文本会长在布局 dump 里,于是"HTML 有没有被正确解析"变成了可以直接读的证据。
实测(5 段不同的 HTML):
| 实例 | HTML | dump 里读到的文本 | 尺寸 |
|---|---|---|---|
| E1 | <p>RHTML 段落一</p> | ✅ 「RHTML 段落一」 | w=1188 h=52 |
| E2 | <h3>RHTML 标题</h3><p>RHTML 正文</p> | ✅ 「RHTML 标题」+「RHTML 正文」 | h=94(标题)/ h=94(同层) |
| E3 | <ul><li>甲</li><li>乙</li><li>丙</li></ul> | ✅ 「甲」「乙」「丙」 | h=52 |
| E4 | <p><b>粗体</b> 与 <a href="…">链接</a></p> | ✅ 「粗体」「链接」 | h=52 |
| E5 | <p>RHTML 红字段落</p> + tagsStyles={{p:{fontSize:18}}} | ✅ 「RHTML 红字段落」 | h=45(字号 18 生效) |
⇒ 五种结构全部渲染成功 ✓✓
⇒ h3 的行高(94)明显大于 p 的行高(52) ⇒ 标题与正文的字号差异被正确应用(结构被正确解析的定量证据)✓
★★★★★ 额外发现:dump 里的 Custom#<标签名> 直接暴露 HTML 结构
在扫几何的时候发现,渲染出的容器以 HTML 标签名命名:
Custom#p [54,726,1266,775] ← <p> 段落
Custom#html [54,932,1266,1097] ← <html>
Custom#body [54,932,1266,1097] ← <body>
Custom#ul [54,932,1266,1097] ← <ul>
Custom#marked-list-item [54,932,1266,987] ← 列表项(带项目符号)
Custom#marked-list-item [54,987,1266,1042]
⇒ html → body → ul → marked-list-item 的层级完整保留 ✓✓
⇒ ⇒ 这是验证"HTML 结构有没有被正确解析"最直观的观测点:不仅能看到文本,还能看到标签层级 ✓
★★★★ 三、defaultFallbackFonts 三条精确命中 —— "Platform.select 带 default"的运行时证据
库里有三处字体族的平台判断(TRenderEngineProvider.js):
const defaultFallbackFonts = {
'sans-serif': Platform.select({ios: 'system', default: 'sans-serif'}),
monospace: Platform.select({ios: 'Menlo', default: 'monospace'}),
serif: Platform.select({ios: 'Times New Roman', default: 'serif'}),
};
⇒ 三处【全都带了 default】 ⇒ 鸿蒙上应该分别取到 'sans-serif' / 'monospace' / 'serif' ✓
实测(页面上直接读这个导出的常量):
NOTE | defaultFallbackFonts = {"sans-serif":"sans-serif","monospace":"monospace","serif":"serif"}
PASS | ★★★ fallback 字体 sans-serif = "sans-serif"(鸿蒙取 default)
PASS | ★★★ fallback 字体 monospace = "monospace"
PASS | ★★★ fallback 字体 serif = "serif"
⇒ 三条精确命中 ✓✓ —— 这是"Platform.select 写上 default 后在鸿蒙上安然落地"的运行时证据 ✓
⇒ 正面样本:Platform.select({ios: …, default: …}) 是该怎么写的写法 ✓
(另外两处平台分支:BRRenderer.js 的 isWeb = Platform.OS === 'web'、GenericPressable.js 的 if (Platform.OS === 'android') —— 鸿蒙上分别取 false 与走 else 分支,都不崩 ✓)
★★★★ 四、一个我预言错了的点:contentWidth 不是布局约束
我原本以为"contentWidth={300} ⇒ 渲染容器宽 300vp = 900px" —— 实测不是:
✗ 未匹配到宽 900px 的块
实测:Custom#p 的宽度是 1212px(= 我这一层的容器宽)
⇒ contentWidth 是"计算图片等尺寸时用的参考宽度",并不会把布局收窄到那个值 ✓
⇒ 实际用法:通常把它设为可用的内容宽度(例如屏宽减去两侧 padding),而不是"期望的容器宽度" ✓
⇒ 教训:prop 的语义要看文档/源码,不能按名字猜 —— 我把"content width"想成了"容器 width" ✗
★★★ 五、其余预检结论(都很干净)
| 预检项 | 结果 |
|---|---|
| 原生痕迹 | ✅ 无原生源文件;NativeModules / requireNativeComponent / TurboModule 全空 |
| 老写法九项 | ✅✅ 全部为空(createClass / React.PropTypes / 从 react 具名导入 / ViewPropTypes / 字符串 ref / this.refs. / mixins / componentWillReceiveProps / componentWillMount) |
deprecated-react-native-prop-types | ✅ 没有(这个库历史上打过该补丁,本轮预检专门查了 ⇒ 没有 ✓) |
measure() / measureInWindow / onLayout / findNodeHandle | ✅ 全空 |
useNativeDriver | ✅ 全空(这个库不做动画) |
| 平台分支 | ✅ 3 处 Platform.select 全带 default + 2 处二元判断 ⇒ 都不崩 |
| peerDependencies | ✅ 只有 react / react-native ⇒ 无原生 peer |
| 脚本 | ✅ 无 prepare(有 prepack,但 npm install 不跑它) |
具名导出与常量(反射实测):
NOTE | 具名导出个数 = 45
NOTE | 默认导出的 typeof = function
PASS | ★★ 11 个关键具名导出全部存在 | 缺失=(无)
NOTE | HTMLContentModel = {"block":"block","textual":"textual","mixed":"mixed","none":"none"}
NOTE | defaultHTMLElementModels 的键数 = 117 ← ★★★ 117 个 HTML 标签模型
PASS | ★★ defaultHTMLElementModels 含 p/h1/ul/li/a/b/br
NOTE | isDomElement({type:"tag"}) = true / isDomText({type:"text"}) = true
⇒ 45 个具名导出、117 个标签模型、HTMLContentModel 4 个取值 —— 都是可精确断言的事实 ✓
⇒ isDomElement / isDomText 传 {type:'tag'} / {type:'text'} 分别返回 true(与 domhandler 的约定一致)✓




六、接入与预检(要点汇总)
# ① 装库本身(file: 接入)
npm install "git+https://atomgit.com/oh-react-native/react-native-render-html.git#6.3.4-ohos-1.0.0" --ignore-scripts
# ② ★ 必须补装它自己的 9 个 dependencies(file: 接入不会自动装)
npm install --ignore-scripts \
ramda@^0.27.2 urijs@^1.19.6 prop-types@^15.5.7 stringify-entities@^3.1.0 \
@jsamr/counter-style@^2.0.1 @jsamr/react-native-li@^2.3.0 \
@native-html/transient-render-engine@11.2.3 \
@types/ramda@^0.27.40 @types/urijs@^1.19.15
更稳的做法:把库的
dependencies原样合并进你工程的package.json,再一次npm install—— 这样版本完全一致。
七、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | src/(TS 源码)+ lib/(commonjs + typescript,94 个 js 文件) ⇒ 源码可读 ✓;427 文件 |
spec.json | ⚠️ 缺 upstreamCommit;⚠️ 上游是 monorepo(directory: packages/render-html) |
validation | ⚠️ "build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0" + rnoh: "0.84.4"(verification: evidence/batch50/…)⇒ 两字段自相矛盾,且既不是当前宿主(0.84.1/0.84.3)也不是权威 0.88 基线 |
| ⚠️ 上游状态 | 仓库已归档(archived: true) |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
这个库没有任何已移除的写法(九项全空、连 deprecated-react-native-prop-types 都没有),
行为依赖 htmlparser2 系的纯 JS 解析 + RN 基础组件 ⇒ 结论跨基线成立 ✓
八、已知限制与本次验证边界
8.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★★★★ file: 接入不会装它自己的 9 个依赖 | 必须显式补装(含核心引擎 @native-html/transient-render-engine@11.2.3) |
★★★★ contentWidth 不是布局约束 | 它用于计算图片等尺寸;容器宽度仍由你的父容器决定(实测渲染块宽 = 容器宽) |
★★★ defaultFallbackFonts 在鸿蒙上是 sans-serif/monospace/serif | 因为三处 Platform.select 都带了 default ✓ |
★★★ dump 里能看到 Custom#<标签名> | Custom#html/Custom#body/Custom#ul/Custom#marked-list-item ⇒ 很好验证结构 |
| ★★ 具名导出 45 个 | 含 TRenderEngineProvider / RenderHTMLSource / TNodeRenderer / defaultHTMLElementModels(117 个标签模型)等 |
| ★★ 上游已归档 | meliorence/react-native-render-html 已 archived;好在零原生依赖、依赖链都在 npm 上 |
| ★ 图片/表格/自定义渲染器 | 支持,但本轮未验证(见下) |
8.2 本次验证的边界
- ★ 未验证
<img>(图片加载、contentWidth对图片尺寸的影响、imagesMaxWidth等); - ★ 未验证
<table>/<ol>/<blockquote>/<code>/<pre>等更多标签; - ★ 未验证
renderers自定义渲染器; - ★ 未验证
<a>的点击回调(onLinkPress); - 未验证
baseStyle/classesStyles/idsStyles; - 未验证
TRenderEngineProvider/RenderHTMLSource的直接用法(只断言了它们存在); - 未验证
domNodeToHTMLString的实际输出(只断言它是函数); - 未验证
defaultSystemFonts/defaultListStyleSpecs的内容; - 未验证
useNormalizedUrl/useIMGElementState等 hook; - 未验证 中文换行、超长 HTML、畸形 HTML 的容错;
- 未在
RN 0.88.0/RNOH 0.84.4上复验(交付包声明的基线); - 未测其它屏幕宽度;未做跨平台对照。
未改动库代码。
九、常见问题
Q1:它在 RN 0.84 上能跑吗?
能。 五种 HTML 结构(段落 / 标题+正文 / 无序列表 / 粗体+链接 / tagsStyles 覆盖)渲染出的文本全部能在布局 dump 里读到,
契约与常量判据全部通过,LogBox 无通知。
Q2:装完就报 Unable to resolve module @native-html/transient-render-engine?
因为 file: 接入不会安装库自己的 dependencies。把库的 9 个依赖显式补装(见第六节),
或者直接把它们合并进你的 package.json 再 npm install。
Q3:contentWidth 我该传多少?
传你实际可用的内容宽度(例如屏宽减两侧 padding)。⚠️ 它不会把布局收窄成那个值 ——
实测:我传 contentWidth={300},但渲染出的 Custom#p 宽度仍是 1212px(= 容器宽)。
Q4:怎么确认 HTML 有没有被正确解析?
两条路:
- 读渲染文本:布局 dump 里能搜到渲染出的文字(本次就是用这招核对 5 种结构的);
- 看容器名:dump 里能看到
Custom#html→Custom#body→Custom#ul→Custom#marked-list-item这样的标签层级 ✓
Q5:字体在鸿蒙上会取到什么?
defaultFallbackFonts 实测是:
{'sans-serif': 'sans-serif', monospace: 'monospace', serif: 'serif'}
(因为三处 Platform.select 都写了 default)✓ 如果你想用系统字体,可以在 systemFonts 里追加。
Q6:可以只渲染一小段 HTML 吗?
可以。最基本用法:
<RenderHTML contentWidth={width} source={{html: '<p>你好</p>'}} />
source 也支持 {uri: 'https://…'} 或直接传 DOM 节点(配合 RenderHTMLSource)。
Q7:这个库需要鸿蒙侧做什么特殊处理吗?
不需要。 纯 JS、零原生依赖、依赖链全在 npm 上;平台分支都带有 default 或安全落到 else。
小结
react-native-render-html 是把 HTML 渲染成 RN 组件树的成熟库。
它零原生依赖(9 个直接依赖 + 13 个传递依赖共 22 个包,全是纯 JS),老写法九项全空,
五种 HTML 结构渲染出的文本全部能在布局 dump 里核对,契约与常量判据全部通过,LogBox 无通知。
最值得记下来的是这几条:
-
★★★★★
file:接入不会装库自己的依赖 —— 这次是 9 个(含核心渲染引擎)。
第一次npm install只added 1 package;补装后added 22 packages⇒
@native-html/transient-render-engine@11.2.3(精确锁定版本)必须显式装上 ✓ -
★★★★★ 文本级判据让"HTML 是否被正确解析"变得可直接核对。
段落 / 标题 / 正文 / 列表三项 / 粗体 / 链接 /tagsStyles覆盖 —— 7 处文本全部在 dump 里读到 ✓
而且h3的行高(94)明显大于p(52) ⇒ 结构差异被正确应用 ✓ -
★★★★★ dump 里的
Custom#<标签名>是额外的结构观测点。
Custom#html → Custom#body → Custom#ul → Custom#marked-list-item⇒ 层级完整保留、可数可验 ✓ -
★★★★
defaultFallbackFonts三条精确命中 —— "Platform.select带default"的运行时证据。
实测{'sans-serif':'sans-serif', monospace:'monospace', serif:'serif'}✓
⇒Platform.select({ios: …, default: …})是"第四平台"上最稳的写法 ✓ -
★★★★ 我预言错了一个 prop 的语义:
contentWidth不是布局约束。
我按名字猜成"容器宽度 300vp = 900px",实测渲染块宽 1212px(= 容器宽) ✗
⇒ prop 的语义要看文档/源码,别按名字猜;它的实际用途是"计算图片等尺寸的参考宽度"。 -
★★★ 量化事实很值钱:45 个具名导出、117 个 HTML 标签模型、
HTMLContentModel4 个取值 ⇒
这些都是可精确断言的契约 ✓ -
★★ 老写法九项全空,连
deprecated-react-native-prop-types都没有。
(这个库历史上打过该补丁,所以本轮预检专门查了这一项 ⇒ 现在没有 ✓) -
★ 一个背景事实:上游已归档(
archived: true)。
好在它零原生依赖、依赖链都在 npm 上 ⇒ 仍然可用;但要有"上游不再更新"的心理准备 ✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-render-html(上游 6.3.4 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-render-html |
| 适配 TAG | 6.3.4-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(9 个直接依赖 + 13 个传递依赖全是纯 JS) |
| 库类型 | HTML 渲染器(HTML → RN 组件树) |
| 运行时依赖 | ⚠️ 9 个直接依赖必须显式补装(含 @native-html/transient-render-engine@11.2.3);无原生依赖 |
prepare / postinstall | ✅ 无(有 prepack,npm install 不跑) |
| 许可证 | BSD-2-Clause |
| 上游仓库 | https://github.com/meliorence/react-native-render-html(★3660,⚠️ 已归档) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = RenderHtmlTestApp) |
# ① 装库本身
npm install "git+https://atomgit.com/oh-react-native/react-native-render-html.git#6.3.4-ohos-1.0.0" --ignore-scripts
# ② ★ 必须补装它自己的 9 个 dependencies(file: 接入不会自动装)
npm install --ignore-scripts ramda@^0.27.2 urijs@^1.19.6 prop-types@^15.5.7 \
stringify-entities@^3.1.0 @jsamr/counter-style@^2.0.1 @jsamr/react-native-li@^2.3.0 \
@native-html/transient-render-engine@11.2.3 @types/ramda@^0.27.40 @types/urijs@^1.19.15
import RenderHTML from 'react-native-render-html';
// ① 基本用法(contentWidth 是"参考宽度",不是布局约束)
<RenderHTML
contentWidth={width} // 通常传可用内容宽度(如屏宽减 padding)
source={{html: '<p>你好,鸿蒙</p>'}}
/>
// ② 逐标签改样式
<RenderHTML
contentWidth={width}
source={{html: '<h3>标题</h3><p>正文</p>'}}
tagsStyles={{p: {color: '#D0021B', fontSize: 18}, h3: {fontSize: 22}}}
/>
// ③ 列表 / 行内标签都支持
<RenderHTML contentWidth={width}
source={{html: '<ul><li>甲</li><li>乙</li><li>丙</li></ul><p><b>粗体</b> 与 <a href="https://example.com">链接</a></p>'}} />
// ⚠️ file: 接入时不装它自己的依赖 ⇒ 必须补装 9 个(否则 Unable to resolve module)
// ⚠️ contentWidth 不是布局约束(实测渲染块宽 = 容器宽)
// ⚠️ defaultFallbackFonts 在鸿蒙上是 sans-serif / monospace / serif(三处 Platform.select 都带 default)
// ⚠️ 上游仓库已归档(archived: true)
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey RenderHtmlTestApp
判据的复现方式
# ① 契约 / 常量:逐条 PASS/FAIL 日志
hdc shell "hilog -x | grep rhtml-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ⇒ 具名导出 45 个、defaultHTMLElementModels 117 个键、
# defaultFallbackFonts = {"sans-serif":"sans-serif","monospace":"monospace","serif":"serif"}
# ② 渲染文本:在布局 dump 里直接搜文本
devecocli ui layout | Select-String 'RHTML'
# ⇒ 「RHTML 段落一」「RHTML 标题」「RHTML 正文」「RHTML 红字段落」
# ③ 渲染结构:dump 里的标签名
devecocli ui layout | Select-String 'Custom#(html|body|ul|p|marked-list-item)'
# ④ 注意:contentWidth 不改变布局宽度(渲染块宽 = 容器宽)
验证环境
| 项 | 版本 |
|---|---|
| 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 |
| 宿主依赖 | ⚠️ 需补装 9 个(合并进宿主 package.json 后共 added 22 packages) |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 3 分 58 秒(首次)+ 4 分 01 秒(接线后)⇒ 均一次成功 |
| 验证规模 | 契约/常量判据全 PASS(45 个具名导出、11 个关键导出、3 条 fallback 字体、4 个 content model、117 个标签模型、2 个类型守卫);7 处渲染文本全部在 dump 里读到;Custom#<标签> 层级可见;LogBox 零通知;4 张截图互异 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
本库无任何已移除写法、依赖全为纯 JS ⇒ 结论跨基线成立。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| 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)