From 2936f1fca35891b7e36debf5330e268588e40abc Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 16 Apr 2026 10:25:00 +0800 Subject: [PATCH] perf: optimize MaterialTypeManagementDialog with memo, parallel fetch, and stable callbacks - Use Promise.all for parallel managers + records loading (async-parallel) - Wrap KeywordCard in memo to skip unnecessary list item re-renders - Stabilize handlers with useCallback + functional setState pattern - Hoist generateId to module scope to avoid per-render recreation Co-Authored-By: Claude Opus 4.6 --- .../MaterialTypeManagementDialog.tsx | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/renderer/src/components/MaterialTypeManagementDialog.tsx b/src/renderer/src/components/MaterialTypeManagementDialog.tsx index 6083c4e..0fc9263 100644 --- a/src/renderer/src/components/MaterialTypeManagementDialog.tsx +++ b/src/renderer/src/components/MaterialTypeManagementDialog.tsx @@ -6,7 +6,7 @@ * Regular users can only see and edit their own records. */ -import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react' +import React, { memo, useState, useEffect, useCallback, useRef, useMemo } from 'react' import { X, Plus, @@ -46,6 +46,9 @@ interface MaterialTypeManagementDialogProps { triggerRef?: React.RefObject } +// Stable ID generator for React keys +const generateId = () => Math.random().toString(36).substring(2, 9) + Date.now().toString(36); + // --- KeywordCard Component (Handles individual items) --- interface KeywordCardProps { item: RowState @@ -56,7 +59,7 @@ interface KeywordCardProps { onRestore: (localId: string) => void } -const KeywordCard: React.FC = ({ item, isAdmin, managers, onUpdate, onDelete, onRestore }) => { +const KeywordCard = memo(function KeywordCard({ item, isAdmin, managers, onUpdate, onDelete, onRestore }: KeywordCardProps) { const [isEditing, setIsEditing] = useState(item.state === 'new'); const [text, setText] = useState(item.record.materialName); const [manager, setManager] = useState(item.record.managerName); @@ -180,7 +183,7 @@ const KeywordCard: React.FC = ({ item, isAdmin, managers, onUp ); -}; +}); // --- Main Dialog Component --- @@ -206,13 +209,16 @@ export const MaterialTypeManagementDialog: React.FC Math.random().toString(36).substring(2, 9) + Date.now().toString(36); - const loadData = useCallback(async () => { setLoading(true) try { - const managersResult = await window.electron.materialType.getManagers() + const [managersResult, recordsResult] = await Promise.all([ + window.electron.materialType.getManagers(), + isAdmin + ? window.electron.materialType.getAll() + : window.electron.materialType.getByManager(currentUsername) + ]) + if (managersResult.success && managersResult.data) { setManagers(managersResult.data) if (isAdmin) { @@ -221,13 +227,7 @@ export const MaterialTypeManagementDialog: React.FC ({ @@ -266,7 +266,7 @@ export const MaterialTypeManagementDialog: React.FC { + const handleAdd = useCallback(() => { const newRow: RowState = { localId: generateId(), record: { materialName: '', managerName: isAdmin ? '' : currentUsername }, @@ -274,9 +274,9 @@ export const MaterialTypeManagementDialog: React.FC [newRow, ...prev]) setSearchQuery('') - } + }, [isAdmin, currentUsername]) - const handleUpdate = (localId: string, updates: Partial) => { + const handleUpdate = useCallback((localId: string, updates: Partial) => { setRows(prev => prev.map(row => { if (row.localId !== localId) return row; return { @@ -285,26 +285,25 @@ export const MaterialTypeManagementDialog: React.FC { + const handleDelete = useCallback((localId: string) => { setRows(prev => { const row = prev.find(r => r.localId === localId); if (!row) return prev; if (row.state === 'new') return prev.filter(r => r.localId !== localId); return prev.map(r => r.localId === localId ? { ...r, state: 'deleted' } : r); }) - } + }, []) - const handleRestore = (localId: string) => { + const handleRestore = useCallback((localId: string) => { setRows(prev => prev.map(r => { if (r.localId !== localId) return r; - // Determine if it was modified before deletion - const wasModified = r.originalRecord?.materialName !== r.record.materialName || + const wasModified = r.originalRecord?.materialName !== r.record.materialName || r.originalRecord?.managerName !== r.record.managerName; return { ...r, state: wasModified ? 'modified' : 'original' } })) - } + }, []) const handleReset = async () => { if (pendingCount === 0) return