当我们在开发流式输出AI对话的功能时一般我们会这么写
<template>
<div v-html="parsedContent" class="markdown-content"></div>
</template>
<script>
import MarkdownIt from 'markdown-it';
var md = new MarkdownIt();
export default {
data() {
return {
parsedContent: ''
}
},
methods: {
onGPTMessage(md) {
this.parsedContent = md.render(md);
}
},
mounted() {
this.$gpt.onMessage(this.onGPTMessage)
}
}
</script>
这样写的话,每次更新内容,都会重新渲染整个内容区域,而且不会经过vue的diff算法优化,相当于所有的元素都重建了。所以当我们选中文本时,就会出现无法选中的效果。
解决方案
出现这个问题的原因是,在vue2中v-html不会经过diff算法更新,而是直接替换innerHTML,那么我们顺着这个思路来解决这个问题:
1、考虑使用vue的patch方法渲染新老dom ❌
vue没有暴露该方法😭
2、使用三方diff算法
| 库名 | 适用场景 | 特点 | 类似 Vue 的 ****patch |
|---|---|---|---|
snabbdom | 虚拟 DOM 更新 | 轻量、模块化 | 最接近 Vue 2 |
diffDOM | 直接 DOM 对比 | 无虚拟 DOM | 适用于动态 HTML |
morphdom | DOM 替换 | 直接操作 DOM | 适用于 SSR |
推荐选择
- Vue 2 兼容性最佳 →
snabbdom(最接近 Vue 内部实现)`` - 动态 HTML 更新 →
diffDOM或morphdom
如果你的目标是优化 v-html 的更新,diffDOM 或 morphdom 可能是最佳选择。若需要更完整的虚拟 DOM 方案,snabbdom 是最接近 Vue 2 的替代方案。
最终方案
<template>
<div ref="htmlcontainer" class="markdown-content"></div>
</template>
<script>
import MarkdownIt from 'markdown-it';
import morphdom from 'morphdom';
var md = new MarkdownIt();
export default {
data() {
return {
parsedContent: ''
}
},
methods: {
updateDom(html) {
const oldElement = this.$refs.htmlcontainer;
const newElement = document.createElement('div');
newElement.className = 'markdown-content';
newElement.innerHTML = html;
morphdom(oldElement, newElement); // 智能更新 DOM
},
onGPTMessage(md) {
const parsedContent = md.render(md);
this.updateDom(parsedContent)
}
},
mounted() {
this.$gpt.onMessage(this.onGPTMessage)
}
}
</script>