> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/ui/store-setup.md).

# 스토어 설정

이 페이지에서는 Redux 스토어를 설정하고, 타입이 지정된 훅을 구성하며, 프로젝트 구조를 정리하는 방법을 다룹니다.

## 폴더 구조

일관성과 유지보수성을 위해 프로젝트는 반드시 이 구조를 따라야 합니다:

```
src/
  app/
    store.ts              # 스토어 설정
    hooks.ts              # 타입이 지정된 훅 (useAppDispatch/useAppSelector)
  shared/
    types/                # DTO(API), 도메인 모델, 매퍼
    utils/                # 공유 유틸리티
  services/
    client.ts             # RTK Query 기본 클라이언트
    baseQuery.ts          # 인증/chainId/재시도가 포함된 기본 쿼리
  features/
    user/
      user.client.ts      # RTK Query 엔드포인트
      user.slice.ts       # UI 상태 슬라이스
      user.selectors.ts   # 메모이제이션된 셀렉터
      __tests__/          # 테스트
    land/
      land.client.ts
      land.slice.ts
      land.selectors.ts
    credits/
      credits.client.ts
      credits.slice.ts
      credits.selectors.ts
```

### 폴더 구성 원칙

* **기능 중심** - 기술적 역할이 아니라 비즈니스 도메인별로 묶기
* **코로케이션** - 관련 코드는 함께 두기
* **명확한 분리** - 원격 데이터(`.client.ts`)와 로컬 상태(`.slice.ts`)

## 스토어 설정

스토어는 RTK의 `configureStore` 로 구성하고 필요한 모든 미들웨어를 포함해야 합니다.

### 기본 스토어 설정

```tsx
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { client } from '@/services/client';
import userReducer from '@/features/user/user.slice';
import landReducer from '@/features/land/land.slice';
import creditsReducer from '@/features/credits/credits.slice';

export const store = configureStore({
  reducer: {
    // RTK Query 클라이언트 리듀서(반드시 포함해야 함)
    [client.reducerPath]: client.reducer,
    
    // 기능 슬라이스
    user: userReducer,
    land: landReducer,
    credits: creditsReducer,
  },
  
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // redux-persist 또는 알려진 비직렬화 경로는 무시
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
        ignoredPaths: ['register'],
      },
    }).concat(client.middleware), // RTK Query 미들웨어를 반드시 포함
    
  devTools: process.env.NODE_ENV !== 'production',
});

// 앱 전체에서 사용할 타입 내보내기
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
```

### 구성 옵션

#### 직렬화 가능성 검사

그 `serializableCheck` 미들웨어는 모든 상태가 직렬화 가능한지 검증합니다. 알려진 예외는 무시하도록 설정하세요:

```tsx
serializableCheck: {
  // 무시할 액션
  ignoredActions: [
    'persist/PERSIST',
    'persist/REHYDRATE',
  ],
  // 무시할 상태 경로
  ignoredPaths: ['register', 'socket.connection'],
}
```

{% hint style="warning" %}
**절대 `serializableCheck` 완전히 비활성화하지 마세요.** 대신 예외를 설정하고 직렬화할 수 없는 데이터는 Redux 밖에 두세요.
{% endhint %}

#### 개발자 도구

디버깅을 위해 개발 환경에서 Redux DevTools를 활성화하세요:

```tsx
devTools: process.env.NODE_ENV !== 'production'
```

프로덕션 디버깅이 필요한 경우에는, 다음과 같이 특정 설정을 사용하세요:

```tsx
devTools: process.env.NODE_ENV !== 'production' ? true : {
  name: 'Decentraland App',
  trace: false,
  traceLimit: 25,
}
```

## 타입이 지정된 훅

다음의 타입이 지정된 버전을 만드세요 `useDispatch` 및 `useSelector` 더 나은 타입 추론과 개발자 경험을 위해.

### 훅 설정

```tsx
// src/app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// 타입이 지정된 dispatch 훅
export const useAppDispatch = () => useDispatch<AppDispatch>();

// 타입이 지정된 selector 훅
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```

### 컴포넌트에서의 사용

```tsx
// ❌ 잘못된 예: 표준 훅 사용
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/app/store';

function Component() {
  const dispatch = useDispatch(); // 타입 추론 없음
  const user = useSelector((state: RootState) => state.user); // 수동 타입 지정
}

// ✅ 좋은 예: 타입이 지정된 훅 사용
import { useAppDispatch, useAppSelector } from '@/app/hooks';

function Component() {
  const dispatch = useAppDispatch(); // 자동으로 타입 지정
  const user = useAppSelector((state) => state.user); // RootState가 추론됨
}
```

### 타입이 지정된 훅의 장점

1. **자동 완성** - 상태 형태에 대한 전체 IntelliSense
2. **타입 안전성** - 컴파일 시 오류를 포착
3. **리팩터링** - 자신 있게 상태 속성 이름 변경
4. **보일러플레이트 감소** - 다음을 반복해서 지정할 필요 없음 `RootState` 를

## Provider 설정

Redux Provider로 앱을 감싸세요:

### Next.js App Router

```tsx
// app/providers.tsx
'use client';

import { Provider } from 'react-redux';
import { store } from '@/app/store';

export function Providers({ children }: { children: React.ReactNode }) {
  return <Provider store={store}>{children}</Provider>;
}
```

```tsx
// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

### Next.js Pages Router

```tsx
// pages/_app.tsx
import { Provider } from 'react-redux';
import { store } from '@/app/store';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}
```

### React (Vite/CRA)

```tsx
// main.tsx / index.tsx
import { Provider } from 'react-redux';
import { store } from '@/app/store';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```

## 환경 구성

환경별 설정을 구성하세요:

```tsx
// src/config/constants.ts
export const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.decentraland.org';
export const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'wss://api.decentraland.org';
export const CHAIN_ID = process.env.NEXT_PUBLIC_CHAIN_ID || '1';

// RTK Query 설정
export const RTK_QUERY_CONFIG = {
  keepUnusedDataFor: 60, // 초
  refetchOnFocus: true,
  refetchOnReconnect: true,
  refetchOnMountOrArgChange: 30, // 초
} as const;
```

## 타입 정의

일관성을 위해 공유 타입 정의를 만드세요:

```tsx
// src/shared/types/api.types.ts
/** API 응답 래퍼 */
export interface ApiResponse<T> {
  ok: boolean;
  data: T;
  error?: string;
}

/** 페이지네이션된 응답 */
export interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  limit: number;
}

/** 공통 API 오류 */
export interface ApiError {
  message: string;
  code: string;
  details?: Record<string, any>;
}
```

```tsx
// src/shared/types/domain.types.ts
/** 사용자 도메인 모델 */
export interface User {
  id: string;
  address: string;
  name?: string;
  avatar?: string;
  createdAt: string;
}

/** Parcel 도메인 모델 */
export interface Parcel {
  id: string;
  x: number;
  y: number;
  owner: string;
  name?: string;
}
```

## 여러 스토어 인스턴스

테스트나 마이크로 프론트엔드에서는 여러 스토어 인스턴스가 필요할 수 있습니다:

```tsx
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit';

export function createStore(preloadedState?: Partial<RootState>) {
  return configureStore({
    reducer: {
      // ... 리듀서
    },
    preloadedState,
    // ... 기타 설정
  });
}

// 기본 스토어 인스턴스
export const store = createStore();

export type AppStore = ReturnType<typeof createStore>;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
```

## 모범 사례

### 1. 단일 스토어

애플리케이션당 항상 하나의 스토어 인스턴스만 사용하세요:

```tsx
// ✅ 좋은 예: 하나의 스토어
export const store = configureStore({ ... });

// ❌ 잘못된 예: 여러 개의 스토어
export const userStore = configureStore({ ... });
export const cartStore = configureStore({ ... });
```

### 2. 리듀서 지연 로딩

코드 분할을 위해 리듀서를 동적으로 주입하세요:

```tsx
import { combineReducers } from '@reduxjs/toolkit';

const staticReducers = {
  user: userReducer,
};

export function createReducer(asyncReducers = {}) {
  return combineReducers({
    ...staticReducers,
    ...asyncReducers,
  });
}

// 스토어에서
let currentReducers = createReducer();

export function injectReducer(key: string, reducer: Reducer) {
  currentReducers = createReducer({ [key]: reducer });
  store.replaceReducer(currentReducers);
}
```

### 3. 핫 모듈 교체

개발 환경에서 리듀서에 대해 HMR을 활성화하세요:

```tsx
if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept('./reducer', () => {
    const newRootReducer = require('./reducer').default;
    store.replaceReducer(newRootReducer);
  });
}
```

### 4. 상태 영속화

redux-persist를 사용할 때는 신중하게 구성하세요:

```tsx
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage,
  // 특정 슬라이스만 영속화
  whitelist: ['user', 'preferences'],
  // RTK Query 캐시는 절대 영속화하지 않기
  blacklist: ['client'],
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
```

## 다음 단계

* 배워보세요 [RTK Query](/contributor/contributor-ko/contributor-guides/ui/rtk-query.md) 데이터 가져오기를 위한
* 이해하기 [상태 관리](/contributor/contributor-ko/contributor-guides/ui/state-management.md) 로컬 상태를 위한
* 검토 [컴포넌트 패턴](/contributor/contributor-ko/contributor-guides/ui/component-patterns.md) 사용 예시용


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/ui/store-setup.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
