# 示例
## 将计时器与长列表隔离
**场景:** 消息列表每秒重新渲染,因为计时器 (`elapsedMs`) 位于父组件中。这会导致大型列表出现明显的卡顿。
**目标:** 保持 UI 不变,但将重新渲染限制在计时器区域。
**之前(有问题的模式):**
tsx
function Messages({ items, isThinking, processingStartedAt }) {
const [elapsedMs, setElapsedMs] = useState(0);
useEffect(() => {
if (!isThinking || !processingStartedAt) {
setElapsedMs(0);
return;
}
setElapsedMs(Date.now() - processingStartedAt);
const interval = window.setInterval(() => {
setElapsedMs(Date.now() - processingStartedAt);
}, 1000);
return () => window.clearInterval(interval);
}, [isThinking, processingStartedAt]);
return (
{items.map((item) => (
))}
{formatDurationMs(elapsedMs)}
);
}
**之后(隔离频繁变动的状态):**
tsx
type WorkingIndicatorProps = {
isThinking: boolean;
processingStartedAt?: number | null;
};
const WorkingIndicator = memo(function WorkingIndicator({
isThinking,
processingStartedAt = null,
}: WorkingIndicatorProps) {
const [elapsedMs, setElapsedMs] = useState(0);
useEffect(() => {
if (!isThinking || !processingStartedAt) {
setElapsedMs(0);
return;
}
setElapsedMs(Date.now() - processingStartedAt);
const interval = window.setInterval(() => {
setElapsedMs(Date.now() - processingStartedAt);
}, 1000);
return () => window.clearInterval(interval);
}, [isThinking, processingStartedAt]);
return
{formatDurationMs(elapsedMs)}
;
});
function Messages({ items, isThinking, processingStartedAt }) {
return (
{items.map((item) => (
))}
);
}
**为什么有效:** 只有 `WorkingIndicator` 子树每秒重新渲染。除非属性发生变化,否则列表保持稳定。
**可选后续步骤:**
- 如果属性稳定,用 `memo` 包装 `MessageRow`。
- 对传递给行的处理函数使用 `useCallback` 以避免重新渲染。
- 如果列表非常大,考虑使用列表虚拟化。