
本文详解如何在 React 中正确解析字符串中的 换行符,并支持基于缩进(-)和序号(#)的列表语法,实现安全、可扩展的富文本自动渲染。
本文详解如何在 react 中正确解析字符串中的 ` ` 换行符,并支持基于缩进(`-`)和序号(`#`)的列表语法,实现安全、可扩展的富文本自动渲染。
在 React 中,直接在 JSX 属性中书写带转义字符的字符串(如 )时,
不会被 JavaScript 解析为实际换行符,而是作为字面量 \n 传入组件 —— 这是因为 JSX 属性值属于 HTML 字符串上下文,而非 JS 字符串字面量。而通过 JS 变量传入(如 text={'
Bubble
Air
Sky'})则能正常解析换行符。因此,关键前提始终是:确保传入组件的 text 值已是已解析的、含真实
字符的字符串。
以下是一个生产就绪的 FormattedText 组件,它接收纯文本(含
),并按规则自动转换为结构化 HTML:
class FormattedText extends React.Component {
formatText = (text) => {
if (!text || typeof text !== 'string') return '';
const lines = text.trim().split('
');
let result = [];
const ulStack = []; // 记录当前嵌套的无序列表层级
const olStack = []; // 记录当前嵌套的有序列表层级
lines.forEach((line) => {
const trimmed = line.trim();
if (!trimmed) return; // 跳过空行
// 无序列表:以一个或多个 '-' 开头,后跟空格和内容
if (/^-+s+/.test(line)) {
const level = line.match(/^-+/)[0].length;
const content = line.replace(/^-+s+/, '').trim();
// 关闭比当前层级深的所有
-
while (ulStack.length > level) {
result.push('
'); ulStack.pop(); } // 打开新层级的
- (若需要)
if (ulStack.length ');
ulStack.push(level);
}
result.push(`
- ${this.escapeHtml(content)} `); // 有序列表:以一个或多个 '#' 开头,后跟空格和内容 } else if (/^#+s+/.test(line)) { const level = line.match(/^#+/)[0].length; const content = line.replace(/^#+s+/, '').trim(); while (olStack.length > level) { result.push(''); olStack.pop(); } if (olStack.length '); olStack.push(level); } result.push(`
- ${this.escapeHtml(content)} `); // 普通段落:每行转为带
的内联文本(可替换为
标签以支持块级语义)
} else {
result.push(`${this.escapeHtml(trimmed)}
`);
}
});
// 清理未闭合的列表
while (ulStack.length > 0) {
result.push('
'); ulStack.pop(); } while (olStack.length > 0) { result.push(''); olStack.pop(); } return result.join(''); }; // 防止 XSS:对用户输入内容做基础 HTML 实体转义 escapeHtml = (str) => { return str .replace(/&/g, '&') .replace(/, '/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; render() { const { text } = this.props; return (
); } }
✅ 使用示例(注意:必须通过 JS 字符串变量传入,确保
已解析):
将选定的 Obsidian 笔记发布为静态站点并部署到 Cloudflare Pages。
const App = () => {
const example1 = '
Bubble
Air
SKY';
const example2 = '
-Bubble
-Air
-SKY';
const example3 = '
#Bubble
#Air
#SKY';
const example4 = '
-Bubble
--Air
---SKY';
return (
<div>
<h3>纯换行</h3>
<formattedtext text="{example1}"></formattedtext><h3>无序列表</h3>
<formattedtext text="{example2}"></formattedtext><h3>有序列表</h3>
<formattedtext text="{example3}"></formattedtext><h3>嵌套列表</h3>
<formattedtext text="{example4}"></formattedtext>
</div>
);
};
⚠️ 重要注意事项:
-
dangerouslySetInnerHTML存在 XSS 风险,绝不可直接渲染未经清洗的用户输入。本例中已内置escapeHtml()方法对,>,&等关键字符转义,但复杂场景建议使用DOMPurify进行完整 HTML 净化。 - 若需支持更多 Markdown 语法(如粗体、链接、代码块),推荐集成成熟库如
react-markdown或marked,而非手动扩展正则逻辑。 - JSX 属性中无法原生解析
,务必避免写法;应统一通过变量或模板字符串传入:text={A B}。
该方案兼顾简洁性与可维护性,适用于轻量级文档展示、配置说明、后台管理文案等场景。










