实践进阶 - 基于React Native 鸿蒙跨平台开发第三方库组件库使用Markdown 文本渲染功能react-native-markdown-display
React Native Markdown Display是一款完全兼容CommonMark标准的Markdown渲染器,专为React Native设计。它使用原生组件而非WebView来渲染所有元素,支持自定义样式和组件覆写。该库修复了react-native-markdown-renderer的诸多问题,并增强了功能。安装简便,支持Yarn和NPM两种方式。组件提供丰富的配置选项,包括样式覆
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
React Native Markdown Display
这是一个100% 兼容 CommonMark 的渲染器,一款真正为 React Native 实现的 Markdown 渲染器。
它不是基于 WebView 的 Markdown 渲染器,而是使用原生组件渲染所有元素。这些组件可根据需要进行覆写和样式自定义。

与 react-native-markdown-renderer 的兼容性
本库旨在作为 react-native-markdown-renderer 的替代方案,修复了大量问题并做了功能增强。
由于新版样式规则的工作方式不同,可能需要对你的样式做少量调整,详见下方「如何设置样式」章节。
安装
Yarn
yarn add react-native-markdown-display
NPM
npm install -S react-native-markdown-display
快速开始
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown from 'react-native-markdown-display';
const copy = `# 一级标题 8-)
**这是一段粗体文本!**
这是普通文本
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
属性与方法
<Markdown> 组件支持以下常用属性:
| 属性 | 默认值 | 必填 | 说明 |
|---|---|---|---|
children |
无 | true |
要渲染的 Markdown 字符串,或预处理后的 AST 语法树 |
style |
默认样式 | false |
用于覆盖各类规则的样式对象,详见下方「规则与样式」 |
mergeStyle |
true |
false |
若为 true,传入的样式会与默认样式合并,而非完全覆盖 |
rules |
默认渲染规则 | false |
指定各类 Markdown 元素如何渲染,详见下方「规则」章节 |
onLinkPress |
默认调用 Linking.openURL(url) |
false |
链接点击回调,详见下方「链接处理」章节 |
debugPrintTree |
false |
false |
在控制台打印 AST 树,方便查看 Markdown 解析结构 |
其他较少使用的配置:
| 属性 | 默认值 | 必填 | 说明 |
|---|---|---|---|
renderer |
AstRenderer 实例 |
false |
自定义渲染器;使用自定义渲染器后,不能再用 rules 或 styles |
markdownit |
MarkdownIt 实例 |
false |
自定义 markdownit 实例,默认:MarkdownIt({typographer: true}) |
maxTopLevelChildren |
null |
false |
若设为数字,只渲染前 N 个顶级子元素,超出后渲染 topLevelMaxExceededItem |
topLevelMaxExceededItem |
<Text key="dotdotdot">...</Text> |
false |
超出最大数量时显示的内容,必须设置 key |
allowedImageHandlers |
['data:image/png;base64', 'data:image/gif;base64', 'data:image/jpeg;base64', 'https://', 'http://'] |
false |
只允许以此开头的图片地址,其他会拼接 defaultImageHandler |
defaultImageHandler |
http:// |
false |
用于补全不合法的图片 URL;设为 null 则直接不渲染该图片 |
语法支持
标题# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
分割线
上方文本
___
中间文本
---
下方文本
强调
**粗体文本**
__粗体文本__
*斜体文本*
_斜体文本_
~~删除线~~
引用块
> 引用可以嵌套…
>> …连续使用大于号即可…
> > > …也可以在符号间加空格。
列表
无序列表
+ 以 +、-、* 开头创建列表
+ 缩进 2 个空格即可创建子列表:
- 符号不同会强制新开一组列表:
* 列表项文本
+ 很长的列表项会自动换行
- 另一个列表项
+ 使用非常简单!
有序列表
1. 第一项
2. 第二项,很长文本自动换行
3. 第三项
指定起始序号:
57. foo
58. bar
| iOS | Android |
|---|---|
![]() |
![]() |
行内 `代码`
缩进式代码
// 注释
代码行 1
代码行 2
代码行 3
代码块围栏
示例文本…
语法高亮
```js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
表格
| 选项 | 说明 |
| ---- | ----------- |
| data | 数据文件路径 |
| engine | 模板引擎 |
| ext | 目标文件扩展名 |
右对齐列
| 选项 | 说明 |
| -----:| -----------:|
| data | 数据文件路径 |
| engine | 模板引擎 |
| ext | 目标文件扩展名 |
| iOS | Android |
|---|---|
![]() |
![]() |
[链接文字](https://www.google.com)
[带标题的链接](https://www.google.com "标题")
自动识别链接 https://www.google.com(需开启 linkify)
| iOS | Android |
|---|---|
![]() |
![]() |


也支持脚注式图片语法
![替代文字][id]
在文档下方定义 URL:
[id]: https://octodex.github.com/images/dojocat.jpg "标题"
| iOS | Android |
|---|---|
![]() |
![]() |
开启 typographer 后可见效果。
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
test.. test... test..... test?..... test!....
!!!!!! ???? ,, -- ---
"智能引号" 和 '单引号'
插件与扩展
可通过任意 markdown-it 兼容插件扩展语法,插件列表见:markdown-it 插件。
以下是集成示例:
第一步
找到新元素并集成插件,可使用 debugPrintTree 查看渲染规则:
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown, { MarkdownIt } from 'react-native-markdown-display';
import blockEmbedPlugin from 'markdown-it-block-embed';
const markdownItInstance =
MarkdownIt({typographer: true})
.use(blockEmbedPlugin, {
containerClassName: "video-embed"
});
const copy = `
# 标题
@[youtube](lJIrF4YjHfQ)
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown
debugPrintTree
markdownit={markdownItInstance}
>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
控制台会输出:
body
-heading1
--textgroup
---text
-video
并提示:
Warning, unknown render rule encountered: video. 'unknown' render rule used (by default, returns null - nothing rendered)
第二步
为新的 video 组件创建渲染规则和样式:
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown, { MarkdownIt } from 'react-native-markdown-display';
import blockEmbedPlugin from 'markdown-it-block-embed';
const markdownItInstance =
MarkdownIt({typographer: true})
.use(blockEmbedPlugin, {
containerClassName: "video-embed"
});
const copy = `
# 标题
@[youtube](lJIrF4YjHfQ)
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown
debugPrintTree
markdownit={markdownItInstance}
style={{
video: {
color: 'red',
}
}}
rules={{
video: (node, children, parent, styles) =>{
// 查看 node 信息判断要渲染哪种视频
console.log(node);
return (<Text key={node.key} style={styles.video}>
这里返回视频组件!
</Text>);
}
}}
>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
视频相关信息会挂在 node 上:
{type: "video", sourceType: "video", sourceInfo: {…}, sourceMeta: null, block: true, …}
attributes: {}
block: true
children: []
content: ""
index: 1
key: "rnmr_1720a98f540_video"
markup: "@[youtube](lJIrF4YjHfQ)"
sourceInfo:
service: YouTubeService
videoID: "lJIrF4YjHfQ"
...
其他调试方式
import Markdown, { MarkdownIt } from 'react-native-markdown-display';
import blockEmbedPlugin from 'markdown-it-block-embed';
const markdownItInstance =
MarkdownIt({typographer: true})
.use(blockEmbedPlugin, {
containerClassName: "video-embed"
});
const copy = `
# 标题
@[youtube](lJIrF4YjHfQ)
`;
// 查看本库使用的 AST 树
const astTree = markdownItInstance.parse(copy, {});
console.log(astTree);
// 查看生成的 HTML(本库不使用,仅作参考)
const html = markdownItInstance.render(copy);
console.log(html);
完整测试 Markdown
用于一次性测试所有样式与渲染:
标题
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
分割线
上方文本
___
中间文本
---
下方文本
强调
**粗体**
__粗体__
*斜体*
_斜体_
~~删除线~~
引用
> 可嵌套
>> 多层
> > > 带空格
列表
无序列表
+ 项目
+ 子项目
- 子项
有序列表
1. 第一项
2. 第二项
代码
行内 `code`
代码块
表格
| 选项 | 说明 |
| ---- | ----------- |
| a | b |
链接
[文字](url)
图片

规则与样式
如何设置样式
文本样式设计类似 CSS:全局样式作用于所有内容,下层可覆盖上层。
注意 text 样式:并非应用到所有文本,比如列表圆点就不受影响。
想统一修改所有文字颜色,应修改 body 样式。
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown from 'react-native-markdown-display';
const copy = `
这段文本会是红色小号字体,因为继承自 body 样式。
// 代码块
const cool = () => {};
更多小号文本
# 一级标题
## 二级标题
### 三级标题
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown
style={{
body: {color: 'red', fontSize: 10},
heading1: {color: 'purple'},
code_block: {color: 'black', fontSize: 14}
}}
>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
样式
样式用于覆盖各规则的默认样式,默认样式见:
默认样式
注意:默认会与默认样式合并,可通过 mergeStyle 关闭。
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar, StyleSheet } from 'react-native';
import Markdown from 'react-native-markdown-display';
const styles = StyleSheet.create({
heading1: {
fontSize: 32,
backgroundColor: '#000',
color: '#fff',
},
heading2: {
fontSize: 24,
},
});
const copy = `
# 一级标题
## 二级标题
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown style={styles}>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
规则
规则用于自定义元素渲染方式,默认规则见:
默认规则
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar, Text } from 'react-native';
import Markdown from 'react-native-markdown-display';
const rules = {
heading1: (node, children, parent, styles) =>
<Text key={node.key} style={[styles.heading, styles.heading1]}>
>> H1:"{children}"
</Text>,
heading2: (node, children, parent, styles) =>
<Text key={node.key} style={[styles.heading, styles.heading2]}>
>> H2:"{children}"
</Text>,
};
const copy = `
# 一级标题
## 二级标题
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown rules={rules}>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
所有规则及对应样式
| 渲染规则 | 样式名 |
|---|---|
body |
body |
heading1–heading6 |
heading1–heading6 |
hr |
hr |
strong |
strong |
em |
em |
s |
s |
blockquote |
blockquote |
bullet_list |
bullet_list |
ordered_list |
ordered_list |
list_item |
list_item(含伪类:ordered_list_icon、bullet_list_icon 等) |
code_inline |
code_inline |
code_block |
code_block |
fence |
fence |
table/thead/tbody/th/tr/td |
同名样式 |
link |
link |
blocklink |
blocklink |
image |
image |
text |
text |
paragraph |
paragraph |
链接处理
默认行为:使用 React Native Linking.openURL(url) 打开链接。
可通过两种方式自定义:
onLinkPress 回调import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown from 'react-native-markdown-display';
const copy = `[这是一个链接!](https://github.com/iamacup/react-native-markdown-display/)`;
const onLinkPress = (url) => {
if (url) {
// 自定义逻辑
return false;
}
// 返回 true 则继续用默认打开方式
return true
}
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{height: '100%'}}
>
<Markdown onLinkPress={onLinkPress}>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
自定义 link/blocklink 规则
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar, Text } from 'react-native';
import Markdown from 'react-native-markdown-display';
const copy = `[链接](url)`;
const rules = {
link: (node, children, parent, styles) => {
return (
<Text
key={node.key}
style={styles.link}
onPress={() => 自定义处理(node.attributes.href)}
>
{children}
</Text>
);
},
};
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView style={{height: '100%'}}>
<Markdown rules={rules}>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
禁用某些 Markdown 语法
可通过 markdownit.disable([...]) 禁用不需要的语法,适合移动端精简场景。
示例:禁用图片和链接:
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown, { MarkdownIt } from 'react-native-markdown-display';
const copy = `
# 标题正常显示
[但链接只会显示为普通文本](不会跳转)
`;
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView style={{height: '100%'}}>
<Markdown
markdownit={
MarkdownIt({typographer: true}).disable([ 'link', 'image' ])
}
>
{copy}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
可禁用的完整列表见:
https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js
预处理
如需在外部预处理 Markdown,可直接传入 AST 树:
import React from 'react';
import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
import Markdown, { MarkdownIt, tokensToAST, stringToTokens } from 'react-native-markdown-display';
const markdownItInstance = MarkdownIt({typographer: true});
const copy = `
# 标题
带**粗体**的文本
`;
const ast = tokensToAST(stringToTokens(copy, markdownItInstance))
const App: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView style={{height: '100%'}}>
<Markdown>
{ast}
</Markdown>
</ScrollView>
</SafeAreaView>
</>
);
};
export default App;
完整代码:
import React, { useState } from 'react';
import {
StyleSheet,
ScrollView,
View,
Text,
SafeAreaView,
TouchableOpacity,
Image,
Dimensions,
} from 'react-native';
import Markdown from 'react-native-markdown-display';
// Base64 Icons
const PRODUCT_ICONS = {
back: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1IDE1TDEwIDEyIDE1IDkiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==',
share: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4IDdDNy4yIDcgNy4yIDIxIDE4IDIxIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8cGF0aCBkPSJNMTggN0M3LjIgNyA3LjIgMjEgMTggMjEiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xOCA3QzcuMiA3IDcuMiAyMSAxOCAyMSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
favorite: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDIxLjVMMSAxMS4yQzEuNjIgOS41MiAzLjQgOC4yNSA1LjQyIDcuNzFDNy40NSA3LjE3IDkuNjMgNy40IDExLjQ1IDguM0wxMiA4LjU4TDEyLjU1IDguM0MxNC4zNyA3LjQgMTYuNTUgNy4xNyAxOC41OCA3LjcxdDIuODEgMi40M0wxMjIuNSAyMS41WiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
cart: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcgMkg1Yy0xLjEgMC0xLjk5LjktMS45OSAyTDUgMjFINyIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE5IDIwSDhjLTEuMSAwLTIgLjktMiAydjFjMCAuMS45IDEgMiAxaDExYzEuMSAwIDItLjkgMi0ydi0xYzAtMS4xLS45LTItMi0yeiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE3IDE2YTEgMSAwIDAgMCAxLTF2LTE0YTItMiAwIDAgMC0yLTJoLTRhMiAyIDAgMCAwLTIgMnYxNGMwIC41LjQ0IDEgMSAxaDJ6IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
star: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyLjVMMTQuMDkgNy41MkwxOS41IDEwLjQ4TDE1LjkxIDEzLjQ0TDE3LjE4IDE5LjA5TDEyIDE2LjIzTDYuODIgMTkuMDlMOC4wOSAxMy40NEwyLjY4IDEwLjQ4TDguMDkgNy41MkwxMiAyLjVaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
check: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTkgMTdsNC00LTQtNCIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K'
};
const ProductDetailScreen = () => {
const [isFavorite, setIsFavorite] = useState(false);
const [selectedImage, setSelectedImage] = useState(0);
// 商品详细信息 Markdown
const productDetailMarkdown = `
# 📱 iPhone 15 Pro Max
## 🔥 产品亮点
### 🎨 设计工艺
- **钛金属机身** - 更轻更坚固,质感出众
- **全新操作按钮** - 可自定义功能,操作更便捷
- **多彩配色** - 自然钛金属、蓝色钛金属、白色钛金属、黑色钛金属
### 📸 摄影系统
- **4800万像素主摄** - 全新传感器,画质更出色
- **5倍光学变焦** - 远摄能力大幅提升
- **电影模式** - 专业级视频拍摄体验
- **夜间模式** - 暗光环境下也能拍出好照片
### ⚡ 性能配置
- **A17 Pro芯片** - 业界首款3纳米制程
- **8GB运行内存** - 多任务处理更流畅
- **最高1TB存储** - 海量空间满足需求
- **USB-C接口** - 传输速度更快,兼容性更好
### 🔋 续航表现
- **视频播放最长29小时**
- **支持20W有线快充**
- **支持15W MagSafe无线充电**
- **支持7.5W Qi无线充电**
## 📦 包装清单
✅ iPhone 15 Pro Max
✅ USB-C 转 Lightning 数据线
✅ 文档资料
## 💡 使用技巧
### 拍照技巧
- 使用**人像模式**获得专业虚化效果
- 开启**HDR**功能提升画面动态范围
- 利用**实况照片**捕捉精彩瞬间
### 日常使用
- 设置**专注模式**提高工作效率
- 启用**低电量模式**延长续航时间
- 使用**快捷指令**自动化日常操作
## 🛡️ 售后保障
- **一年有限保修**
- **AppleCare+服务**(额外付费)
- **全球联保服务**
- **技术支持热线**
---
*价格:¥9,999起*
*库存:充足*
`;
const productImages = [
'https://images.unsplash.com/photo-1607503242738-4e5c709c6a07?w=400&h=400&fit=crop',
'https://images.unsplash.com/photo-1607504932500-bf4d8c09d6c9?w=400&h=400&fit=crop',
'https://images.unsplash.com/photo-1607502545814-d032a5c0f8cc?w=400&h=400&fit=crop',
'https://images.unsplash.com/photo-1607501499296-4aff2b67f8be?w=400&h=400&fit=crop'
];
const handleBack = () => {
console.log('返回上一页');
};
const handleShare = () => {
console.log('分享商品');
};
const handleFavorite = () => {
setIsFavorite(!isFavorite);
console.log('收藏状态:', !isFavorite);
};
const handleAddToCart = () => {
console.log('加入购物车');
};
const handleBuyNow = () => {
console.log('立即购买');
};
return (
<SafeAreaView style={styles.container}>
{/* 顶部导航栏 */}
<View style={styles.header}>
<TouchableOpacity style={styles.headerButton} onPress={handleBack}>
<Image source={{ uri: PRODUCT_ICONS.back }} style={styles.headerIcon} />
</TouchableOpacity>
<Text style={styles.headerTitle}>商品详情</Text>
<View style={styles.headerActions}>
<TouchableOpacity style={styles.headerButton} onPress={handleShare}>
<Image source={{ uri: PRODUCT_ICONS.share }} style={styles.headerIcon} />
</TouchableOpacity>
<TouchableOpacity style={styles.headerButton} onPress={handleFavorite}>
<Image
source={{ uri: PRODUCT_ICONS.favorite }}
style={[styles.headerIcon, isFavorite && styles.favoritedIcon]}
/>
</TouchableOpacity>
</View>
</View>
<ScrollView style={styles.scrollView}>
{/* 商品图片轮播 */}
<View style={styles.imageSection}>
<Image
source={{ uri: productImages[selectedImage] }}
style={styles.mainImage}
/>
<View style={styles.imageDots}>
{productImages.map((_, index) => (
<TouchableOpacity
key={index}
style={[
styles.dot,
selectedImage === index && styles.activeDot
]}
onPress={() => setSelectedImage(index)}
/>
))}
</View>
</View>
{/* 商品基本信息 */}
<View style={styles.infoSection}>
<Text style={styles.productTitle}>iPhone 15 Pro Max</Text>
<View style={styles.priceContainer}>
<Text style={styles.currentPrice}>¥9,999</Text>
<Text style={styles.originalPrice}>¥10,999</Text>
</View>
<View style={styles.ratingContainer}>
<View style={styles.stars}>
{[1, 2, 3, 4, 5].map((star) => (
<Image
key={star}
source={{ uri: PRODUCT_ICONS.star }}
style={styles.starIcon}
/>
))}
</View>
<Text style={styles.ratingText}>4.9 (1.2万+评价)</Text>
</View>
</View>
{/* 促销信息 */}
<View style={styles.promotionSection}>
<View style={styles.promotionTag}>
<Text style={styles.promotionText}>限时优惠</Text>
</View>
<Text style={styles.promotionDesc}>购买即享价值¥299的保护壳免费赠送</Text>
</View>
{/* 商品详情 Markdown 渲染 */}
<View style={styles.detailSection}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>商品详情</Text>
<View style={styles.divider} />
</View>
<View style={styles.markdownContainer}>
<Markdown style={markdownStyles}>
{productDetailMarkdown}
</Markdown>
</View>
</View>
{/* 规格参数 */}
<View style={styles.specSection}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>规格参数</Text>
<View style={styles.divider} />
</View>
<View style={styles.specList}>
<View style={styles.specItem}>
<Text style={styles.specKey}>品牌</Text>
<Text style={styles.specValue}>Apple</Text>
</View>
<View style={styles.specItem}>
<Text style={styles.specKey}>型号</Text>
<Text style={styles.specValue}>iPhone 15 Pro Max</Text>
</View>
<View style={styles.specItem}>
<Text style={styles.specKey}>存储容量</Text>
<Text style={styles.specValue}>256GB</Text>
</View>
<View style={styles.specItem}>
<Text style={styles.specKey}>颜色</Text>
<Text style={styles.specValue}>自然钛金属</Text>
</View>
<View style={styles.specItem}>
<Text style={styles.specKey}>屏幕尺寸</Text>
<Text style={styles.specValue}>6.7英寸</Text>
</View>
</View>
</View>
</ScrollView>
{/* 底部操作栏 */}
<View style={styles.bottomBar}>
<TouchableOpacity style={styles.cartButton} onPress={handleAddToCart}>
<Image source={{ uri: PRODUCT_ICONS.cart }} style={styles.barIcon} />
<Text style={styles.barButtonText}>加入购物车</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buyButton} onPress={handleBuyNow}>
<Text style={styles.buyButtonText}>立即购买</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
// Markdown 样式配置
const markdownStyles = {
heading1: {
fontSize: 24,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 12,
marginTop: 16,
},
heading2: {
fontSize: 20,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 10,
marginTop: 14,
},
heading3: {
fontSize: 18,
fontWeight: '600',
color: '#1a1a1a',
marginBottom: 8,
marginTop: 12,
},
text: {
fontSize: 16,
color: '#333333',
lineHeight: 24,
marginBottom: 8,
},
strong: {
fontWeight: 'bold',
color: '#1a1a1a',
},
em: {
fontStyle: 'italic',
},
bullet_list: {
paddingLeft: 20,
marginBottom: 12,
},
ordered_list: {
paddingLeft: 20,
marginBottom: 12,
},
listItem: {
fontSize: 16,
color: '#333333',
lineHeight: 24,
marginBottom: 4,
},
code_inline: {
backgroundColor: '#f0f0f0',
paddingHorizontal: 4,
paddingVertical: 2,
borderRadius: 4,
fontFamily: 'monospace',
},
fence: {
backgroundColor: '#f8f8f8',
padding: 12,
borderRadius: 8,
marginBottom: 12,
borderWidth: 1,
borderColor: '#e0e0e0',
},
blockquote: {
borderLeftWidth: 4,
borderLeftColor: '#2196F3',
paddingLeft: 16,
marginLeft: 8,
backgroundColor: '#f5f9ff',
paddingVertical: 8,
marginBottom: 12,
},
hr: {
height: 1,
backgroundColor: '#e0e0e0',
marginVertical: 16,
},
};
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#ffffff',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
headerButton: {
padding: 8,
borderRadius: 20,
},
headerIcon: {
width: 24,
height: 24,
tintColor: '#333333',
},
favoritedIcon: {
tintColor: '#FF4444',
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: '#1a1a1a',
},
headerActions: {
flexDirection: 'row',
alignItems: 'center',
},
scrollView: {
flex: 1,
},
imageSection: {
backgroundColor: '#ffffff',
padding: 16,
},
mainImage: {
width: width - 32,
height: width - 32,
borderRadius: 12,
resizeMode: 'cover',
},
imageDots: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 12,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#cccccc',
marginHorizontal: 4,
},
activeDot: {
backgroundColor: '#2196F3',
width: 20,
},
infoSection: {
backgroundColor: '#ffffff',
padding: 20,
marginTop: 8,
},
productTitle: {
fontSize: 22,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 12,
},
priceContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
currentPrice: {
fontSize: 24,
fontWeight: 'bold',
color: '#FF4444',
marginRight: 8,
},
originalPrice: {
fontSize: 16,
color: '#999999',
textDecorationLine: 'line-through',
},
ratingContainer: {
flexDirection: 'row',
alignItems: 'center',
},
stars: {
flexDirection: 'row',
marginRight: 8,
},
starIcon: {
width: 16,
height: 16,
tintColor: '#FFD700',
marginRight: 2,
},
ratingText: {
fontSize: 14,
color: '#666666',
},
promotionSection: {
backgroundColor: '#FFF8E1',
padding: 16,
margin: 16,
borderRadius: 12,
borderLeftWidth: 4,
borderLeftColor: '#FFC107',
},
promotionTag: {
backgroundColor: '#FFC107',
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
alignSelf: 'flex-start',
marginBottom: 8,
},
promotionText: {
fontSize: 12,
fontWeight: '600',
color: '#333333',
},
promotionDesc: {
fontSize: 14,
color: '#555555',
lineHeight: 20,
},
detailSection: {
backgroundColor: '#ffffff',
margin: 16,
borderRadius: 12,
padding: 20,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
specSection: {
backgroundColor: '#ffffff',
margin: 16,
borderRadius: 12,
padding: 20,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
marginBottom: 80,
},
sectionHeader: {
marginBottom: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#1a1a1a',
marginBottom: 12,
},
divider: {
height: 1,
backgroundColor: '#e0e0e0',
},
markdownContainer: {
backgroundColor: '#fafafa',
borderRadius: 8,
padding: 16,
},
specList: {
backgroundColor: '#fafafa',
borderRadius: 8,
padding: 16,
},
specItem: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#eeeeee',
},
specKey: {
fontSize: 16,
color: '#666666',
fontWeight: '500',
},
specValue: {
fontSize: 16,
color: '#333333',
fontWeight: '600',
},
bottomBar: {
flexDirection: 'row',
backgroundColor: '#ffffff',
paddingHorizontal: 16,
paddingVertical: 12,
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
elevation: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
cartButton: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f0f0f0',
borderRadius: 25,
paddingVertical: 14,
marginRight: 12,
},
buyButton: {
flex: 2,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#2196F3',
borderRadius: 25,
paddingVertical: 14,
},
barIcon: {
width: 20,
height: 20,
tintColor: '#333333',
marginRight: 8,
},
barButtonText: {
fontSize: 16,
fontWeight: '500',
color: '#333333',
},
buyButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#ffffff',
},
});
export default ProductDetailScreen;

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:


React Native Markdown Display是一款完全兼容CommonMark标准的Markdown渲染器,专为React Native设计。它使用原生组件而非WebView来渲染所有元素,支持自定义样式和组件覆写。该库修复了react-native-markdown-renderer的诸多问题,并增强了功能。安装简便,支持Yarn和NPM两种方式。组件提供丰富的配置选项,包括样式覆盖、链接处理、调试打印等。支持所有常见Markdown语法,如标题、列表、代码块、表格等,并配有详细的示例说明。开发者可以轻松集成到项目中,构建美观的Markdown展示界面。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐











所有评论(0)