fix(ui): CodeBlock 添加防抖优化,减少流式输出时的高亮闪烁
- 使用 ref 跟踪上一次高亮的代码,避免重复高亮 - 添加 150ms 防抖延迟,减少流式输出时的高亮次数 - 优化渲染逻辑,未高亮时显示纯文本
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user