From 76b1ae157352c638f4e070816f9cbd36c61d8c08 Mon Sep 17 00:00:00 2001 From: kurihada Date: Mon, 15 Dec 2025 23:18:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20CodeBlock=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=98=B2=E6=8A=96=E4=BC=98=E5=8C=96=EF=BC=8C=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E8=BE=93=E5=87=BA=E6=97=B6=E7=9A=84=E9=AB=98?= =?UTF-8?q?=E4=BA=AE=E9=97=AA=E7=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 ref 跟踪上一次高亮的代码,避免重复高亮 - 添加 150ms 防抖延迟,减少流式输出时的高亮次数 - 优化渲染逻辑,未高亮时显示纯文本 --- packages/ui/src/components/CodeBlock.tsx | 55 +++++++++++++----------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/components/CodeBlock.tsx b/packages/ui/src/components/CodeBlock.tsx index 1f23a17..68eb014 100644 --- a/packages/ui/src/components/CodeBlock.tsx +++ b/packages/ui/src/components/CodeBlock.tsx @@ -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(''); - const [isLoading, setIsLoading] = useState(true); + // 使用 ref 跟踪上一次高亮的代码,避免不必要的重新高亮 + const lastHighlightedCode = useRef(''); + const highlightTimer = useRef | 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(`
${escapeHtml(code)}
`); - } - } finally { - if (!cancelled) { - setIsLoading(false); - } + lastHighlightedCode.current = code; + setHighlightedHtml(`
${escapeHtml(code)}
`); } - } - - 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 - {/* 代码内容 */} + {/* 代码内容 - 始终显示高亮后的内容,未高亮时显示纯文本 */}
- {isLoading ? ( -
-            {code}
-          
- ) : ( + {highlightedHtml ? (
+ ) : ( +
+            {code}
+          
)}