Files
MediaCrawler/webui/src/components/console/TerminalLine.tsx
T
butubb f4373c098e perf(console): 日志行加 memo —— 点折叠卡死就是这个
日志是「来一条 push 一条」,store 里的数组每来一条就是新的,于是整个列表重渲染;
而列表最多留 500 条,不做 memo 就是每来一行重渲染 500 行,每行还要跑 5 次 cn()
(tailwind-merge 不便宜)。爬虫一跑日志是成片来的,主线程直接被占满 —— 表现就是
点什么都没反应,点折叠最容易注意到,因为那一下还要把整份列表挂载/卸载一遍。

log 对象在数组里是稳定引用,memo 之后只有新来的那一行会渲染。
2026-10-11 08:16:41 +08:00

84 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { memo } from 'react'
import { cn } from '@/lib/utils'
import type { LogEntry } from '@/types/crawler'
interface TerminalLineProps {
log: LogEntry
}
const levelConfig: Record<string, { text: string; bg: string; glow: string }> = {
info: {
text: 'text-cyber-neon-cyan',
bg: 'bg-cyber-neon-cyan/10',
glow: 'shadow-[0_0_3px_rgba(0,255,255,0.3)]'
},
success: {
text: 'text-cyber-neon-green',
bg: 'bg-cyber-neon-green/10',
glow: 'shadow-[0_0_3px_rgba(0,255,65,0.3)]'
},
warning: {
text: 'text-cyber-neon-orange',
bg: 'bg-cyber-neon-orange/10',
glow: 'shadow-[0_0_3px_rgba(255,152,0,0.3)]'
},
error: {
text: 'text-cyber-neon-pink',
bg: 'bg-cyber-neon-pink/10',
glow: 'shadow-[0_0_3px_rgba(255,0,128,0.3)]'
},
debug: {
text: 'text-[#8b949e]',
bg: 'bg-[#21262d]',
glow: ''
},
}
const levelIcons: Record<string, string> = {
info: 'DATA',
success: 'OK',
warning: 'WARN',
error: 'ERR',
debug: 'DBG',
}
/**
* 一行日志。
*
* **必须 memo。** 日志是「来一条就 push 一条」,每来一条 store 里的数组就是新的,
* 于是整个列表重渲染;而这个列表最多留 500 条 —— 不做 memo 就是每来一行重渲染 500 行,
* 每行还要跑 5 次 `cn()`(tailwind-merge 不便宜)。爬虫在跑的时候日志是成片来的,
* 主线程直接被占满,表现就是**点什么都没反应**(点折叠最容易注意到,因为那一下还要
* 把整份列表挂载/卸载一遍)。
*
* `log` 对象在数组里是稳定引用,所以 memo 之后只有新来的那一行会渲染。
*/
export const TerminalLine = memo(function TerminalLine({ log }: TerminalLineProps) {
const config = levelConfig[log.level] || levelConfig.info
return (
<div className="flex gap-2 text-xs leading-relaxed font-mono group hover:bg-[#21262d]/50 px-1 -mx-1 rounded transition-colors">
{/* Timestamp */}
<span className="text-[#8b949e] flex-shrink-0 opacity-60 group-hover:opacity-100 transition-opacity">
[{log.timestamp}]
</span>
{/* Level badge */}
<span className={cn(
'flex-shrink-0 w-14 px-1 rounded text-center',
config.bg,
config.text,
config.glow
)}>
[{levelIcons[log.level]}]
</span>
{/* Message */}
<span className={cn('break-all', config.text)}>
{log.message}
</span>
</div>
)
})