Yuki Takei 5 месяцев назад
Родитель
Сommit
5ce9ca23c2
1 измененных файлов с 35 добавлено и 15 удалено
  1. 35 15
      .serena/memories/apps-app-modal-performance-optimization-v3.md

+ 35 - 15
.serena/memories/apps-app-modal-performance-optimization-v3.md

@@ -34,7 +34,7 @@
 ## 解決策
 
 ### アーキテクチャ
-1. **useDynamicModalLoader**: 汎用的な動的ローディングフック
+1. **useLazyLoader**: 汎用的な動的ローディングフック (コンポーネントのアクティブ/非アクティブ状態に応じて動的ロード)
 2. **グローバルキャッシュ**: 同じimportの重複実行防止
 3. **責務の分離**: モーダルロジックと動的ローディングロジックの分離
 
@@ -42,39 +42,59 @@
 
 ### 1. 汎用ローダーの作成
 
-**ファイル**: `apps/app/client/util/use-dynamic-modal-loader.ts`
+**ファイル**: `apps/app/src/client/util/use-lazy-loader.ts`
 
 ```tsx
 import { useState, useEffect, useCallback } from 'react';
 
-// グローバルキャッシュ
-const modalCache = new Map<string, Promise<any>>();
+// Global cache for dynamically loaded components
+const componentCache = new Map<string, Promise<any>>();
 
 const getCachedImport = <T>(
   key: string,
   importFn: () => Promise<{ default: React.ComponentType<T> }>
 ): Promise<{ default: React.ComponentType<T> }> => {
-  if (!modalCache.has(key)) {
-    modalCache.set(key, importFn());
+  if (!componentCache.has(key)) {
+    componentCache.set(key, importFn());
   }
-  return modalCache.get(key)!;
+  return componentCache.get(key)!;
 };
 
-export const useDynamicModalLoader = <T extends {}>(
+/**
+ * Dynamically loads a component when it becomes active
+ * 
+ * @param importKey - Unique identifier for the component (used for caching)
+ * @param importFn - Function that returns a dynamic import promise
+ * @param isActive - Whether the component should be loaded (e.g., modal open, tab selected, etc.)
+ * @returns The loaded component or null if not yet loaded
+ * 
+ * @example
+ * // For modals
+ * const Modal = useLazyLoader('my-modal', () => import('./MyModal'), isOpen);
+ * 
+ * @example
+ * // For tab content
+ * const TabContent = useLazyLoader('tab-advanced', () => import('./AdvancedTab'), activeTab === 'advanced');
+ * 
+ * @example
+ * // For conditional panels
+ * const AdminPanel = useLazyLoader('admin-panel', () => import('./AdminPanel'), isAdmin);
+ */
+export const useLazyLoader = <T extends {}>(
   importKey: string,
   importFn: () => Promise<{ default: React.ComponentType<T> }>,
-  isOpen: boolean
+  isActive: boolean
 ) => {
   const [Component, setComponent] = useState<React.ComponentType<T> | null>(null);
 
   const memoizedImportFn = useCallback(importFn, [importKey]);
 
   useEffect(() => {
-    if (isOpen && !Component) {
+    if (isActive && !Component) {
       getCachedImport(importKey, memoizedImportFn)
         .then(mod => setComponent(() => mod.default));
     }
-  }, [isOpen, Component, importKey, memoizedImportFn]);
+  }, [isActive, Component, importKey, memoizedImportFn]);
 
   return Component;
 };
@@ -128,13 +148,13 @@ export const TemplateModal = (props: TemplateModalProps) => {
 ```tsx
 import React from 'react';
 import { Modal } from 'reactstrap';
-import { useDynamicModalLoader } from '~/utils/use-dynamic-modal-loader';
+import { useLazyLoader } from '~/client/util/use-lazy-loader';
 import { useTemplateModal } from '~/hooks/useTemplateModal';
 
 export const TemplateModalDynamic = (): JSX.Element => {
   const { data: templateModalStatus, close } = useTemplateModal();
   
-  const TemplateModal = useDynamicModalLoader(
+  const TemplateModal = useLazyLoader(
     'template-modal',
     () => import('./TemplateModal').then(mod => ({ default: mod.TemplateModal })),
     templateModalStatus?.isOpened ?? false
@@ -171,10 +191,10 @@ export { TemplateModalDynamic as TemplateModal } from './dynamic';
 ## チェックリスト
 
 ### 実装確認項目
-- [ ] `useDynamicModalLoader` フックが作成済み
+- [ ] `useLazyLoader` フックが作成済み
 - [ ] モーダルディレクトリが作成済み(index.ts, [Modal].ts, dynamic.ts)
 - [ ] 実際のモーダルコンポーネントが分離済み
-- [ ] 動的ローダーが `useDynamicModalLoader` を使用
+- [ ] 動的ローダーが `useLazyLoader` を使用
 - [ ] エクスポートファイルが正しく設定済み
 
 ### 動作確認項目