perf(skill): stabilize useCallback refs and memoize filtered list

Use useRef for batchUpload and emitTreeUpdate to remove unstable
dependencies from useCallback, preventing unnecessary memo invalidation
on all 16 TemplateCard components. Wrap filtered list in useMemo and
replace && conditional with ternary for rendering safety.
This commit is contained in:
yyh
2026-01-30 15:06:44 +08:00
parent 038b03fa8e
commit 32329cf27b

View File

@@ -1,7 +1,7 @@
'use client'
import type { SkillTemplateSummary } from './templates/types'
import { memo, useCallback, useState } from 'react'
import { memo, useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useWorkflowStore } from '@/app/components/workflow/store'
@@ -24,7 +24,11 @@ const SkillTemplatesSection = () => {
const appId = appDetail?.id || ''
const storeApi = useWorkflowStore()
const batchUpload = useBatchUpload()
const batchUploadRef = useRef(batchUpload)
batchUploadRef.current = batchUpload
const emitTreeUpdate = useSkillTreeUpdateEmitter()
const emitTreeUpdateRef = useRef(emitTreeUpdate)
emitTreeUpdateRef.current = emitTreeUpdate
const handleUse = useCallback(async (summary: SkillTemplateSummary) => {
const entry = SKILL_TEMPLATES.find(e => e.id === summary.id)
@@ -39,7 +43,7 @@ const SkillTemplatesSection = () => {
const children = await entry.loadContent()
const uploadData = await buildUploadDataFromTemplate(summary.name, children)
await batchUpload.mutateAsync({
await batchUploadRef.current.mutateAsync({
appId,
tree: uploadData.tree,
files: uploadData.files,
@@ -50,7 +54,7 @@ const SkillTemplatesSection = () => {
})
storeApi.getState().setUploadStatus('success')
emitTreeUpdate()
emitTreeUpdateRef.current()
}
catch {
storeApi.getState().setUploadStatus('partial_error')
@@ -58,9 +62,9 @@ const SkillTemplatesSection = () => {
finally {
setLoadingId(null)
}
}, [appId, batchUpload, storeApi, emitTreeUpdate])
}, [appId, storeApi])
const filtered = SKILL_TEMPLATES.filter((entry) => {
const filtered = useMemo(() => SKILL_TEMPLATES.filter((entry) => {
if (searchValue) {
const q = searchValue.toLowerCase()
return entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q)
@@ -68,7 +72,7 @@ const SkillTemplatesSection = () => {
if (activeCategory !== 'all')
return entry.tags?.some(tag => tag.toLowerCase() === activeCategory.toLowerCase())
return true
})
}), [searchValue, activeCategory])
return (
<section className="flex flex-col gap-3 px-6 py-2">
@@ -95,9 +99,9 @@ const SkillTemplatesSection = () => {
/>
))}
</div>
{loadingId && (
<div className="pointer-events-none fixed inset-0 z-50" />
)}
{loadingId
? <div className="pointer-events-none fixed inset-0 z-50" />
: null}
</section>
)
}