Frontend/TypeScript

[Redux] Next.js - App Router 에서 Redux 사용하기

ayeongjin 2025. 2. 18. 14:11

1. Redux의 핵심 구조

 

✅ Store 내부 (Redux의 핵심 요소)

애플리케이션 상태(state)를 저장하고 관리하는 역할입니다.

  • state: 애플리케이션의 현재 상태 (객체 형태)
  • reducer: 상태(state)를 변경하는 순수 함수
  • getState(): 현재 상태를 반환하는 함수
  • dispatch(action): 액션을 전달하여 상태 변경 요청
  • subscribe(): 상태 변경을 감지하고 실행할 함수를 등록

 

✅ Store 외부 (Redux를 사용하는 요소)

Redux는 단독으로 동작하는 게 아니라 UI와 함께 사용됩니다.

UI  →  Action  →  Store  →  Render UI
  • Admin (Redux DevTools): 상태 변화를 추적하고 디버깅하는 도구
  • Render (React/Next.js UI 업데이트): state 변경 시 컴포넌트가 리렌더링됨

 


 

2. Redux 폴더 구조 (Next.js/App Router + TypeScript 기준)

📂 src
 ┣ 📂 redux
 ┃ ┣ 📂 features
 ┃ ┃ ┣ 📂 counter
 ┃ ┃ ┃ ┣ 📜 counterSlice.ts  # 특정 기능(state, reducer, actions) 관리
 ┃ ┃ ┣ 📂 user
 ┃ ┃ ┃ ┣ 📜 userSlice.ts      # 사용자 관련 상태 관리
 ┃ ┣ 📜 store.ts              # Redux store 설정 (모든 리듀서 연결)
 ┣ 📂 components
 ┃ ┣ 📜 Counter.tsx           # Redux state를 사용하는 컴포넌트
 ┣ 📂 app
 ┃ ┣ 📂 layout.tsx            # 전체 앱 레이아웃 설정
 ┃ ┣ 📂 page.tsx              # 메인 페이지 (홈)
 ┃ ┣ 📂 providers.tsx         # Redux Provider 적용

 


 

3. Redux 기본 코드 (Next.js/App Router + TypeScript 기준)

 

1️⃣ Redux Store 설정 (store.ts)

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import userReducer from './features/user/userSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer,
    // ... 등등 등록해서 사용
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

  • configureStore()를 사용해 Redux store를 생성
  • RootState, AppDispatch 타입을 지정하여 TypeScript에서 사용 편리

 

2️⃣ 카운터 기능 (counterSlice.ts)

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
}

const initialState: CounterState = { value: 0 };

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
    reset: (state) => { state.value = 0; },
    setCount: (state, action: PayloadAction<number>) => { state.value = action.payload; },
  },
});

export const { increment, decrement, reset, setCount } = counterSlice.actions;
export default counterSlice.reducer;
  • Redux Toolkit의 createSlice를 사용해 액션과 리듀서를 한 번에 정의

 

3️⃣ Redux Provider 적용 (providers.tsx)

App Router에서는 layout.tsx에서 Redux를 적용할 수 없기 때문에, 별도의 providers.tsx 파일을 만들어 Redux Provider를 감싸줘야 합니다.

'use client';

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

interface ProvidersProps {
  children: React.ReactNode;
}

export function Providers({ children }: ProvidersProps) {
  return <Provider store={store}>{children}</Provider>;
}
  • 'use client'를 꼭 추가해야 함 (App Router에서는 기본적으로 서버 컴포넌트이므로 클라이언트 컴포넌트로 지정)
  • children을 받아 Redux Provider로 감싸서 상태를 관리할 수 있도록 설정

 

4️⃣ Redux 적용 (layout.tsx)

Next.js App Router에서는 layout.tsx가 모든 페이지의 공통 레이아웃을 설정하는 역할을 하므로 여기서 Providers.tsx를 감싸면 전체 애플리케이션에서 Redux 상태를 사용할 수 있습니다.

typescript
복사편집
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

 

5️⃣ 예시로 Counter 컴포넌트 생성 (Counter.tsx)

Redux 상태를 사용하여 카운터 UI를 만들기

typescript
복사편집
'use client';

import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset, setCount } from '../redux/features/counter/counterSlice';
import { RootState } from '../redux/store';

const Counter = () => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
      <button onClick={() => dispatch(setCount(10))}>Set to 10</button>
    </div>
  );
};

export default Counter;
  • 'use client' 추가 필수! (app/ 폴더 내부는 기본적으로 서버 컴포넌트이므로, 클라이언트 컴포넌트로 설정해야 Redux 사용 가능)
  • useSelector와 useDispatch를 사용해 Redux 상태를 가져오고, 액션을 실행

 

6️⃣ 홈 페이지에서 Counter 사용 (page.tsx)

Counter.tsx를 메인 페이지에서 불러와서 등록

import Counter from '../components/Counter';

export default function Home() {
  return (
    <main>
      <h1>Redux Counter Example</h1>
      <Counter />
    </main>
  );
}

 


 

4. App Router에서 Redux 적용 흐름 정리

  1. Redux store 생성 (store.ts)
  2. slice(counterSlice.ts)를 생성하여 상태 관리
  3. Redux Provider를 providers.tsx에 별도로 생성
  4. layout.tsx에서 Providers를 감싸 Redux 적용
  5. 클라이언트 컴포넌트('use client')에서 Redux 사용

 


 

5. 배운점

✅ 전역 상태 관리 (Props Drilling 해결)

  • 여러 컴포넌트에서 상태를 쉽게 공유할 수 있음
  • props를 계속 넘기는 번거로움을 해결

✅ 예측 가능한 상태 관리

  • state가 한 곳(store) 에서 관리되므로 변경 흐름을 쉽게 추적 가능
  • 상태 변경은 reducer를 통해서만 이루어져 코드가 예측 가능

✅ 강력한 디버깅 기능

  • Redux DevTools를 사용하면 state 변화 과정을 추적 가능
  • 이전 상태로 되돌리는 Time-travel debugging 가능

✅ 비동기 데이터 관리 용이

  • redux-thunk, redux-saga 등을 이용해 비동기 API 요청을 쉽게 처리 가능
  • useEffect에서 상태 관리하는 대신 Redux에서 해결 가능

'Frontend > TypeScript' 카테고리의 다른 글

[TypeScript] 9. 유틸리티 타입  (0) 2025.01.31
[TypeScript] 8. 조건부 타입  (0) 2025.01.30
[TypeScript] 7. 타입 조작하기  (0) 2025.01.29
[TypeScript] 6-2. 제네릭 활용  (1) 2025.01.28
[TypeScript] 6-1. 제네릭이란?  (0) 2025.01.28