perf: wrap ToolCall, CodeBlock, ThinkingBlock, ImageBlock in React.memo
Prevents unnecessary re-renders of these frequently rendered child components when parent message list updates but individual props haven't changed.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, type HTMLAttributes, type ReactElement } from 'react';
|
import { useState, useCallback, memo, type HTMLAttributes, type ReactElement } from 'react';
|
||||||
import { Check, Copy, Hash, WrapText, AlignLeft, ChevronDown, ChevronUp } from 'lucide-react';
|
import { Check, Copy, Hash, WrapText, AlignLeft, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import { copyToClipboard } from '../lib/clipboard';
|
import { copyToClipboard } from '../lib/clipboard';
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ const LINE_THRESHOLD = 3; // Only show line numbers for blocks with more than th
|
|||||||
const COLLAPSE_THRESHOLD = 25; // Collapse code blocks longer than this
|
const COLLAPSE_THRESHOLD = 25; // Collapse code blocks longer than this
|
||||||
const COLLAPSE_PREVIEW_LINES = 10; // Lines to show when collapsed
|
const COLLAPSE_PREVIEW_LINES = 10; // Lines to show when collapsed
|
||||||
|
|
||||||
export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
export const CodeBlock = memo(function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [showLineNumbers, setShowLineNumbers] = useState(() => {
|
const [showLineNumbers, setShowLineNumbers] = useState(() => {
|
||||||
const stored = localStorage.getItem(LINE_NUMBER_KEY);
|
const stored = localStorage.getItem(LINE_NUMBER_KEY);
|
||||||
@@ -181,4 +181,4 @@ export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, memo } from 'react';
|
||||||
import { X, ImageOff } from 'lucide-react';
|
import { X, ImageOff } from 'lucide-react';
|
||||||
|
|
||||||
interface ImageBlockProps {
|
interface ImageBlockProps {
|
||||||
@@ -41,7 +41,7 @@ function Lightbox({ src, alt, onClose }: ImageBlockProps & { onClose: () => void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImageBlock({ src, alt }: ImageBlockProps) {
|
export const ImageBlock = memo(function ImageBlock({ src, alt }: ImageBlockProps) {
|
||||||
const [lightbox, setLightbox] = useState(false);
|
const [lightbox, setLightbox] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -81,6 +81,6 @@ export function ImageBlock({ src, alt }: ImageBlockProps) {
|
|||||||
{lightbox && <Lightbox src={src} alt={alt} onClose={() => setLightbox(false)} />}
|
{lightbox && <Lightbox src={src} alt={alt} onClose={() => setLightbox(false)} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
// buildImageSrc moved to ../lib/image.ts
|
// buildImageSrc moved to ../lib/image.ts
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useState } from 'react';
|
import { useState, memo } from 'react';
|
||||||
import { ChevronRight, ChevronDown, Brain } from 'lucide-react';
|
import { ChevronRight, ChevronDown, Brain } from 'lucide-react';
|
||||||
import { useT } from '../hooks/useLocale';
|
import { useT } from '../hooks/useLocale';
|
||||||
|
|
||||||
export function ThinkingBlock({ text }: { text: string }) {
|
export const ThinkingBlock = memo(function ThinkingBlock({ text }: { text: string }) {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
@@ -25,4 +25,4 @@ export function ThinkingBlock({ text }: { text: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
import { useState, useCallback, useMemo, useEffect, useRef, memo } from 'react';
|
||||||
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
|
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
|
||||||
import hljs from '../lib/highlight';
|
import hljs from '../lib/highlight';
|
||||||
import { copyToClipboard } from '../lib/clipboard';
|
import { copyToClipboard } from '../lib/clipboard';
|
||||||
@@ -260,7 +260,7 @@ function extractImageFromResult(result: string): { src: string; remaining: strin
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToolCall({ name, input, result }: { name: string; input?: Record<string, unknown>; result?: string }) {
|
export const ToolCall = memo(function ToolCall({ name, input, result }: { name: string; input?: Record<string, unknown>; result?: string }) {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [wrap, setWrap] = useState(true);
|
const [wrap, setWrap] = useState(true);
|
||||||
@@ -359,4 +359,4 @@ export function ToolCall({ name, input, result }: { name: string; input?: Record
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user