fix(ui): CodeBlock 添加防抖优化,减少流式输出时的高亮闪烁

- 使用 ref 跟踪上一次高亮的代码,避免重复高亮
- 添加 150ms 防抖延迟,减少流式输出时的高亮次数
- 优化渲染逻辑,未高亮时显示纯文本
This commit is contained in:
2025-12-15 23:18:07 +08:00
parent 92619df026
commit 76b1ae1573
+30 -25
View File
@@ -4,7 +4,7 @@
* 代码块组件,支持语法高亮和一键复制
*/
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { Copy, Check } from 'lucide-react';
import { codeToHtml } from 'shiki';
import { cn } from '../utils/cn';
@@ -18,37 +18,42 @@ interface CodeBlockProps {
export function CodeBlock({ code, language = 'text', className }: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const [highlightedHtml, setHighlightedHtml] = useState<string>('');
const [isLoading, setIsLoading] = useState(true);
// 使用 ref 跟踪上一次高亮的代码,避免不必要的重新高亮
const lastHighlightedCode = useRef<string>('');
const highlightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
// 语法高亮
// 语法高亮 - 使用防抖避免频繁高亮导致闪烁
useEffect(() => {
let cancelled = false;
// 清除之前的定时器
if (highlightTimer.current) {
clearTimeout(highlightTimer.current);
}
async function highlight() {
// 如果代码没变,不需要重新高亮
if (code === lastHighlightedCode.current && highlightedHtml) {
return;
}
// 使用防抖:等待 150ms 后再进行高亮(流式输出时减少高亮次数)
highlightTimer.current = setTimeout(async () => {
try {
const html = await codeToHtml(code, {
lang: language,
theme: 'github-dark',
});
if (!cancelled) {
setHighlightedHtml(html);
}
lastHighlightedCode.current = code;
setHighlightedHtml(html);
} catch {
// 如果语言不支持,回退到纯文本
if (!cancelled) {
setHighlightedHtml(`<pre><code>${escapeHtml(code)}</code></pre>`);
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
lastHighlightedCode.current = code;
setHighlightedHtml(`<pre><code>${escapeHtml(code)}</code></pre>`);
}
}
highlight();
}, 150);
return () => {
cancelled = true;
if (highlightTimer.current) {
clearTimeout(highlightTimer.current);
}
};
}, [code, language]);
@@ -82,17 +87,17 @@ export function CodeBlock({ code, language = 'text', className }: CodeBlockProps
</button>
</div>
{/* 代码内容 */}
{/* 代码内容 - 始终显示高亮后的内容,未高亮时显示纯文本 */}
<div className="overflow-x-auto">
{isLoading ? (
<pre className="p-4 bg-code text-fg-secondary text-sm font-mono">
<code>{code}</code>
</pre>
) : (
{highlightedHtml ? (
<div
className="shiki-wrapper text-sm [&>pre]:p-4 [&>pre]:m-0 [&>pre]:bg-code"
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
/>
) : (
<pre className="p-4 bg-code text-fg-secondary text-sm font-mono">
<code>{code}</code>
</pre>
)}
</div>
</div>