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

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











