React中用highlight.js实现带行号代码高亮,修复多行SQL高亮异常
问题分析与修复方案
看起来你遇到的核心问题是逐行调用highlightBlock破坏了代码的上下文关联——SQL这类语言的语法高亮依赖整个代码块的上下文(比如跨多行的语句、关键字的延续),单独处理每一行时,HLJS无法识别孤立行的语法角色,导致除第一行外的其他行无法正确高亮。
下面给你两个可行的修复思路,从修改现有代码到更优雅的重构都有:
方案一:先整体高亮,再拆分到行(适配现有表格结构)
这个思路是先让HLJS处理完整的代码块,生成带高亮标记的HTML,再把这些高亮后的内容拆分成行,放到你的表格里。这样每一行都会保留正确的高亮样式,同时不改变你现有的UI结构。
修改后的代码示例:
if (data.success === 1) { const targetLang = translateLanguage(data.code.language); setCodeLanguage(targetLang); // 第一步:整体高亮完整代码,获取带高亮标签的HTML const highlightedCode = hljs.highlight(data.code.source_code, { language: targetLang }).value; // 第二步:把高亮后的代码按换行拆分(注意保留所有HTML标签) const highlightedLines = highlightedCode.split('\n'); // 第三步:生成表格行,用dangerouslySetInnerHTML插入高亮内容 const rows = highlightedLines.map((line, index) => ( <tr key={index} className="line"> <td className="line-number">{index + 1}</td> <td id={"plus" + index} className="plus-square-line"> <PlusSquareTwoTone className="plus-square" /> </td> <td id={"codeblock" + index} className={'language-' + targetLang} style={rowStyle} dangerouslySetInnerHTML={{ __html: line }} /> </tr> )); setCodeRows(rows); // 这里不需要再循环调用highlightBlock了! }
注意事项:
- 使用
dangerouslySetInnerHTML时,要确保你的代码来源是可信的(比如自己后端返回的代码,不是用户提交的未过滤内容),避免XSS攻击风险。如果需要处理用户提交的代码,建议先做HTML转义,再交给HLJS处理。
方案二:用CSS实现行号,避免逐行拆分(更优雅高效)
如果可以调整UI实现方式,推荐用CSS计数器生成行号,这样不需要把代码拆分成表格行,直接让HLJS处理整个代码块,既保证高亮正确,又简化代码结构。
实现步骤:
- 调整HTML结构:
if (data.success === 1) { const targetLang = translateLanguage(data.code.language); setCodeLanguage(targetLang); setFullCode(data.code.source_code); // 保存完整代码 } // 渲染时的结构 <div className="code-with-lines"> <pre> <code className={`language-${codeLanguage}`}>{fullCode}</code> </pre> </div>
- 添加CSS样式实现行号:
.code-with-lines { counter-reset: line-counter; padding-left: 2.5em; /* 给行号留出空间 */ } .code-with-lines pre code { display: block; counter-increment: line-counter; } .code-with-lines pre code::before { content: counter(line-counter); display: inline-block; width: 2em; margin-left: -2.5em; /* 把行号拉到预留空间里 */ text-align: right; color: #888; /* 行号的灰色样式,和GitHub一致 */ padding-right: 0.5em; border-right: 1px solid #eee; margin-right: 0.5em; }
- 高亮整个代码块:
// 组件挂载或代码更新时调用 useEffect(() => { if (fullCode && codeLanguage) { hljs.highlightBlock(document.querySelector(`.language-${codeLanguage}`)); } }, [fullCode, codeLanguage]);
这个方案的优势:
- HLJS处理完整代码块,上下文完整,高亮100%正确;
- 代码结构更简洁,没有表格拆分的冗余;
- 性能更好,不需要循环处理每一行;
- 行号样式更容易调整,和GitHub的风格更接近。
为什么原来的方式不行?
HLJS的highlightBlock是针对完整代码块设计的,它会分析整个代码的语法结构(比如SQL中的SELECT ... FROM ... WHERE是一个完整的语句链)。当你把代码拆成单独的行后,每一行都是孤立的“片段”,HLJS无法识别这些片段属于完整SQL语句的一部分,自然无法正确高亮后续行的语法元素。
内容的提问来源于stack exchange,提问作者Alex Popa
相关产品推荐
相关产品推荐

