
本文介绍在 react 中安全、高效地渲染含 `\n` 换行的代码类字符串,并实现关键词(如 `export`、`const`、`fadeanimation`)的语法着色,兼顾可维护性与性能。
在 React 中直接用 dangerouslySetInnerHTML 渲染含 HTML 标签的字符串虽快,但存在 XSS 风险且无法动态控制样式;而单纯对 \n 做 split().map() 处理(如原逻辑)虽安全,却难以进一步对子词着色。真正健壮的方案是:将字符串解析为带语义的 token 序列,再逐 token 渲染为带样式的 JSX 元素。
以下推荐两种生产就绪的实现方式,均避免内联 HTML 和字符串拼接:
✅ 方案一:预定义 Token 数组(推荐用于静态常量)
适用于内容固定、结构清晰的代码片段(如你的 fadeAnimation 示例):
// 定义带语义的 token 数组(支持嵌套换行与缩进)
export const fadeAnimationTokens: Array<{
type: 'keyword' | 'identifier' | 'punctuation' | 'comment' | 'newline';
text: string;
indent?: number; // 缩进层级(单位:px)
}> = [
{ type: 'keyword', text: 'export' },
{ type: 'keyword', text: 'const' },
{ type: 'identifier', text: 'fadeAnimation' },
{ type: 'punctuation', text: '=' },
{ type: 'newline', text: '' },
{ type: 'punctuation', text: '{' },
{ type: 'newline', text: '' },
{ type: 'indent', text: '', indent: 20 },
{ type: 'keyword', text: 'initial' },
{ type: 'punctuation', text: ':' },
{ type: 'punctuation', text: '{' },
// ... 后续 tokens 省略,按需补充
];对应渲染组件:
const CodeBlock = ({ tokens }: { tokens: typeof fadeAnimationTokens }) => {
return (
{tokens.map((token, i) => {
const baseStyle = { color: '#a69a9a' };
let style = { ...baseStyle };
switch (token.type) {
case 'keyword':
style.color = '#007acc'; // 蓝色:export/const/return 等
break;
case 'identifier':
style.color = '#333'; // 深灰:变量名、函数名
break;
case 'punctuation':
style.color = '#666'; // 灰色:{ } : , =
break;
case 'indent':
return ;
case 'newline':
return
;
default:
break;
}
return (
{token.text}
{token.type !== 'newline' && token.type !== 'indent' && ' '}
);
})}
);
};
// 使用
✅ 方案二:运行时正则分词(适合动态内容)
若字符串来自 props 或 API,可用轻量正则提取关键词并保留原始换行结构:
const highlightCode = (code: string): JSX.Element[] => {
// 先按 \n 分行,再对每行内关键词着色
return code.split('\n').map((line, lineIndex) => {
// 移除首尾空格后判断是否为空行
const trimmed = line.trim();
const indentWidth = line.length - trimmed.length;
// 匹配关键词(支持单词边界,避免误匹配)
const highlightedLine = trimmed
.replace(/\b(export|const|let|var|function|return|if|else|for|while)\b/g,
(match) => `${match}`)
.replace(/\b([a-zA-Z_$][\w$]*)\b(?=\s*:)/g,
(match) => `${match}`)
.replace(/([{}[\]();,])/g,
(match) => `${match}`);
return (
{lineIndex < code.split('\n').length - 1 &&
}
);
});
};
// 注意:使用 dangerouslySetInnerHTML 时务必确保 code 来源可信!
// 若不可信,应先用 DOMPurify 清洗 HTML 字符串。⚠️ 关键注意事项
- 永远避免在不可信输入上使用 dangerouslySetInnerHTML —— 方案一完全规避此风险,更安全;
- Token 数组法性能更优:无运行时正则开销,React 可精准 diff;
- CSS 类名优于内联 style:将 .keyword { color: #007acc; } 写入 CSS 文件,提升可维护性;
- 缩进处理要一致:建议用 pre + white-space: pre 或显式 marginLeft,避免空格塌陷;
- 考虑使用专业库:对于复杂语法高亮(如完整 JavaScript),推荐 react-syntax-highlighter 或 Prism。
通过结构化 token 或受控正则分词,你既能精准控制每个词的颜色与间距,又能保持 React 的声明式、可预测性优势——告别字符串拼接和样式混乱。










