大模型流式输出卡住了,教你一招搞定
前言
腊月二十四,南方小年夜,苦逼的前端程序媛还在搬砖,怎一个惨字了得。沸点都刷烂了,这不都沦落到自己写了😂
就这,产品经理还不放过我。
“前端,问答的时候复制按钮点不动啦!赶紧看看!”
好家伙,岂止是复制按钮用不了,页面直接卡住了好吗?
一通头脑风暴,发现是流式输出,界面频繁重绘导致的性能问题。
简简单单看个代码
<script setup lang="ts">
import { ref } from 'vue'
import MarkdownIt from 'markdown-it'
import data from './README.md?raw' // ?raw表示将资源引入为字符串
import { ElButton } from 'element-plus'
const md = new MarkdownIt()
let str = ref<string>(''),
content = ref(),
index = ref<number>(0)
function mockStream() {
let timer = setInterval(() => {
if (index.value >= data.length) {
clearInterval(timer)
return
}
str.value += data.substring(index.value, index.value + 1)
content.value = md.render(str.value)
index.value++;
}, 100)
}
</script>
<template>
<div>
<ElButton type="primary" @click="mockStream">START BASE RENDER</ElButton>
<div v-html="content"></div>
</div>
</template>
就会发现,这界面咱是选也选不了,点也点不动。究其原因,content一直在重新赋值导致dom频繁重绘。
想法
咱就是说,有没有什么办法能够让dom不全部重绘呢?
⭐⭐⭐增量渲染⭐⭐⭐
把dom转换成DomTree数组,依托vue的for循环更新机制去做增量渲染。
开搞
1. 把dom转化成DomTree
使用htmlparser2的parseDocument函数,一行代码就ok
content.value = parseDocument(md.render(str.value)).children
2. 递归DomTree,转成dom
import { defineComponent, h } from "vue";
// 递归渲染组件
export const RenderWrap = defineComponent({
name: 'RenderWrap',
props: {
node: {
type: Object,
required: true,
}
},
setup(props) {
return () => {
const { node } = props;
if (node.type === 'text') {
return node.data
} else {
return h(
node.tagName,
{ ...node.attribs },
node.children.map((child: any, index: number) =>
h(RenderWrap, { node: child, key: index })
)
)
}
}
}
})
3. 站在尤大的肩膀上
🐕🐕🐕哈哈哈🐕🐕🐕
其实是用v-for遍历生成的DomTree,使用RenderWrap组件再转成dom,借助vue的增量更新算法。
<RenderWrap v-for="(node, index) in content" :node="node" :key="index" />
这谁不得夸我是个大聪明啊😂
结语
这种方案虽说能够降低页面重绘的区域,但是遇到内容超多的段落时,整段都会重绘。
友友们要是有其他方案,可以交换学习一下哦😊
我是胡邹邹,持续潜水,偶尔冒泡。
最后,新人没有赞真的很容易放弃欸 /(ㄒoㄒ)/~~