perf: memoize derived render data

This commit is contained in:
2026-06-05 17:35:54 +08:00
parent 6060705345
commit b8b3b8f137
6 changed files with 143 additions and 56 deletions
+34 -9
View File
@@ -578,7 +578,17 @@ function CanvasPage({
// Save immediately when user leaves page or switches tab (placed after runCanvasAutoSave definition)
// — see useEffect below near runCanvasAutoSave
const canvasAssets = serverAssets.filter((asset) => asset.imageUrl);
const canvasAssets = useMemo(
() => serverAssets.filter((asset) => asset.imageUrl),
[serverAssets],
);
const assetCountsByCategory = useMemo(() => {
const counts = new Map<string, number>();
for (const asset of serverAssets) {
counts.set(asset.type, (counts.get(asset.type) ?? 0) + 1);
}
return counts;
}, [serverAssets]);
const shouldShowEmptyProjectState =
projectsLoaded && projects.length === 0 && !projectId && workflow.source === "blank" && workflow.nodes.length === 0;
const isWaitingForProjects = isAuthenticated && !projectsLoaded;
@@ -2640,10 +2650,13 @@ function CanvasPage({
setConnectorDrag(null);
};
const collapsedPackageNodeKeys = new Set(
nodePackages.flatMap((nodePackage) =>
nodePackage.collapsed ? nodePackage.nodeIds.map((node) => getCanvasSelectionKey(node)) : []
)
const collapsedPackageNodeKeys = useMemo(
() => new Set(
nodePackages.flatMap((nodePackage) =>
nodePackage.collapsed ? nodePackage.nodeIds.map((node) => getCanvasSelectionKey(node)) : []
)
),
[nodePackages],
);
const isNodeCollapsedInPackage = (kind: CanvasNodeKind, id: string) =>
collapsedPackageNodeKeys.has(getCanvasSelectionKey({ kind, id }));
@@ -2684,6 +2697,18 @@ function CanvasPage({
return positionedLink ? [positionedLink] : [];
}),
].filter((link) => !isLinkCollapsedInPackage(link));
const visibleTextNodes = useMemo(
() => textNodes.filter((textNode) => !isNodeCollapsedInPackage("text", textNode.id)),
[collapsedPackageNodeKeys, textNodes],
);
const visibleImageNodes = useMemo(
() => imageNodes.filter((imageNode) => !isNodeCollapsedInPackage("image", imageNode.id)),
[collapsedPackageNodeKeys, imageNodes],
);
const visibleVideoNodes = useMemo(
() => videoNodes.filter((videoNode) => !isNodeCollapsedInPackage("video", videoNode.id)),
[collapsedPackageNodeKeys, videoNodes],
);
const pendingLinkPreview =
pendingLinkPort && pendingLinkPreviewPoint
? (() => {
@@ -4002,7 +4027,7 @@ function CanvasPage({
) : null}
</svg>
) : null}
{textNodes.filter((textNode) => !isNodeCollapsedInPackage("text", textNode.id)).map((textNode) => {
{visibleTextNodes.map((textNode) => {
const textNodeSelected = isSelectedNode("text", textNode.id);
const textNodeActive = isActiveSelectedNode("text", textNode.id);
const textNodeResizing = nodeResizeDrag?.kind === "text" && nodeResizeDrag.nodeId === textNode.id;
@@ -4270,7 +4295,7 @@ function CanvasPage({
</div>
);
})}
{imageNodes.filter((imageNode) => !isNodeCollapsedInPackage("image", imageNode.id)).map((imageNode) => {
{visibleImageNodes.map((imageNode) => {
const imageNodeSelected = isSelectedNode("image", imageNode.id);
const imageNodeActive = isActiveSelectedNode("image", imageNode.id);
const imageNodeResizing = nodeResizeDrag?.kind === "image" && nodeResizeDrag.nodeId === imageNode.id;
@@ -4774,7 +4799,7 @@ function CanvasPage({
</div>
);
})}
{videoNodes.filter((videoNode) => !isNodeCollapsedInPackage("video", videoNode.id)).map((videoNode) => {
{visibleVideoNodes.map((videoNode) => {
const videoNodeSelected = isSelectedNode("video", videoNode.id);
const videoNodeActive = isActiveSelectedNode("video", videoNode.id);
const videoNodeResizing = nodeResizeDrag?.kind === "video" && nodeResizeDrag.nodeId === videoNode.id;
@@ -5485,7 +5510,7 @@ function CanvasPage({
onClick={() => setSelectedExistingCategory(category.key)}
>
{category.label}
<span>{serverAssets.filter((asset) => asset.type === category.key).length} </span>
<span>{assetCountsByCategory.get(category.key) ?? 0} </span>
</button>
))}
</div>