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 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-04-16 10:25:00 +08:00
parent f57fdf69f7
commit 2936f1fca3

View File

@@ -6,7 +6,7 @@
* Regular users can only see and edit their own records. * 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 { import {
X, X,
Plus, Plus,
@@ -46,6 +46,9 @@ interface MaterialTypeManagementDialogProps {
triggerRef?: React.RefObject<HTMLButtonElement | null> triggerRef?: React.RefObject<HTMLButtonElement | null>
} }
// Stable ID generator for React keys
const generateId = () => Math.random().toString(36).substring(2, 9) + Date.now().toString(36);
// --- KeywordCard Component (Handles individual items) --- // --- KeywordCard Component (Handles individual items) ---
interface KeywordCardProps { interface KeywordCardProps {
item: RowState item: RowState
@@ -56,7 +59,7 @@ interface KeywordCardProps {
onRestore: (localId: string) => void onRestore: (localId: string) => void
} }
const KeywordCard: React.FC<KeywordCardProps> = ({ 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 [isEditing, setIsEditing] = useState(item.state === 'new');
const [text, setText] = useState(item.record.materialName); const [text, setText] = useState(item.record.materialName);
const [manager, setManager] = useState(item.record.managerName); const [manager, setManager] = useState(item.record.managerName);
@@ -180,7 +183,7 @@ const KeywordCard: React.FC<KeywordCardProps> = ({ item, isAdmin, managers, onUp
</div> </div>
</div> </div>
); );
}; });
// --- Main Dialog Component --- // --- Main Dialog Component ---
@@ -206,13 +209,16 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
).length ).length
const isSynced = pendingCount === 0; const isSynced = pendingCount === 0;
// UUID Generator for stable React keys
const generateId = () => Math.random().toString(36).substring(2, 9) + Date.now().toString(36);
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
setLoading(true) setLoading(true)
try { 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) { if (managersResult.success && managersResult.data) {
setManagers(managersResult.data) setManagers(managersResult.data)
if (isAdmin) { if (isAdmin) {
@@ -221,13 +227,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
} }
let records: MaterialTypeRecord[] = [] let records: MaterialTypeRecord[] = []
if (isAdmin) { if (recordsResult.success && recordsResult.data) records = recordsResult.data
const result = await window.electron.materialType.getAll()
if (result.success && result.data) records = result.data
} else {
const result = await window.electron.materialType.getByManager(currentUsername)
if (result.success && result.data) records = result.data
}
setRows( setRows(
records.map((record) => ({ records.map((record) => ({
@@ -266,7 +266,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
// --- Row Actions --- // --- Row Actions ---
const handleAdd = () => { const handleAdd = useCallback(() => {
const newRow: RowState = { const newRow: RowState = {
localId: generateId(), localId: generateId(),
record: { materialName: '', managerName: isAdmin ? '' : currentUsername }, record: { materialName: '', managerName: isAdmin ? '' : currentUsername },
@@ -274,9 +274,9 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
} }
setRows(prev => [newRow, ...prev]) setRows(prev => [newRow, ...prev])
setSearchQuery('') setSearchQuery('')
} }, [isAdmin, currentUsername])
const handleUpdate = (localId: string, updates: Partial<MaterialTypeRecord>) => { const handleUpdate = useCallback((localId: string, updates: Partial<MaterialTypeRecord>) => {
setRows(prev => prev.map(row => { setRows(prev => prev.map(row => {
if (row.localId !== localId) return row; if (row.localId !== localId) return row;
return { return {
@@ -285,26 +285,25 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
state: row.state === 'new' ? 'new' : 'modified' state: row.state === 'new' ? 'new' : 'modified'
} }
})) }))
} }, [])
const handleDelete = (localId: string) => { const handleDelete = useCallback((localId: string) => {
setRows(prev => { setRows(prev => {
const row = prev.find(r => r.localId === localId); const row = prev.find(r => r.localId === localId);
if (!row) return prev; if (!row) return prev;
if (row.state === 'new') return prev.filter(r => r.localId !== localId); if (row.state === 'new') return prev.filter(r => r.localId !== localId);
return prev.map(r => r.localId === localId ? { ...r, state: 'deleted' } : r); 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 => { setRows(prev => prev.map(r => {
if (r.localId !== localId) return 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; r.originalRecord?.managerName !== r.record.managerName;
return { ...r, state: wasModified ? 'modified' : 'original' } return { ...r, state: wasModified ? 'modified' : 'original' }
})) }))
} }, [])
const handleReset = async () => { const handleReset = async () => {
if (pendingCount === 0) return if (pendingCount === 0) return