본문 바로가기

교육

「React 18 + TypeScript + MobX 기반 구조에서 useStore 훅 설계와 스토어 계층 아키텍처 이해」

반응형

필요 라이브러리 설치

시스템 구조에서 MobX + React Context 기반 store 설계를 위해 필요한 라이브러리 설치:

npm install mobx mobx-react-lite
npm install react react-dom
npm install typescript @types/react @types/react-dom

Vite 기반 프로젝트라면:

npm create vite@latest my-app --template react-ts

 

 

대규모 ERP 시스템에서 모듈별 store 구조를 다음처럼 나누었을 때:

root
 ├─ cmn (공통 store)
 ├─ mdm (Master Data Management store)
 └─ sys (System별 store)

 

1) TypeScript 제네릭을 이용한 반환 타입 자동 추론

아래 타입 정의는 useStore('mdm') 호출 시 자동으로 타입이 결정되게 하는 구조입니다:

type TUseStoreReturnType<T extends TUseStoreType> =
  T extends 'cmn' ? TCmnStores :
  T extends 'sys' ? TSysStores :
  T extends 'all' ? TCmnStores & TSysStores :
  T extends 'root' ? RootRefacStore : 
  never;

 

예)

const cmnStore = useStore("cmn"); // => type TCmnStores
const sysStore = useStore("sys"); // => type TSysStores

 

2) React Context 사용

스토어를 전역적으로 접근하기 위해 Context 사용

const rootRefacStore = useContext(RootRefacStoreContext);

 

 

3) CMN, SYS 스토어 분리 + 병합

case 'all':
  return { ...cmd, ...sys };

 

ERP 시스템에서 여러 도메인의 스토어 사용이 용이함.

4) root 반환

root 전체 스토어를 그대로 받고 싶을 때:

case 'root':
  return rootRefacStore;

 

 

1장. ERP Front 구조 이해

  • React + MobX + Context 기반 Store Layer
  • Root → cmn → (mdm / sys) store 설계 이유
  • React Context로 모듈 간 의존성 끊고 유지보수성 향상

2장. useStore 훅의 역할

  • ERP 시스템에서 store 접근을 중앙 관리
  • 입력값에 따라 반환 store가 자동 결정
  • TypeScript 제네릭으로 타입 안정성 보장

3장. 타입 시스템 설명

TUseStoreType

type TUseStoreType = 'cmn' | 'sys' | 'all' | 'root';

 

TUseStoreReturnType

입력값에 따라 반환 타입이 달라지는 형태:

T extends 'cmn' → TCmnStores
T extends 'sys' → TSysStores
T extends 'all' → TCmnStores & TSysStores
T extends 'root' → RootRefacStore

 

4장. 실제 동작 과정

  1. Context에서 Root Store 가져옴
  2. 사용자가 'cmn' 또는 'sys' 전달
  3. switch-case로 적절한 store 반환
  4. TypeScript가 자동으로 타입을 결정
  5. 컴포넌트에서 매우 안전하게 store 접근 가능

5장. 예제: CMN 화면에서 store 사용

import { observer } from "mobx-react-lite";
import { useStore } from "../../common/hooks/useStore";

export const MdmUserList = observer(() => {
  const mdm = useStore("mdm");

  return (
    <div>
      사용자 수: {mdm.userStore.count}
    </div>
  );
});

 

6장. 예제: SYS + CMN 모두 사용하는 화면

const Dashboard = observer(() => {
  const stores = useStore("all");

  return (
    <>
      <div>공통코드: {stores.codeStore.total}</div>
      <div>시스템 알림: {stores.sysAlertStore.total}</div>
    </>
  );
});

 

7장. Root Store 전체 사용 예제

const App = () => {
  const root = useStore("root");

  return (
    <div>
      Loaded modules: {root.loadedModules.join(", ")}
    </div>
  );
};

 

 

실전 예제용 완성 코드

Root Store Context

import { createContext } from "react";
import { RootRefacStore } from "./RootRefacStore";

export const RootRefacStoreContext = createContext<RootRefacStore | null>(null);

 

 

RootRefacStore 예시

export class RootRefacStore {
  mdm: TCmnStores;
  sys: TSysStores;

  constructor() {
    this.mdm = new TCmnStores();
    this.sys = new TSysStores();
  }
}

 

Provider 설정

export function StoreProvider({ children }) {
  const root = new RootRefacStore();

  return (
    <RootRefacStoreContext.Provider value={root}>
      {children}
    </RootRefacStoreContext.Provider>
  );
}

 

사용자 정의 훅 최종 정리

export function useStore<T extends TUseStoreType>(
  useStoreType: T
): TUseStoreReturnType<T> {
  const rootRefacStore = useContext(RootRefacStoreContext);
  if (!rootRefacStore) {
    throw new Error('rootRefacStore가 생성되지 않았습니다');
  }

  const { cmn, sys } = rootRefacStore;

  switch (useStoreType) {
    case 'cmn':
      return cmn as TUseStoreReturnType<T>;
    case 'sys':
      return sys as TUseStoreReturnType<T>;
    case 'all':
      return { ...cmn, ...sys } as TUseStoreReturnType<T>;
    case 'root':
      return rootRefacStore as TUseStoreReturnType<T>;
    default:
      throw new Error('Invalid useStoreType');
  }
}

 

 

반응형