☰
React Native for OpenHarmony 实战:三方库 react-native-render-html 的鸿蒙化适配指南
2026/10/24 2:17:20 网站建设 项目流程

本文记录把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):

实例HTMLdump 里读到的文本尺寸
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):

constdefaultFallbackFonts={'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 个标签模型、HTMLContentModel4 个取值 —— 都是可精确断言的事实 ✓
⇒isDomElement/isDomText传{type:'tag'}/{type:'text'}分别返回true(与domhandler的约定一致)✓


六、接入与预检(要点汇总)

# ① 装库本身(file: 接入)npminstall"git+https://atomgit.com/oh-react-native/react-native-render-html.git#6.3.4-ohos-1.0.0"--ignore-scripts# ② ★ 必须补装它自己的 9 个 dependencies(file: 接入不会自动装)npminstall--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)

关于基线:本次验证在RN0.84.1+ RNOH0.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 本次验证的边界

  1. ★ 未验证<img>(图片加载、contentWidth对图片尺寸的影响、imagesMaxWidth等);
  2. ★ 未验证<table>/<ol>/<blockquote>/<code>/<pre>等更多标签;
  3. ★ 未验证renderers自定义渲染器;
  4. ★ 未验证<a>的点击回调(onLinkPress);
  5. 未验证baseStyle/classesStyles/idsStyles;
  6. 未验证TRenderEngineProvider/RenderHTMLSource的直接用法(只断言了它们存在);
  7. 未验证domNodeToHTMLString的实际输出(只断言它是函数);
  8. 未验证defaultSystemFonts/defaultListStyleSpecs的内容;
  9. 未验证useNormalizedUrl/useIMGElementState等 hook;
  10. 未验证中文换行、超长 HTML、畸形 HTML 的容错;
  11. 未在RN 0.88.0/RNOH 0.84.4上复验(交付包声明的基线);
  12. 未测其它屏幕宽度;未做跨平台对照。

未改动库代码。


九、常见问题

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 无通知。

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

  1. ★★★★★file:接入不会装库自己的依赖 —— 这次是 9 个(含核心渲染引擎)。
    第一次npm install只added 1 package;补装后added 22 packages⇒
    @native-html/transient-render-engine@11.2.3(精确锁定版本)必须显式装上✓

  2. ★★★★★ 文本级判据让"HTML 是否被正确解析"变得可直接核对。
    段落 / 标题 / 正文 / 列表三项 / 粗体 / 链接 /tagsStyles覆盖 ——7 处文本全部在 dump 里读到✓
    而且h3的行高(94)明显大于p(52)⇒ 结构差异被正确应用 ✓

  3. ★★★★★ dump 里的Custom#<标签名>是额外的结构观测点。
    Custom#html → Custom#body → Custom#ul → Custom#marked-list-item⇒层级完整保留、可数可验✓

  4. ★★★★defaultFallbackFonts三条精确命中 —— "Platform.select带default"的运行时证据。
    实测{'sans-serif':'sans-serif', monospace:'monospace', serif:'serif'}✓
    ⇒Platform.select({ios: …, default: …})是"第四平台"上最稳的写法✓

  5. ★★★★ 我预言错了一个 prop 的语义:contentWidth不是布局约束。
    我按名字猜成"容器宽度 300vp = 900px",实测渲染块宽1212px(= 容器宽)✗
    ⇒prop 的语义要看文档/源码,别按名字猜;它的实际用途是"计算图片等尺寸的参考宽度"。

  6. ★★★ 量化事实很值钱:45 个具名导出、117 个 HTML 标签模型、HTMLContentModel4 个取值 ⇒
    这些都是可精确断言的契约✓

  7. ★★ 老写法九项全空,连deprecated-react-native-prop-types都没有。
    (这个库历史上打过该补丁,所以本轮预检专门查了这一项 ⇒ 现在没有 ✓)

  8. ★ 一个背景事实:上游已归档(archived: true)。
    好在它零原生依赖、依赖链都在 npm 上 ⇒仍然可用;但要有"上游不再更新"的心理准备 ✓


本篇用到的库

项内容
三方库react-native-render-html(上游6.3.4的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-render-html
适配 TAG6.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)
# ① 装库本身npminstall"git+https://atomgit.com/oh-react-native/react-native-render-html.git#6.3.4-ohos-1.0.0"--ignore-scripts# ② ★ 必须补装它自己的 9 个 dependencies(file: 接入不会自动装)npminstall--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-bcom.rnoh084.demo-aEntryAbility--psrnAppKey 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 for OpenHarmony(RNOH)。从零准备开发环境(DevEco Studio、HarmonyOS SDK API 26、Node.js、ohpm、hvigorw、模拟器与hdc调试)请按 CPF-RN 开发者指南的「环境初始化」操作:

  • CPF-RN 开发者指南 · 02-搭建准备 · 环境初始化:https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md

环境就绪后,本次验证使用的具体版本组合见文末「验证环境」一节。


验证环境

项版本
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
宿主依赖⚠️需补装 9 个(合并进宿主 package.json 后共added 22 packages)
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap3 分 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

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

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

立即咨询