vue2 SSE流式输出无法选择文字问题

430 阅读1分钟

当我们在开发流式输出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
morphdomDOM 替换直接操作 DOM适用于 SSR

推荐选择

  • Vue 2 兼容性最佳snabbdom(最接近 Vue 内部实现)``
  • 动态 HTML 更新diffDOMmorphdom

如果你的目标是优化 v-html 的更新,diffDOMmorphdom 可能是最佳选择。若需要更完整的虚拟 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>