From 893a0bf1fd0bab2782f467b86bd7e4418ddec7fe Mon Sep 17 00:00:00 2001 From: MadCcc Date: Tue, 25 Feb 2025 10:35:29 +0800 Subject: [PATCH 1/3] perf: cache effect (#218) (cherry picked from commit 80f1368732ed4894ec1027d5094e6ecf78aefc3b) # Conflicts: # src/hooks/useGlobalCache.tsx --- src/hooks/useGlobalCache.tsx | 13 ++++++++++++- tests/animation.spec.tsx | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hooks/useGlobalCache.tsx b/src/hooks/useGlobalCache.tsx index 93ab6c39..e04ffc6a 100644 --- a/src/hooks/useGlobalCache.tsx +++ b/src/hooks/useGlobalCache.tsx @@ -13,6 +13,8 @@ export type ExtractStyle = ( }, ) => [order: number, styleId: string, style: string] | null; +const effectMap = new Map(); + export default function useGlobalCache( prefix: string, keyPath: KeyType[], @@ -84,7 +86,15 @@ export default function useGlobalCache( // which will clear cache on the first time. buildCache(([times, cache]) => { if (polyfill && times === 0) { - onCacheEffect?.(cacheContent); + if (!effectMap.has(fullPathStr)) { + onCacheEffect?.(cacheContent); + effectMap.set(fullPathStr, true); + + // 微任务清理混存,可以认为是单次 batch render 中只触发一次 effect + Promise.resolve().then(() => { + effectMap.delete(fullPathStr); + }); + } } return [times + 1, cache]; }); @@ -97,6 +107,7 @@ export default function useGlobalCache( if (nextCount === 0) { // Always remove styles in useEffect callback register(() => { + effectMap.delete(fullPathStr); // With polyfill, registered callback will always be called synchronously // But without polyfill, it will be called in effect clean up, // And by that time this cache is cleaned up. diff --git a/tests/animation.spec.tsx b/tests/animation.spec.tsx index cb69f527..98bc8211 100644 --- a/tests/animation.spec.tsx +++ b/tests/animation.spec.tsx @@ -179,7 +179,7 @@ describe('animation', () => { }); }); - it('re-mount should not missing animation style', () => { + it('re-mount should not missing animation style', async () => { function genComp(cls: string) { return () => { const [token, hashId] = useCacheToken(theme, [baseToken], { @@ -210,6 +210,8 @@ describe('animation', () => { // Clean up document.head.innerHTML = ''; + await Promise.resolve(); + // Render again render( From e6033b8a13f41933ee292fc08e8630fff5cf44c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=BE=F0=9D=92=96=F0=9D=92=99=F0=9D=92=89?= Date: Fri, 9 May 2025 15:45:06 +0800 Subject: [PATCH 2/3] Update src/hooks/useGlobalCache.tsx --- src/hooks/useGlobalCache.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useGlobalCache.tsx b/src/hooks/useGlobalCache.tsx index e04ffc6a..9d0a6bf8 100644 --- a/src/hooks/useGlobalCache.tsx +++ b/src/hooks/useGlobalCache.tsx @@ -90,7 +90,7 @@ export default function useGlobalCache( onCacheEffect?.(cacheContent); effectMap.set(fullPathStr, true); - // 微任务清理混存,可以认为是单次 batch render 中只触发一次 effect + // 微任务清理缓存,可以认为是单次 batch render 中只触发一次 effect Promise.resolve().then(() => { effectMap.delete(fullPathStr); }); From 5008b621d0dfdb030ad4827afc4fe6a38d7d466c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=BE=F0=9D=92=96=F0=9D=92=99=F0=9D=92=89?= Date: Mon, 12 May 2025 10:57:51 +0800 Subject: [PATCH 3/3] chore: add first-render demo --- docs/demo/first-render.md | 8 ++++++ docs/examples/first-render.tsx | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docs/demo/first-render.md create mode 100644 docs/examples/first-render.tsx diff --git a/docs/demo/first-render.md b/docs/demo/first-render.md new file mode 100644 index 00000000..bad5bc3f --- /dev/null +++ b/docs/demo/first-render.md @@ -0,0 +1,8 @@ +--- +title: First Render +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/first-render.tsx b/docs/examples/first-render.tsx new file mode 100644 index 00000000..6b8a56c7 --- /dev/null +++ b/docs/examples/first-render.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import './basic.less'; +import Button from './components/Button'; + +const Demo = () => { + const renderStart = React.useRef(Date.now()); + const [renderTime, setRenderTime] = React.useState(0); + + React.useEffect(() => { + setRenderTime(Date.now() - renderStart.current); + }, []); + + return ( + <> +

Render Time: {renderTime}ms

+ {Array(10000) + .fill(1) + .map((_, key) => ( +
+ + + + +
+ ))} + + ); +}; + +export default function App() { + const [show, setShow] = React.useState(false); + + return ( +
+

默认情况下不会自动删除添加的样式

+ + + + {show && ( +
+ +
+ )} +
+ ); +}