From 3d164159acb6b8edfd2d962b3ed998365efec30d Mon Sep 17 00:00:00 2001 From: duxingchen Date: Thu, 6 Aug 2026 17:33:54 +0800 Subject: [PATCH] =?UTF-8?q?perf(frontend):=20=E9=80=92=E5=BD=92=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20Memo=20=E4=BC=98=E5=8C=96=20=E2=80=94=20React.memo?= =?UTF-8?q?=20+=20useMemo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TaskTreeViewer: - Modal/ReceiveConfirmModal/RejectModal/TransferModal 加 React.memo - TaskNodeCard 递归组件加 React.memo,阻断父节点状态变更引发的整树重渲染 - countAllTasks 用 useMemo 缓存,仅在 task_tree 引用变化时重新计算 TaskListCard: - TaskNode 递归组件加 React.memo - countAll 用 useMemo 缓存 --- .../components/TaskTree/TaskTreeViewer.tsx | 30 +++++++++++-------- frontend/src/components/scan/TaskListCard.tsx | 10 +++++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/TaskTree/TaskTreeViewer.tsx b/frontend/src/components/TaskTree/TaskTreeViewer.tsx index f0cba09..bb9dea3 100644 --- a/frontend/src/components/TaskTree/TaskTreeViewer.tsx +++ b/frontend/src/components/TaskTree/TaskTreeViewer.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, useMemo, memo } from "react"; import { Search, Loader2, @@ -75,7 +75,7 @@ function getStatusConfig(status: string) { // 通用 Modal 容器 // ============================================================ -function Modal({ +const Modal = memo(function Modal({ open, onClose, title, @@ -110,13 +110,13 @@ function Modal({ ); -} +}); // ============================================================ // 确认接收弹窗 // ============================================================ -function ReceiveConfirmModal({ +const ReceiveConfirmModal = memo(function ReceiveConfirmModal({ open, task, submitting, @@ -163,13 +163,13 @@ function ReceiveConfirmModal({ ); -} +}); // ============================================================ // 品质驳回弹窗 // ============================================================ -function RejectModal({ +const RejectModal = memo(function RejectModal({ open, task, submitting, @@ -244,13 +244,13 @@ function RejectModal({ ); -} +}); // ============================================================ // 完工裂变转交弹窗 // ============================================================ -function TransferModal({ +const TransferModal = memo(function TransferModal({ open, task, submitting, @@ -496,7 +496,7 @@ function TransferModal({ ); -} +}); // ============================================================ // 单个任务节点卡片 @@ -507,7 +507,7 @@ interface ModalTarget { action: "receive" | "reject" | "transfer"; } -function TaskNodeCard({ +const TaskNodeCard = memo(function TaskNodeCard({ task, isLast, onAction, @@ -653,7 +653,7 @@ function TaskNodeCard({ )} ); -} +}); // ============================================================ // 主容器组件 @@ -779,6 +779,12 @@ export default function TaskTreeViewer() { // ---- 渲染 ---- + // 🚀 缓存递归计算 — 仅在 task_tree 变化时重新计算 + const totalTaskCount = useMemo( + () => (product?.task_tree ? countAllTasks(product.task_tree) : 0), + [product?.task_tree], + ); + const modalTask = modalTarget?.task ?? null; return ( @@ -880,7 +886,7 @@ export default function TaskTreeViewer() {

{product.top_level_tasks?.length ?? 0} 顶层 {product.task_tree?.length - ? ` · ${countAllTasks(product.task_tree)} 总计` + ? ` · ${totalTaskCount} 总计` : ""}

diff --git a/frontend/src/components/scan/TaskListCard.tsx b/frontend/src/components/scan/TaskListCard.tsx index 84d5dec..c123d4e 100644 --- a/frontend/src/components/scan/TaskListCard.tsx +++ b/frontend/src/components/scan/TaskListCard.tsx @@ -1,4 +1,5 @@ /** 任务进度列表卡片 — 递归渲染 task_tree */ +import { useMemo, memo } from "react"; import { ClipboardList, GitBranch, AlertTriangle } from "lucide-react"; import type { TaskResponse, TaskSummary } from "../../types/api"; import { TASK_STATUS } from "../../types/api"; @@ -30,7 +31,7 @@ interface TaskNodeProps { depth: number; } -function TaskNode({ task, depth }: TaskNodeProps) { +const TaskNode = memo(function TaskNode({ task, depth }: TaskNodeProps) { return ( <>
@@ -62,19 +63,22 @@ function TaskNode({ task, depth }: TaskNodeProps) { ))} ); -} +}); function countAll(tasks: TaskResponse[]): number { return tasks.reduce((s, t) => s + 1 + (t.child_tasks ? countAll(t.child_tasks) : 0), 0); } export default function TaskListCard({ tasks }: TaskListCardProps) { + // 🚀 缓存递归计算 + const totalCount = useMemo(() => countAll(tasks), [tasks]); + return (

任务流转树

- {countAll(tasks)} 个任务 + {totalCount} 个任务
{tasks.length === 0 ? (
暂无关联任务