Skip to content

모범 사례

Context-Action 프레임워크를 사용할 때 따라야 할 컨벤션과 모범 사례입니다.

🧭 빠른 팀 요약

팀에서 “이 프레임워크를 권장 방식대로 어떻게 개발해야 하는가”만 빠르게 맞추고 싶다면 아래 규칙부터 공유하면 됩니다.

  1. contexts에서 액션/스토어/리프 컨텍스트 경계를 먼저 정의합니다.
  2. 비즈니스 규칙과 계산은 business 또는 순수 함수로 분리합니다.
  3. 최신 store 값 읽기, side effect, API 호출은 handlers가 담당합니다.
  4. actions는 view에서 직접 dispatch 문자열을 쓰지 않도록 helper를 제공합니다.
  5. hooksuseStoreValue() 중심으로 구독과 파생 값을 노출합니다.
  6. views는 렌더링과 사용자 입력 전달만 담당하고 business logic을 넣지 않습니다.
  7. action handler에서는 컴포넌트 스코프 값을 쓰지 말고 항상 store.getValue()로 최신 값을 읽습니다.
  8. handler는 useCallback으로 안정화하고, 에러 처리는 try/catch 또는 controller.abort()로 명시합니다.
  9. DOM 이벤트 객체나 무거운 참조 객체는 store에 그대로 저장하지 않습니다.
  10. 새 기능을 추가할 때는 “문서 → 예제 → 테스트” 순서로 최소 검증 루프를 남깁니다.

바로 연결되는 문서

이 문서를 언제 읽으면 좋은가

  • 팀 온보딩 직후 공통 규칙을 맞출 때
  • PR 리뷰 기준을 짧게 합의할 때
  • 새 기능을 어떤 레이어에 넣어야 할지 빠르게 판단할 때

🔧 보안 및 성능 가이드라인

이벤트 객체 처리

DOM 이벤트 객체를 스토어에 저장하지 마세요. 메모리 누수를 유발할 수 있습니다:

tsx
// ❌ 잘못된 방법: 메모리 누수 유발
function BadHandler() {
  useActionHandler('handleClick', async (event) => {
    // 오류가 발생하고 저장이 차단됩니다
    store.setValue({ clickEvent: event }); 
  });
}

// ✅ 올바른 방법: 필요한 데이터만 추출
function GoodHandler() {
  useActionHandler('handleClick', async (event) => {
    const clickData = {
      clientX: event.clientX,
      clientY: event.clientY,
      target: event.target?.tagName,
      timestamp: Date.now()
    };
    store.setValue({ clickData });
  });
}

메모리 관리

프레임워크가 메모리 집약적 객체를 자동으로 처리합니다:

tsx
// EventBus가 자동으로 메모리 누수를 방지하는 방법:
// 1. DOM 요소 및 React 컴포넌트 감지
// 2. 전체 객체 대신 필수 메타데이터만 저장
// 3. 문제가 될 수 있는 참조 자동 정리

오류 처리

중앙화된 오류 처리 시스템을 사용하세요:

tsx
// 프레임워크가 제공하는 ErrorHandlers:
// - 스토어 작업 오류
// - 이벤트 핸들러 실패
// - Ref 마운트 콜백 오류  
// - 적절한 컨텍스트가 포함된 비동기 작업 실패

네이밍 컨벤션

도메인 기반 리네이밍 패턴

핵심 컨벤션은 명확한 컨텍스트 분리를 위한 도메인별 리네이밍입니다.

스토어 패턴 리네이밍

tsx
// ✅ 권장: 도메인별 리네이밍
const {
  Provider: UserStoreProvider,
  useStore: useUserStore,
  useStoreManager: useUserStoreManager
} = createStoreContext('User', {...});

// ❌ 지양: 직접 객체 접근
const UserStores = createStoreContext('User', {...});
const userStore = UserStores.useStore('profile'); // 도메인이 불분명

액션 패턴 리네이밍

tsx
// ✅ 권장: 명시적 타입을 가진 도메인별 리네이밍
const {
  Provider: UserActionProvider,
  useActionDispatch: useUserAction,
  useActionHandler: useUserActionHandler
} = createActionContext<UserActions>('UserActions');

// ❌ 지양: 제네릭 이름
const {
  Provider,
  useActionDispatch,
  useActionHandler
} = createActionContext<UserActions>('UserActions');

컨텍스트 네이밍 규칙

도메인 기반 네이밍

tsx
// ✅ 권장: 명확한 도메인 구분
'UserProfile'     // 사용자 프로필 관련
'ShoppingCart'    // 쇼핑카트 관련  
'ProductCatalog'  // 상품 카탈로그 관련
'OrderManagement' // 주문 관리 관련
'AuthSystem'      // 인증 시스템 관련

// ❌ 지양: 모호한 이름
'Data'           // 너무 포괄적
'State'          // 구체적이지 않음
'App'            // 범위가 불분명 (루트 레벨에서만 사용)
'Manager'        // 역할이 불분명

액션 vs 스토어 구분

tsx
// 액션 컨텍스트 (행동/이벤트 중심)
'UserActions'         // 사용자 액션
'PaymentActions'      // 결제 액션
'NavigationActions'   // 내비게이션 액션

// 스토어 컨텍스트 (데이터/상태 중심)  
'UserData'           // 사용자 데이터
'PaymentData'        // 결제 데이터
'AppSettings'        // 애플리케이션 설정

파일 구조

권장 디렉토리 구조

src/
├── contexts/
│   ├── user/
│   │   ├── UserActions.tsx     # 액션 컨텍스트
│   │   ├── UserStores.tsx      # 스토어 컨텍스트
│   │   └── types.ts            # 사용자 관련 타입
│   ├── payment/
│   │   ├── PaymentActions.tsx
│   │   ├── PaymentStores.tsx
│   │   └── types.ts
│   └── index.ts                # 모든 컨텍스트 내보내기
├── components/
├── pages/
└── utils/

컨텍스트 파일 구성

tsx
// contexts/user/UserActions.tsx
import { createActionContext } from '@context-action/react';
import type { UserActions } from './types';

export const {
  Provider: UserActionProvider,
  useActionDispatch: useUserAction,
  useActionHandler: useUserActionHandler
} = createActionContext<UserActions>('UserActions');

// contexts/user/UserStores.tsx
import { createStoreContext } from '@context-action/react';
import type { UserStoreConfig } from './types';

export const {
  Provider: UserStoreProvider,
  useStore: useUserStore,
  useStoreManager: useUserStoreManager
} = createStoreContext('User', userStoreConfig);

패턴 사용법

액션 패턴 모범 사례

핸들러 등록

tsx
// ✅ 권장: 안정적인 핸들러를 위해 useCallback 사용
function UserComponent() {
  useUserActionHandler('updateProfile', useCallback(async (payload) => {
    try {
      await updateUserProfile(payload);
      console.log('프로필이 성공적으로 업데이트되었습니다');
    } catch (error) {
      console.error('프로필 업데이트 실패:', error);
    }
  }, [])); // 안정적인 핸들러를 위한 빈 의존성 배열
}

// ❌ 지양: 인라인 핸들러 (재등록 발생)
function UserComponent() {
  useUserActionHandler('updateProfile', async (payload) => {
    await updateUserProfile(payload); // 매 렌더링마다 재등록
  });
}

에러 핸들링

tsx
// ✅ 권장: 적절한 에러 핸들링을 위해 controller.abort 사용
useActionHandler('riskyAction', (payload, controller) => {
  try {
    // 실패할 수 있는 비즈니스 로직
    processData(payload);
  } catch (error) {
    controller.abort('처리 실패', error);
  }
});

스토어 패턴 모범 사례

스토어 접근

tsx
// ✅ 권장: 특정 스토어 구독
function UserProfile() {
  const profileStore = useUserStore('profile');
  const profile = useStoreValue(profileStore);
  
  return <div>{profile.name}</div>;
}

// ❌ 지양: 불필요한 스토어 접근
function UserProfile() {
  const profileStore = useUserStore('profile');
  const settingsStore = useUserStore('settings'); // 사용되지 않음
  const profile = useStoreValue(profileStore);
  
  return <div>{profile.name}</div>;
}

스토어 업데이트

tsx
// ✅ 권장: 복잡한 변경을 위한 함수형 업데이트
const { updateStore } = useUserStoreManager();

const updateProfile = (changes: Partial<UserProfile>) => {
  updateStore('profile', prevProfile => ({
    ...prevProfile,
    ...changes,
    updatedAt: Date.now()
  }));
};

// ✅ 허용: 간단한 변경을 위한 직접 업데이트
const setUserName = (name: string) => {
  updateStore('profile', { ...currentProfile, name });
};

타입 정의

액션 타입

tsx
// ✅ 권장: ActionPayloadMap 확장
interface UserActions extends ActionPayloadMap {
  updateProfile: { name: string; email: string };
  deleteAccount: { confirmationCode: string };
  logout: void; // 페이로드 없는 액션
}

// ❌ 지양: 순수 인터페이스
interface UserActions {
  updateProfile: { name: string; email: string }; // ActionPayloadMap 누락
}

스토어 타입

tsx
// ✅ 권장: 명확한 타입 정의
interface UserProfile {
  id: string;
  name: string;
  email: string;
  createdAt: number;
  updatedAt: number;
}

interface UserSettings {
  theme: 'light' | 'dark';
  notifications: boolean;
  language: string;
}

const userStoreConfig = {
  profile: { 
    initialValue: {
      id: '',
      name: '',
      email: '',
      createdAt: 0,
      updatedAt: 0
    } as UserProfile
  },
  settings: { 
    initialValue: {
      theme: 'light',
      notifications: true,
      language: 'en'
    } as UserSettings
  }
};

성능 가이드라인

핸들러 최적화

tsx
// ✅ 권장: 메모이제이션된 핸들러
const optimizedHandler = useCallback(async (payload: UserActions['updateProfile']) => {
  await updateUserProfile(payload);
}, []);

useUserActionHandler('updateProfile', optimizedHandler);

스토어 구독 최적화

tsx
// ✅ 권장: 특정 값 구독
const userName = useStoreValue(profileStore)?.name;

// ❌ 지양: 부분 데이터만 필요할 때 불필요한 전체 객체 구독
const fullProfile = useStoreValue(profileStore);
const userName = fullProfile.name; // 프로필의 모든 변경에 대해 재렌더링

패턴 조합

Provider 계층구조

tsx
// ✅ 권장: 논리적 provider 순서
function App() {
  return (
    <UserStoreProvider>      {/* 데이터 레이어 먼저 */}
      <UserActionProvider>   {/* 액션 레이어 두 번째 */}
        <PaymentStoreProvider>
          <PaymentActionProvider>
            <AppContent />
          </PaymentActionProvider>
        </PaymentStoreProvider>
      </UserActionProvider>
    </UserStoreProvider>
  );
}

교차 패턴 통신

tsx
// ✅ 권장: 액션이 스토어를 업데이트
function UserComponent() {
  const { updateStore } = useUserStoreManager();
  
  useUserActionHandler('updateProfile', useCallback(async (payload) => {
    try {
      const updatedProfile = await updateUserProfile(payload);
      updateStore('profile', updatedProfile); // API 호출 후 스토어 업데이트
    } catch (error) {
      console.error('프로필 업데이트 실패:', error);
    }
  }, [updateStore]));
}

일반적인 함정

이런 패턴은 피하세요

tsx
// ❌ 하지 마세요: 액션 디스패치와 직접 스토어 업데이트 혼용
function BadComponent() {
  const dispatch = useUserAction();
  const { updateStore } = useUserStoreManager();
  
  const handleUpdate = () => {
    updateStore('profile', newProfile);  // 직접 스토어 업데이트
    dispatch('updateProfile', newProfile); // 그리고 액션 디스패치 - 중복!
  };
}

// ❌ 하지 마세요: 컴포넌트 내부에서 컨텍스트 생성
function BadComponent() {
  const { Provider } = createActionContext<UserActions>('User'); // 잘못됨!
  return <Provider>...</Provider>;
}

// ❌ 하지 마세요: 자주 변경되는 의존성으로 핸들러 등록
function BadComponent({ userId }: { userId: string }) {
  useUserActionHandler('updateProfile', async (payload) => {
    await updateUserProfile(userId, payload); // userId 클로저가 자주 변경됨
  }); // useCallback과 deps의 userId 누락
}

고급 베스트 프랙티스

액션 핸들러 상태 접근

⚠️ 중요: 스토어 값 클로저 함정 피하기

액션 핸들러 내부에서 스토어 값에 접근할 때, 컴포넌트 스코프의 값을 절대 사용하지 마세요. 클로저 함정이 발생합니다:

tsx
// ❌ 잘못됨: 핸들러에서 컴포넌트 스코프 값 사용
function UserComponent() {
  const userStore = useUserStore('profile');
  const user = useStoreValue(userStore); // 이 값이 클로저에 갇힘!
  
  useUserActionHandler('updateUser', async (payload) => {
    // 🚨 버그: 이 'user'는 핸들러 등록 시점의 값, 현재 시점이 아님!
    if (user.isActive) {  // 오래된 값!
      await updateUserAPI(payload);
    }
  });
}

// ✅ 올바름: 핸들러 내부에서 스토어 값에 직접 접근
function UserComponent() {
  const userStore = useUserStore('profile');
  const user = useStoreValue(userStore); // 컴포넌트 렌더링용으로만 사용
  
  useUserActionHandler('updateUser', useCallback(async (payload) => {
    // ✅ 항상 스토어에서 최신 상태를 가져옴
    const currentUser = userStore.getValue(); // 실시간 값!
    
    if (currentUser.isActive) {
      await updateUserAPI(payload);
    }
  }, [userStore])); // 의존성에는 스토어 참조만 포함
}

실시간 상태 접근 패턴

tsx
// ✅ 패턴 1: 간단한 확인을 위한 직접 store getValue()
useActionHandler('conditionalAction', async (payload) => {
  const currentState = someStore.getValue();
  
  if (currentState.isReady) {
    // 액션 실행
  }
});

// ✅ 패턴 2: 여러 스토어 조정
useActionHandler('complexAction', async (payload) => {
  const userState = userStore.getValue();
  const settingsState = settingsStore.getValue();
  const uiState = uiStore.getValue();
  
  // 모든 현재 상태를 사용하여 의사결정
  if (userState.isLoggedIn && settingsState.apiEnabled && !uiState.isLoading) {
    // 복잡한 로직 실행
  }
});

// ✅ 패턴 3: 상태 검증 및 업데이트
useActionHandler('validateAndUpdate', async (payload) => {
  const current = dataStore.getValue();
  
  // 현재 상태 검증
  if (current.version !== payload.expectedVersion) {
    throw new Error('버전 불일치');
  }
  
  // 현재 상태를 기준으로 업데이트
  dataStore.setValue({
    ...current,
    ...payload.updates,
    version: current.version + 1
  });
});

useEffect 의존성 베스트 프랙티스

스토어와 Dispatch 참조는 안정적

Context-Action 프레임워크는 스토어 인스턴스와 dispatch 함수가 안정적인 참조를 갖도록 보장합니다:

tsx
// ✅ 이것들은 useEffect 의존성에서 생략해도 안전
function MyComponent() {
  const userStore = useUserStore('profile');  // 안정적인 참조
  const dispatch = useUserAction();           // 안정적인 참조
  const user = useStoreValue(userStore);
  
  useEffect(() => {
    if (user.needsSync) {
      dispatch('syncUser', { id: user.id });
      userStore.setValue({ ...user, lastSyncAttempt: Date.now() });
    }
  }, [user.needsSync, user.id]); // userStore나 dispatch 포함하지 않음
  
  // 대안: 명시적으로 포함해도 됨 (해롭지 않음)
  useEffect(() => {
    if (user.needsSync) {
      dispatch('syncUser', { id: user.id });
    }
  }, [user.needsSync, user.id, dispatch, userStore]); // 이것도 괜찮음
}

의존성 배열 가이드라인

tsx
// ✅ 포함: 실제로 변경되고 동작에 영향을 주는 값들
useEffect(() => {
  if (user.isActive) {
    startPolling();
  }
}, [user.isActive]); // 파생된 값들 포함

// ✅ 생략: 안정적인 참조들 (하지만 포함해도 해롭지 않음)
const stableRef = userStore;
const stableDispatch = dispatch;

useEffect(() => {
  // 이것들은 deps에 포함할 필요 없지만, 포함해도 됨
  stableRef.setValue(newValue);
  stableDispatch('action', payload);
}, []); // 빈 deps 괜찮음

// ❌ 피하기: 특정 속성만 중요할 때 전체 객체 포함
useEffect(() => {
  updateUI();
}, [user]); // 모든 user 변경에 재실행

// ✅ 더 좋음: 관련된 속성만 포함
useEffect(() => {
  updateUI();
}, [user.theme, user.language]); // 이것들이 변경될 때만 재실행

상태 이슈 디버깅

상태 모니터링 기법

tsx
// ✅ 상태 변경을 추적하기 위한 디버그 로깅 추가
useActionHandler('debugAction', async (payload) => {
  const beforeState = store.getValue();
  console.log('이전:', beforeState);
  
  // 업데이트 수행
  store.setValue(newValue);
  
  const afterState = store.getValue();
  console.log('이후:', afterState);
  
  // 상태 변경 확인
  if (beforeState === afterState) {
    console.warn('상태가 예상대로 변경되지 않았습니다!');
  }
});

// ✅ 복잡한 상태 추적을 위한 디버그 유틸리티 생성
const createStateLogger = (storeName: string, store: Store<any>) => ({
  logCurrent: () => console.log(`${storeName}:`, store.getValue()),
  logChange: (action: string) => {
    const before = store.getValue();
    return (after: any) => {
      console.log(`${storeName} ${action}:`, { before, after });
    };
  }
});

일반적인 디버깅 시나리오

tsx
// 🔍 디버그: 상태 변경 시 컴포넌트가 리렌더링되지 않음
function DebuggingComponent() {
  const store = useStore('data');
  const value = useStoreValue(store);
  
  // 구독을 확인하기 위한 로깅 추가
  useEffect(() => {
    console.log('컴포넌트 리렌더링됨, 값:', value);
  });
  
  // 스토어 업데이트가 작동하는지 확인
  const testUpdate = () => {
    console.log('업데이트 전:', store.getValue());
    store.setValue({ ...store.getValue(), timestamp: Date.now() });
    console.log('업데이트 후:', store.getValue());
  };
  
  return (
    <div>
      <div>현재 값: {JSON.stringify(value)}</div>
      <button onClick={testUpdate}>업데이트 테스트</button>
    </div>
  );
}

// 🔍 디버그: 액션 핸들러가 실행되지 않음
function DebuggingActions() {
  useActionHandler('testAction', useCallback(async (payload) => {
    console.log('핸들러가 페이로드로 실행됨:', payload);
    
    // 에러를 캐치하기 위한 try-catch 추가
    try {
      // 여기에 로직
    } catch (error) {
      console.error('핸들러 에러:', error);
      throw error; // 에러 전파를 유지하기 위해 재throw
    }
  }, []));
  
  const dispatch = useActionDispatch();
  
  const testDispatch = () => {
    console.log('testAction 디스패치 중...');
    dispatch('testAction', { test: true });
  };
  
  return <button onClick={testDispatch}>액션 테스트</button>;
}

프로덕션 디버깅 및 컴포넌트 생명주기 관리

치명적 이슈: 중복 액션 핸들러 등록

문제: 동일한 액션 핸들러를 여러 번 등록하면 예측할 수 없는 동작이 발생합니다.

tsx
// ❌ 잘못된 예: 중복 핸들러 등록
useActionHandler('updateResults', async (payload) => {
  store.setValue(payload.data);
});
useActionHandler('updateResults', async (payload) => {  // 중복!
  store.setValue(payload.data);  // 첫 번째 핸들러를 덮어씁니다
});

// ✅ 올바른 예: 단일 핸들러 등록
const updateResultsHandler = useCallback(async (payload) => {
  store.setValue(payload.data);
}, [store]);
useActionHandler('updateResults', updateResultsHandler);

디버깅 팁: grep -n "useActionHandler.*'actionName'" src/**/*.tsx

처리 상태로 경쟁 조건 방지하기

문제: 빠른 버튼 클릭으로 인한 경쟁 조건과 상태 불일치가 발생합니다.

tsx
// ✅ 경쟁 조건을 방지하기 위한 처리 상태 추가
const stores = createStoreContext('Demo', {
  data: initialData,
  isProcessing: false  // 처리 상태 추가
});

const criticalActionHandler = useCallback(async (payload) => {
  const currentProcessing = isProcessingStore.getValue();
  
  if (currentProcessing) {
    console.warn('액션이 이미 진행 중입니다. 요청을 무시합니다');
    return; // 조기 반환으로 경쟁 조건 방지
  }
  
  isProcessingStore.setValue(true);
  try {
    await performCriticalOperation(payload);
  } finally {
    isProcessingStore.setValue(false); // 항상 처리 상태 해제
  }
}, [isProcessingStore]);

useActionHandler('criticalAction', criticalActionHandler);

// ✅ 처리 상태를 반영하는 UI
function ActionButton() {
  const isProcessing = useStoreValue(isProcessingStore);
  const dispatch = useActionDispatch();
  
  return (
    <button
      onClick={() => dispatch('criticalAction', payload)}
      disabled={isProcessing}
    >
      {isProcessing ? '⏳ 처리 중...' : '액션 실행'}
    </button>
  );
}

RefContext를 사용한 안전한 컴포넌트 언마운트

문제: 컴포넌트 언마운트가 수동 ref 정리와 충돌합니다.

tsx
// ❌ 잘못된 예: 컴포넌트 useEffect에서 수동 ref 정리
function Component() {
  const elementRef = useRefHandler('element');
  
  useEffect(() => {
    return () => {
      elementRef.setRef(null); // 이는 액션 핸들러 정리와 충돌합니다
    };
  }, []);
  
  return <div ref={elementRef.setRef} />;
}

// ✅ 올바른 예: 관심사 분리 - React는 DOM을, 액션은 상태를 처리
function Component() {
  const elementRef = useRefHandler('element');
  
  useEffect(() => {
    console.log('컴포넌트 마운트됨');
    return () => console.log('컴포넌트 언마운트 중');
    // React가 DOM 정리를 자동으로 처리하도록 함
  }, []);
  
  return <div ref={elementRef.setRef} />;
}

// ✅ 액션 핸들러가 상태와 ref 조정을 관리
const unmountElementHandler = useCallback(async () => {
  const isCurrentlyMounted = isMountedStore.getValue();
  
  if (isCurrentlyMounted) {
    isMountedStore.setValue(false); // 먼저 상태 업데이트
    
    // React가 컴포넌트를 언마운트한 후 ref 상태 확인
    setTimeout(() => {
      const currentRef = elementRef.target;
      if (currentRef) {
        elementRef.setRef(null); // 필요한 경우에만 수동 정리
      }
    }, 50);
  }
}, [isMountedStore, elementRef]);

useActionHandler('unmountElement', unmountElementHandler);

프로덕션 디버깅 기법

상태 모니터링: 프로덕션 이슈 진단을 위한 포괄적 상태 모니터링:

tsx
// ✅ 다차원 상태 모니터링
const debugStores = createStoreContext('Debug', {
  actionLog: [] as string[],
  errorCount: 0,
  operationTimes: {} as Record<string, number>
});

const addLogHandler = useCallback(async ({ message }) => {
  const timestamp = new Date().toLocaleTimeString();
  const logEntry = `[${timestamp}] ${message}`;
  
  actionLogStore.update(prev => [
    ...prev.slice(-49), // 최근 50개 항목 유지
    logEntry
  ]);
}, [actionLogStore]);

useActionHandler('addLog', addLogHandler);

에러 복구: 자동 재시도 로직으로 우아한 에러 복구:

tsx
// ✅ 지수 백오프를 사용한 자동 재시도
const reliableActionHandler = useCallback(async (payload) => {
  const maxRetries = 3;
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      await performOperation(payload);
      return; // 성공
    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) throw error; // 최종 실패
      
      const delay = 100 * Math.pow(2, attempt - 1); // 지수 백오프
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}, []);

useActionHandler('reliableAction', reliableActionHandler);

스트레스 테스트: 간헐적 이슈를 재현하기 위해 프로덕션 조건을 시뮬레이션:

tsx
// ✅ 간단한 스트레스 테스트 헬퍼
function StressTester({ children }: { children: ReactNode }) {
  const [isStressTesting, setIsStressTesting] = useState(false);
  
  useEffect(() => {
    if (!isStressTesting) return;
    
    const interval = setInterval(() => {
      if (Math.random() > 0.7) { // 사이클당 30% 확률
        // 빠른 사용자 행동을 시뮬레이션하는 랜덤 액션 트리거
        const actions = ['mount', 'unmount', 'waitForRef'];
        const randomAction = actions[Math.floor(Math.random() * actions.length)];
        console.log(`🎯 스트레스 테스트: ${randomAction}`);
      }
    }, 100);
    
    return () => clearInterval(interval);
  }, [isStressTesting]);
  
  return (
    <div>
      <button onClick={() => setIsStressTesting(!isStressTesting)}>
        {isStressTesting ? '🛑 중지' : '🎯 시작'} 스트레스 테스트
      </button>
      {children}
    </div>
  );
}

Released under the Apache-2.0 License.