如何在 React 中对带换行符的长字符串进行语法高亮渲染

本文介绍在 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 的声明式、可预测性优势——告别字符串拼接和样式混乱。