本文记录把 react-native-simple-markdown(把 Markdown 渲染成 RN 组件)适配到 HarmonyOS 的完整过程。

它做的事很专一:给一段 Markdown 源文,渲染成原生组件 —— 支持标题(h1~h6)、粗体、斜体、行内代码、列表、引用、链接、分隔线、表格等;还能用 whitelist/blacklist 裁剪规则、用 styles 覆盖样式。

结论:可以用,而且这个库"看起来老、代码其实很干净"(九项老写法全空)。
但它在 package.json 里埋了一个 git:// 协议的依赖,在今天的网络环境下必然装不上 —— 这是本文最值得记录的一件事(以及它的干净替代方案)。

★★★★★ 一、一个"必然装不上"的依赖,和它的干净替代

库的 package.json:

"dependencies": {
  "lodash": "^4.15.0",
  "simple-markdown": "git://github.com/CharlesMangwa/simple-markdown.git"     // ★★ 这里
}

两个问题叠加:

  1. git:// 协议 —— GitHub 早已停止支持这个匿名协议;
  2. 即使换成 https://,当前网络也访问不了 GitHub(此前实测过 Failed to connect to github.com port 443)。

⇒ npm install 一定失败 ✗

★★★ 好消息:它只用到了 simple-markdown 的 4 个公开 API

// index.js 的 _renderContent
const rules = this._postProcessRules(_.merge({}, SimpleMarkdown.defaultRules, initialRules(mergedStyles), this.props.rules));
const tree  = SimpleMarkdown.parserFor(rules)(blockSource, {inline: false});
return SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, 'react'))(tree);

⇒ 只用了 defaultRules / parserFor / ruleOutput / reactFor —— 全是 simple-markdown 的公开 API
⇒ 而 npm 上的官方包 simple-markdown@0.7.3(纯 JS、MIT) 同样提供这 4 个 ✓

⇒ 处置:把那条 git:// 换成 npm 的 ^0.7.3 ✓

⚠️ 这是本轮唯一改动库文件的地方(只改了 package.json 里的一行依赖声明,没动任何实现代码),在此如实记录。

★★★★★ 上设备之前,先在 Node 里把核心逻辑跑通

这一步很值:这个库的核心逻辑是纯 JS,可以先在 PC 上验证,拿到"设备上应该看到什么"的精确预言:

const SM = require('simple-markdown');
// 4 个 API 都在:
// defaultRules=object, parserFor=function, ruleOutput=function, reactFor=function
// defaultRules 规则数 = 29
// 键 = Array, autolink, blockQuote, br, codeBlock, def, del, em, escape, fence, heading, hr,
//      image, inlineCode, lheading, link, list, mailto, newline, nptable, paragraph,
//      refimage, reflink, strong, table, tableSeparator, text, u, url

const tree = SM.parserFor(SM.defaultRules)('# 标题\n\n**粗体** 与 `code`\n\n- 甲\n- 乙\n\n> 引用\n\n[链接](https://example.com)\n\n', {inline: false});
const el   = SM.reactFor(SM.ruleOutput(SM.defaultRules, 'react'))(tree);
// 提取纯文本 ⇒ "标题粗体 与 code甲乙引用链接"     ★ 完全正确

⇒ 上设备之前就确认了:换成 npm 版之后,解析与渲染链路是通的 ✓✓
⇒ 这对"依赖被替换过"的场景特别重要 —— 先证明替代品兼容,再去看设备表现 ✓

★★★★★ 二、文本级判据:13 处渲染文本全部可在 dump 里核对

和"HTML 渲染"那类库一样,Markdown 渲染出的文本会长在布局 dump 里,于是可以直接读证据。

实测(一个包含 9 种语法的文档;统一用 RSM 前缀避免与页面说明文字混淆):

Markdown 源dump 里读到的文本行高
# RSM 标题一✅ 「RSM 标题一」h=113
## RSM 标题二✅ 「RSM 标题二」h=84
正文含 **RSM 粗体**✅ 「RSM 粗体」h=49
正文含 *RSM 斜体*✅ 「RSM 斜体」h=49
正文含 `RSM 行内代码`✅ 「RSM 行内代码」h=55
- RSM 甲 / - RSM 乙✅ 「RSM 甲」「RSM 乙」h=49
> RSM 引用✅ 「RSM 引用」h=49
[RSM 链接](https://example.com)✅ 「RSM 链接」h=49
结尾 RSM 段落。✅ 「结尾 RSM 段落」h=49

⇒ 13 处 RSM 文本全部命中 ✓✓ —— 9 种 Markdown 语法全部被正确解析 ✓

★★★★★ 三、字号 ⇒ 行高:严格单调、线性对应(样式系统正确工作的最硬证据)

库的默认样式(styles.js)给了六个级别的标题字号:

heading1: {fontSize: 32}, heading2: {fontSize: 24}, heading3: {fontSize: 18},
heading4: {fontSize: 16}, heading5: {fontSize: 13}, heading6: {fontSize: 11},
text: {color: '#222222'},

实测行高:

来源fontSize实测行高
## RSM 标题二24h=84
# RSM 标题一32h=113
# RSM 大标题(E3 用 styles 覆盖成 40)40h=141
正文 / 列表 / 引用 / 链接默认h=49
行内代码(有边框+背景)默认h=55

⇒ 三次单调递增:84 < 113 < 141,与 24 < 32 < 40 严格对应 ✓✓
⇒ 而且是线性:84 × 32/24 = 112(实测 113)、84 × 40/24 = 140(实测 141)⇒ 只差 1px 取整 ✓✓
⇒ ⇒ 这说明 fontSize 这套样式在 RNOH 上是真的生效并被正确测量的 —— 比"渲染出来不崩"强得多 ✓

★★★★★ 四、whitelist 被双向证实(一个绝妙的观测点)

_postProcessRules 的实现:

_postProcessRules = (preRules) => {
  const defaultRules = ['paragraph', 'text'];          // 始终保留
  if (this.props.whitelist.length) {
    return _.pick(preRules, _.concat(this.props.whitelist, defaultRules));    // 只留白名单
  } else if (this.props.blacklist.length) {
    return _.omit(preRules, _.pullAll(this.props.blacklist, defaultRules));   // 剔除黑名单
  } else { return preRules; }
};

实测(whitelist={['heading']},源文 = # RSM WL 标题 + 含 **RSM WL 粗体** 的段落):

期望实测
heading 保留 ⇒ 标题正常渲染✅ 「RSM WL 标题」h=113(与默认 heading1 一致)
strong 被移除 ⇒ 粗体失效、** 变成字面文本✅ dump 里读到 *RSM —— 星号暴露出来了 ✓✓

⇒ 一个"反证"式的判据:规则被裁剪后,Markdown 标记会原样留在文本里 ✓
⇒ ⇒ 这比"看粗体有没有变粗"可靠得多(字形差异在 dump 里看不出来,而星号是文本的一部分)✓

★★★ 五、一个库内部的"样式键不一致"(不崩,但默认样式缺失)

index.js 和 rules.js 引用了几个 styles.js 里并不存在的键:

// index.js L79
<View style={[initialStyles.view, this.props.styles.view]}>       // ★ styles.js 里没有 view
// rules.js 中
style: [styles.blockQuoteSectionBar, styles.blockQuoteBar]        // ★ 没有 blockQuoteBar
style: styles.blockQuoteText                                      // ★ 没有 blockQuoteText
style: styles.br                                                  // ★ 没有 br

实测(直接读 defaultProps.styles 的键):

NOTE | initialStyles 的键数 = 31
PASS | ★★ 库内部不一致:styles.view 不存在(但 index.js 引用了它)
PASS | ★★ 库内部不一致:styles.blockQuoteBar 不存在(但 rules.js 引用了它)
PASS | ★★ 库内部不一致:styles.br 不存在(但 rules.js 引用了它)

⇒ RN 对 style 里的 undefined 是容忍的 ⇒ 不会崩 ✓ 只是这些位置的默认样式不生效 ✓
⇒ 实践含义:如果你依赖"引用块的左边竖条 / 换行高度"这些默认样式,需要自己在 styles 里补上这些键 ✓

★★★★ 六、其余预检结论(意外地干净)

预检项结果
老写法九项✅✅ 全部为空(createClass / React.PropTypes / 从 react 具名导入 / ViewPropTypes / deprecated-react-native-prop-types / 字符串 ref / this.refs. / mixins / componentWillReceiveProps / componentWillMount)
getDefaultProps / getInitialState✅ 空(用的是 static defaultProps)
平台分支✅ Platform.OS 一处都没有
measure() / onLayout / useNativeDriver / NativeModules✅ 全空
Text.propTypes / Text.style✅ 空(很多老库会踩 Text.propTypes,这个库没有)
写法class + static defaultProps + 3 个箭头类字段(_postProcessRules / _renderContent / shouldComponentUpdate)+ 原型 render

契约反射(全 PASS):

NOTE | Markdown 的 typeof = function
PASS | ★ prototype.render 存在
PASS | ★ prototype.shouldComponentUpdate 不存在(它是箭头类字段)
PASS | ★ prototype._renderContent 不存在(箭头类字段)
PASS | ★ prototype._postProcessRules 不存在(箭头类字段)
NOTE | defaultProps 的键 = blacklist, children, rules, styles, whitelist
PASS | ★ defaultProps 恰好 5 个键
PASS | ★ defaultProps.children = "" / rules = {} / whitelist = [] / blacklist = []
PASS | ★★ styles.heading1.fontSize = 32 / heading2 = 24 / text.color = #222222

⇒ "两类成员混用"又一次出现(3 个方法挂在实例上、render 挂在原型上)⇒
写断言前必须先看源码是哪种写法 ✓

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


七、接入与预检(要点)

# ① 装库本身(file: 接入)
npm install "git+https://atomgit.com/oh-react-native/react-native-simple-markdown.git#1.1.0-ohos-1.0.0" --ignore-scripts
# ② ★ 必须装它的依赖(file: 接入不会自动装)
npm install --ignore-scripts lodash@^4.17.21 simple-markdown@^0.7.3
# ③ ★★ 并且要把库里那条 git:// 依赖改掉(否则 npm install 会失败)
#    "simple-markdown": "git://github.com/CharlesMangwa/simple-markdown.git"  →  "^0.7.3"

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

类别结果
交付包内容index.js(2414 B)+ rules.js(6916 B)+ styles.js(2267 B) ⇒ 源码可读 ✓(13 文件)
spec.json⚠️ 缺 upstreamCommit
validation⚠️ "build-pass-device-pending"
⚠️ 声明的基线reactNative: "0.88.0" + rnoh: "0.84.4"(verification: evidence/batch50/…)⇒ 两字段自相矛盾,且既不是当前宿主(0.84.1/0.84.3)也不是权威 0.88 基线
⚠️ 我改动的文件只有 package.json 的一行依赖声明(git://… → ^0.7.3);实现代码零改动

关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
这个库没有任何已移除的写法(九项全空、无 Text.propTypes、无平台分支),
行为只依赖 simple-markdown 的纯 JS 解析 + RN 的 View/Text ⇒ 结论跨基线成立 ✓


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

9.1 用这个库要知道的

事项说明
★★★★ 它的 package.json 里有 git:// 依赖今天必然装不上 ⇒ 换成 npm 的 simple-markdown@^0.7.3(它只用 4 个公开 API,实测兼容)
★★★ 六种 Markdown 语法都验证过标题(h1/h2)、粗体、斜体、行内代码、列表、引用、链接、段落 ⇒ 全部渲染正确
★★★★ 字号与行高线性对应fontSize 24 ⇒ h=84、32 ⇒ h=113、40 ⇒ h=141 ⇒ 样式确实生效
★★★ whitelist/blacklist 真的会裁剪规则被裁掉的语法会把标记原样留在文本里(实测 ** 变成 * 可见)
★★ 几处样式键并不存在styles.view / blockQuoteBar / blockQuoteText / br ⇒ 不崩,但默认样式缺失,需要自己补
★★ blacklist 有副作用实现里用了 _.pullAll(this.props.blacklist, defaultRules) —— 会原地修改你传进来的数组 ⚠️
★ 两类成员混用_postProcessRules/_renderContent/shouldComponentUpdate 在实例上;render 在原型上
★ 链接是 Text + onPress: _.noop点击链接不会打开浏览器(rules.js 里 onPress: _.noop)
★ 代码块字体 'Courier'鸿蒙上可能没有该字体 ⇒ 回退到系统字体(不影响功能)

9.2 本次验证的边界

  1. ★ 未验证 <img> 图片渲染(styles.image 是 320×320 固定值);
  2. ★ 未验证表格(table / tableHeader / tableRow 规则都在 defaultRules 里,但我没渲染表格);
  3. ★ 未验证 blacklist(只验证了 whitelist;且 blacklist 有"原地修改入参"的副作用,需谨慎);
  4. ★ 未验证链接点击(onPress 是 _.noop,点了也不会打开浏览器);
  5. 未验证 引用块左侧竖条/换行的默认样式(因为对应样式键缺失);
  6. 未验证 rules 自定义规则(只用默认规则);
  7. 未验证 六级标题里的 h3~h6(只验证了 h1/h2 + 覆盖后的 40);
  8. 未验证 嵌套列表、任务列表、图片链接语法等;
  9. 未验证 超长文档、畸形 Markdown 的容错;
  10. 未在 RN 0.88.0 / RNOH 0.84.4 上复验(交付包声明的基线);
  11. 未测其它屏幕宽度;未做跨平台对照。

实现代码未改动(仅改了 package.json 的一行依赖声明)。


十、常见问题

Q1:它在 RN 0.84 上能跑吗?

能。 9 种 Markdown 语法渲染出的文本全部能在布局 dump 里读到;whitelist 与 styles 覆盖都按预期生效;
defaultProps/原型方法等契约判据全 PASS;LogBox 无通知。

Q2:npm install 卡在 simple-markdown 怎么办?

那是一条 git:// 协议的依赖,今天必然装不上(GitHub 已停用该协议,且本网络访问不了 GitHub)。
把它换成 npm 版:

// 库的 package.json
"simple-markdown": "^0.7.3"     // 原来是 git://github.com/CharlesMangwa/simple-markdown.git

理由:这个库只用到 defaultRules / parserFor / ruleOutput / reactFor 四个公开 API,
npm 版 0.7.3 全都提供(我还在 PC 上先用 Node 跑通了一次)。

Q3:怎么控制只渲染部分语法?

<Markdown whitelist={['heading']}># 标题</Markdown>   // 只保留 heading(paragraph/text 始终保留)
<Markdown blacklist={['image']}>…</Markdown>          // 剔除 image

⚠️ blacklist 会原地修改你传进来的数组(实现里用了 _.pullAll),建议传副本。

Q4:怎么改样式?

<Markdown styles={{heading1: {fontSize: 40}, text: {color: '#D0021B'}}}>…</Markdown>

styles 会与内置的 initialStyles 做浅合并(Object.assign({}, initialStyles, this.props.styles))。

Q5:为什么引用块没有左边的竖条?

因为 rules.js 引用的是 styles.blockQuoteBar / styles.blockQuoteText,而 styles.js 里没有这两个键 ⇒
它们被传成 undefined(不崩,但没有样式)。自己补上即可:

<Markdown styles={{blockQuoteBar: {width: 3, backgroundColor: '#ddd', marginRight: 15}, blockQuoteText: {flex: 1}}}>…</Markdown>

Q6:点链接为什么没反应?

rules.js 里链接的 onPress 是 _.noop(空函数)⇒ 这个库不处理链接跳转。
需要跳转就自定义 rules.link。

Q7:标题大小怎么对应?

内置 heading1~heading6 的 fontSize 分别是 32 / 24 / 18 / 16 / 13 / 11。
实测行高与字号线性对应(density 3 下:24⇒84px、32⇒113px、40⇒141px)。


小结

react-native-simple-markdown 是**“看起来老、代码其实很干净”**的 Markdown 渲染库(class + static defaultProps + 3 个箭头类字段),
九项老写法全空、无平台分支、无 Text.propTypes。设备侧 9 种语法渲染全部正确,whitelist 与 styles 覆盖都生效,LogBox 无通知。

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

  1. ★★★★★ 它埋了一条 git:// 依赖 —— 今天必然装不上。
    "simple-markdown": "git://github.com/CharlesMangwa/simple-markdown.git" ✗
    ⇒ 而它只用 4 个公开 API(defaultRules / parserFor / ruleOutput / reactFor)⇒
    换成 npm 的 simple-markdown@^0.7.3 即可 ✓(只改了一行依赖声明,实现代码零改动)

  2. ★★★★★ 上设备前先在 Node 里跑通核心逻辑。
    这个库的核心是纯 JS ⇒ 我在 PC 上用同一份 simple-markdown@0.7.3 跑了一遍解析+渲染,
    得到纯文本 "标题粗体 与 code甲乙引用链接" —— 上设备前就确认了替代品兼容 ✓
    ⇒ 对"依赖被替换过"的场景,这一步特别值得做。

  3. ★★★★★ 字号 ⇒ 行高严格单调、且线性对应。
    fontSize 24 ⇒ h=84、32 ⇒ h=113、40 ⇒ h=141 ✓
    而且 84×32/24=112(实测 113)、84×40/24=140(实测 141)⇒ 只差 1px 取整 ✓
    ⇒ 这是"样式系统在 RNOH 上真的生效"的最硬证据。

  4. ★★★★★ whitelist 用"星号反证"来验证,比看字形可靠。
    裁掉 strong 规则后,**RSM WL 粗体** 的标记原样留在文本里 ⇒ dump 里读到 *RSM ✓
    ⇒ 规则被裁剪这件事,用"标记是否漏出来"判断,比"粗体有没有变粗"在 dump 里更可测 ✓

  5. ★★★ 库内部几处样式键并不存在(styles.view / blockQuoteBar / blockQuoteText / br)。
    undefined 样式在 RN 里不崩,只是默认样式缺失 ⇒ 想用就自己补 ✓

  6. ★★★ 两类成员混用又要先看源码:3 个箭头类字段(实例上)+ render(原型上)✓

  7. ★★ 九项老写法全空 —— 包括很多老库会踩的 Text.propTypes / deprecated-react-native-prop-types。
    “库老"不等于"写法老”,预检要按事实查、不要按年代猜 ✓

  8. ⚠️ 两处要如实写:
    ① 我改动了库的 package.json 一行(依赖声明),实现代码未动;
    ② 我自己的 PowerShell 有个 bug(两个 -match 连用导致 $Matches 被后者覆盖)⇒ 修正后数据才出来 ✓


本篇用到的库

项内容
三方库react-native-simple-markdown(上游 1.1.0 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-simple-markdown
适配 TAG1.1.0-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS)
库类型Markdown 渲染器(Markdown → RN 组件)
运行时依赖⚠️ lodash(宿主已有)+ simple-markdown(原声明是 git://,需换成 npm 的 ^0.7.3)
prepare / postinstall✅ 无
许可证MIT
上游仓库https://github.com/CharlesMangwa/react-native-simple-markdown(★416)
宿主工程RNOH084Demo(测试页 rnAppKey = SimpleMarkdownTestApp)
# ① 装库
npm install "git+https://atomgit.com/oh-react-native/react-native-simple-markdown.git#1.1.0-ohos-1.0.0" --ignore-scripts
# ② 装依赖(file: 接入不会自动装)
npm install --ignore-scripts lodash@^4.17.21 simple-markdown@^0.7.3
# ③ 把库里那条 git:// 依赖改成 npm 版,否则 npm install 会失败
#    "simple-markdown": "git://github.com/CharlesMangwa/simple-markdown.git"  →  "^0.7.3"
import Markdown from 'react-native-simple-markdown';

// ① 基本用法(children 就是 Markdown 源文)
<Markdown>
  {'# 标题\n\n这是一段 **粗体** 与 `行内代码`。\n\n- 甲\n- 乙\n\n> 引用\n\n[链接](https://example.com)'}
</Markdown>

// ② 只保留部分语法
<Markdown whitelist={['heading']}>{'# 标题\n\n这段的 **粗体** 会被当纯文本(标记会漏出来)'}</Markdown>

// ③ 覆盖样式
<Markdown styles={{heading1: {fontSize: 40}, text: {color: '#D0021B'}}}>{'# 大标题\n\n正文'}</Markdown>

// ⚠️ package.json 里那条 git:// 依赖必须换成 ^0.7.3(否则装不上)
// ⚠️ blacklist 会原地修改你传入的数组(实现里用了 _.pullAll)
// ⚠️ 链接的 onPress 是 _.noop ⇒ 点了不会打开浏览器
// ⚠️ styles.view / blockQuoteBar / blockQuoteText / br 这几个键在 styles.js 里不存在
// ⚠️ 六级标题 fontSize:32 / 24 / 18 / 16 / 13 / 11
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey SimpleMarkdownTestApp

判据的复现方式

# ① 契约 / 样式键 / simple-markdown API:逐条 PASS/FAIL 日志
hdc shell "hilog -x | grep rsm-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
#    ⇒ defaultProps 5 键、styles.heading1.fontSize=32、initialStyles 31 键、
#      styles.view/blockQuoteBar/br 均不存在、simple-markdown 4 个 API 可用、defaultRules 29 条
# ② 渲染文本:在布局 dump 里直接搜
devecocli ui layout | Select-String 'RSM '
#    ⇒ RSM 标题一/标题二/正文/粗体/斜体/行内代码/甲/乙/引用/链接/结尾 RSM 段落
# ③ 字号 ⇒ 行高:heading2 84 / heading1 113 / 覆盖成 40 的那条 141
# ④ whitelist 反证:裁掉 strong 后,文本里会漏出 ** 标记

验证环境

项版本
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
宿主依赖⚠️ 需装 simple-markdown@0.7.3(替换 git:// 声明)+ lodash(已有)
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap 6 分 31 秒 ⇒ 一次成功(打包与构建退出码都检查了)
验证规模契约判据 25 条全 PASS;13 处渲染文本全部命中(9 种语法);字号⇒行高三档线性对应(84/113/141);whitelist 双向证实(标题生效 + 标记漏出);Node 侧预验证(纯文本 "标题粗体 与 code甲乙引用链接");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

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐