真正可复用的是结构转换逻辑+高亮触发时机的封装:①抽离parseMarkdownCodeBlocks()处理代码块转义与HTML生成;②封装DOM插入后高亮钩子,精准作用于新增code元素;③用自定义元素解耦生命周期,自动响应slot变更并按需高亮。

动态高亮不能靠重复写 hljs.highlightElement() 或反复调用 hljs.highlightAll() 来“复用”,真正可复用的是**结构转换逻辑 + 高亮触发时机的封装**。直接拷代码、改 selector,不出三天就维护不住。
✅ 把 Markdown 代码块转成 的逻辑抽成独立函数</H3><p>所有流式渲染场景(SSE、fetch ReadableStream、Vue/React 动态插入)都得先处理原始文本中的 <code><pre class='brush:lang;toolbar:false;'>
...
,再塞进 DOM。这个转换步骤必须剥离出来,避免每个页面都写一遍正则和 HTML 转义:
- 正则要支持空语言标签(
```)、多行内容、结尾换行可选
- 必须对
code 内容做四字符转义:、<code>>、&、",漏一个就可能破坏 DOM 或引发 XSS
- 返回值统一为合法 HTML 字符串,不操作 DOM,不依赖任何框架
- 示例函数名建议用
parseMarkdownCodeBlocks(),语义清晰,方便后续加单元测试
✅ 高亮动作封装为可配置的 DOM 插入后钩子
```)、多行内容、结尾换行可选code 内容做四字符转义:、<code>>、&、",漏一个就可能破坏 DOM 或引发 XSS
parseMarkdownCodeBlocks(),语义清晰,方便后续加单元测试不同场景插入方式不同(innerHTML、append()、v-html、ReactDOM.createRoot().render()),但共同点是:节点挂载完成 → 找到新插入的 <pre class="brush:php;toolbar:false;"><code></code> → 调用 <code>hljs.highlightElement()</code>。把这个流程包成一个函数:</pre>
- 接收两个参数:
containerEl(插入目标容器)和newHtml(已转换好的 HTML 字符串) - 内部用
insertAdjacentHTML('beforeend', newHtml)插入,再用containerEl.querySelectorAll('pre code')找出新增的code元素 - 对每个匹配元素检查是否已有
class,没有就补上language-plaintext,再调hljs.highlightElement(el) - 避免用
highlightAll()—— 它会重扫全页,性能差且可能重复高亮已处理过的节点
✅ 给自定义元素加高亮能力,彻底解耦 DOM 操作
如果项目里高频出现“带代码的富文本块”,直接封装成自定义元素最省心。比如定义 <highlight-code></highlight-code>,它自动监听 slotchange、解析 Markdown、转义、高亮,外部只管传内容:
- 内部用
this.attachShadow({ mode: 'open' })隔离样式,避免污染全局 - 用
<slot></slot>接收原始 Markdown 文本,不强制要求父级预处理 - 监听
slotchange后调用parseMarkdownCodeBlocks()+highlightElement(),整个流程闭环 - 支持属性控制行为,例如
lang="javascript"强制指定语言,no-escape跳过转义(仅限可信来源) - 这样连
hljs的引入都可以按需加载,不污染主包体积
最容易被忽略的是:高亮不是“一次调用就完事”,而是和 DOM 生命周期强绑定的。只要节点被 Vue/React 重新 render、或被 innerHTML 覆盖,之前的高亮就会丢失——所以复用的关键,从来不是怎么写高亮,而是怎么让高亮“跟着节点一起活下来”。
立即学习“前端免费学习笔记(深入)”;



















