Skip to content

모범 사례

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

네이밍 컨벤션

도메인 기반 리네이밍 패턴

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

스토어 패턴 리네이밍

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

// ❌ 지양: 직접 객체 접근
const UserStores = createDeclarativeStorePattern('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 { createDeclarativeStorePattern } from '@context-action/react';
import type { UserStoreConfig } from './types';

export const {
  Provider: UserStoreProvider,
  useStore: useUserStore,
  useStoreManager: useUserStoreManager
} = createDeclarativeStorePattern('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 누락
}

Released under the Apache-2.0 License.