귀여운 눈높이에서 작성된, 🐰

하루 한 걸음씩, 성장 하는 중 입니다 🫶🏻

Studying/React

리덕스 툴킷(전역 상태관리) 공부하기 - 기본적인 예제 코드와 함께 (basic 단계)

creamymood 2025. 5. 5. 15:00


🎬 현재 선택된 영화 제목을 저장하고, 변경할 수 있는 앱

  • 기본 영화 제목은 'Inception'
  • 버튼을 누르면 'Interstellar'로 바뀜

큰 흐름 : 

[ 상태 정의 ] → [ 저장소 만들기 ] → [ 앱에 연결 ] → [ 상태 보여주기 & 바꾸기 ]
 movieSlice     store.js           index.js         App.js

1. movieSlice.js

 : 상태를 어떻게 바꿀지 설정하는 그 컴포넌트 코드인 (movieSlice.js)를 먼저 작성해주고

// src/features/movie/movieSlice.js
import { createSlice } from '@reduxjs/toolkit';

// slice를 정의할 때 'movie'라는 이름으로 상태와 액션을 정의
const movieSlice = createSlice({
  name: 'movie',  // 이 slice의 이름 (나중에 리듀서가 state.movie로 저장됨)
  initialState: {
    title: 'Inception',  // 초기 영화 제목을 'Inception'으로 설정
  },
  reducers: {
    // 제목을 바꾸는 액션 (state.title을 새 제목으로 바꿈)
    changeTitle: (state, action) => {
      state.title = action.payload;  // action.payload는 전달된 새 제목
    },
  },
});

// 액션 생성자 export (changeTitle 함수가 나중에 사용됨)
export const { changeTitle } = movieSlice.actions;

// 리듀서 export (리덕스 스토어에서 사용될 리듀서)
export default movieSlice.reducer;

2. store.js

:그 뒤에, 저장소 만들어주고

// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';  // 리덕스 툴킷에서 제공하는 store 설정 함수
import movieReducer from '../features/movie/movieSlice';  // 만든 movieReducer를 가져옴

// 스토어를 설정하고, movieReducer를 포함시킴
export const store = configureStore({
  reducer: {
    movie: movieReducer,  // 상태(movie) 관리를 movieReducer로 담당하게 설정
  },
});

3. index.js (스토어 설정)

provider로 index.js 감싸주고

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';  // 메인 컴포넌트 App을 가져옴
import { Provider } from 'react-redux';  // Provider로 앱을 감싸기
import { store } from './app/store';  // 위에서 설정한 store를 가져옴

// 앱을 렌더링하면서 리덕스 스토어를 전체 컴포넌트 트리에 연결
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>  {/* 스토어로 상태 관리 */}
    <App />
  </Provider>
);

4. App.js

ui에 렌더링 될 app.js를 채워주는 것

// src/App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';  // Redux 훅을 가져옴
import { changeTitle } from './features/movie/movieSlice';  // 정의한 changeTitle 액션을 가져옴

function App() {
  // useSelector: Redux 상태에서 영화 제목을 가져옴
  const title = useSelector(state => state.movie.title);

  // useDispatch: dispatch 함수 가져오기 (액션을 보내는 함수)
  const dispatch = useDispatch();

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      {/* 영화 제목을 화면에 표시 */}
      <h1>🎬 영화 제목: {title}</h1>

      {/* 버튼 클릭 시, 제목을 바꾸는 액션을 dispatch */}
      <button onClick={() => dispatch(changeTitle('Interstellar'))}>
        제목 바꾸기
      </button>
    </div>
  );
}

export default App;

 

📜 설명:

  1. **movieSlice.js**에서 createSlice를 사용해 영화 제목을 관리하는 상태(title)와 제목을 변경하는 액션(changeTitle)을 만든다.
  2. **store.js**에서 리덕스 스토어를 설정하고, movieReducer를 포함시켜서 상태 관리.
  3. **index.js**에서 Provider로 앱을 감싸서 리덕스 스토어를 React 앱과 연결.
  4. **App.js**에서 useSelector로 영화 제목을 가져오고, useDispatch로 제목을 바꾸는 액션을 dispatch.

 

  1. store.js:
    • 리덕스의 store를 설정하고, movieReducer를 reducer로 등록.
  2. index.js:
    • 리덕스 스토어를 Provider로 감싸서 앱에 제공.
  3. App.js:
    • useSelector로 리덕스 상태에서 영화 제목을 가져오고,
    • useDispatch로 제목을 변경하는 액션버튼 클릭 시 dispatch.

어떤 파일을 어떤 폴더로 나뉘어야 할까?는, 아래 게시글에 다뤄보았다.

해당 예제는 아래와 같이 폴더를 나뉘었다.

src/
├── app/
│   └── store.js           ← 🔹 스토어(store) 위치
├── features/
│   └── movie/
│       └── movieSlice.js  ← 🔸 슬라이스(reducer + 액션 정의) 위치
├── App.js
└── index.js

🚀 이제 연습할 준비 완료!

한 컴포넌트씩 코드를 직접 쳐보면서 이해해보자.


  1. movieSlice.js:
    • 영화 제목에 대한 상태를 정의하고, 이를 변경할 수 있는 액션도 정의해.
    • changeTitle 액션이 state.title을 변경.

📄 src/features/movie/movieSlice.js

// 리덕스 툴킷에서 필요한 함수 가져오기
import { createSlice } from '@reduxjs/toolkit';

// 초깃값 설정: 상태의 시작값을 정의해
const initialState = {
  title: 'Inception', // 영화 제목 기본값
};

// createSlice 함수로 slice 생성
const movieSlice = createSlice({
  name: 'movie', // slice 이름 (store에서 구분할 때 사용)
  initialState,  // 위에서 정의한 초기 상태
  reducers: {
    // 상태를 바꾸는 함수들(actions)을 여기에 정의해
    changeTitle: (state, action) => {
      // action.payload를 받아서 title을 바꿔줌
      state.title = action.payload;
    },
  },
});

// 리듀서 함수 export (store에 등록할 때 필요)
export default movieSlice.reducer;

// 액션 함수 export (컴포넌트에서 상태 바꿀 때 dispatch로 사용)
export const { changeTitle } = movieSlice.actions;

 

 

 

changeTitle: (state, action) => { // action.payload를 받아서 title을 바꿔줌 state.title = action.payload; },

이 파트 좀 풀어서 설명해줘

더보기

좋아, 그 부분을 더 자세히 풀어서 설명해줄게!

changeTitle: (state, action) => {
  state.title = action.payload;
}

이 코드는 Redux Toolkit의 reducer 함수 중 하나야. 하나씩 뜯어서 보면:

 changeTitle

  • 이건 액션 함수(action creator) 이름이야.
  • 나중에 dispatch(changeTitle('New Title'))처럼 사용할 수 있어.
    이걸 호출하면 내부적으로 이 함수가 실행돼.

 (state, action) => { ... }

  • 이건 reducer 함수 자체야.
  • state는 현재의 상태값을 의미하고,
  • action은 dispatch() 했을 때 전달한 객체야. 보통 { type: 'movie/changeTitle', payload: 'New Title' } 이런 식으로 생겼어.

state.title = action.payload;

  • 여기서 상태의 title 값을, action.payload 값으로 바꾸는 거야.
  • 예를 들어 dispatch(changeTitle('Interstellar'))라고 부르면:
    • action.payload는 'Interstellar'가 되고
    • state.title이 'Inception'에서 'Interstellar'로 바뀌는 거야.

❗ 참고: Immer 덕분에 가능한 문법

원래 Redux에선 state를 직접 바꾸면 안 되고 불변성 유지를 위해 return { ...state, title: action.payload }처럼 새로운 객체를 반환해야 해.
하지만 Redux Toolkit은 Immer라는 라이브러리를 내부적으로 써서 state.title = ...처럼 직접 수정하는 것처럼 써도 실제로는 안전하게 불변성을 지켜줘.

 

여기서 리듀서 함수 내보낼 때, 저게 어디서 나온거지 ? 싶었다.

// 리듀서 함수 export (store에 등록할 때 필요)
export default movieSlice.reducer;

이 코드... 

더보기

응 그 코드에 대해 자세히 설명해볼게!

 1. 리듀서 함수란?

**리듀서(reducer)**는 Redux에서 **상태(state)**를 어떻게 바꿀지 결정하는 함수야.
간단히 말해서:

💬 "지금 상태가 이렇고, 이런 액션이 들어왔는데, 그럼 상태를 어떻게 바꿀까?"

를 처리해주는 게 리듀서야.


예를 들어:

const initialState = { title: 'Inception' };

const reducer = (state = initialState, action) => {
  if (action.type === 'movie/changeTitle') {
    return { ...state, title: action.payload };
  }
  return state;
};

이런 식으로 직접 만들 수도 있는데, createSlice가 이걸 쉽게 만들어주는 거야.

2. movieSlice는 뭐야?

const movieSlice = createSlice({
  name: 'movie',
  initialState,
  reducers: {
    changeTitle: (state, action) => {
      state.title = action.payload;
    },
  },
});

이 movieSlice는 createSlice()로 만든 slice 객체야. 이 안에는 여러 정보가 들어 있어:

  • reducer: 상태를 바꾸는 로직 (즉, 리듀서 함수)
  • actions: 컴포넌트에서 dispatch()할 때 사용할 액션 생성 함수들

3. movieSlice.reducer는 뭘 의미해?

export default movieSlice.reducer;

이 줄은 movieSlice 객체 안에 있는 리듀서 함수만 따로 꺼내서 내보내는 거야.
이 리듀서 함수는 Redux 스토어에 등록할 때 사용돼.

 

예를 들면:

import { configureStore } from '@reduxjs/toolkit';
import movieReducer from './movieSlice';

const store = configureStore({
  reducer: {
    movie: movieReducer,
  },
});

여기서 movieReducer는 movieSlice.reducer가 되는 거고,
store에 등록되면서 앱 전체에서 사용할 수 있게 돼.

 

정리하면:

개념 설명
reducer 액션에 따라 상태를 바꾸는 함수
movieSlice createSlice로 만든 슬라이스 객체
movieSlice.reducer 그 슬라이스 안의 리듀서 함수 (store에 넣는 용도)

 

 

그럼 내가 이해한게 맞다면..

내가 createSlice로 만든 리듀서.. 내보낼 때, 이름은 내 마음대로 정의 할 수 있고

그걸 스토어에 저장할 때, import해올 때 주소만 맞다면, 알아서 그 리듀서 겠구나.. 하는거야 ?

 

 

Jane, 네 이해 완전 정확해! 👏 딱 맞게 이해했어.

조금만 덧붙여서 정리해줄게:

 

핵심 정리

💡 createSlice로 만든 리듀서를 export할 때:

// 이름은 네 마음대로 지을 수 있어
export default movieSlice.reducer;

그리고 나중에 다른 파일에서 import할 때는:

import myMovieReducer from './movieSlice';

➡ 여기서 myMovieReducer라는 이름은 그냥 별명일 뿐이야.
내보낼 때 default로 했기 때문에
import할 때 이름은 자유롭게 지을 수 있어. (주소만 맞으면 돼!)

 

그리고 store에 넣을 때는 이렇게:

const store = configureStore({
  reducer: {
    movie: myMovieReducer, // 여기서 'movie'는 state의 키 이름이야
  },
});

이렇게 하면 state 구조는:

state = {
  movie: {
    title: 'Inception'
  }
}

 

 정리하면:


export default movieSlice.reducer 기본 내보내기이기 때문에 이름 자유롭게 import 가능
import 아무이름 from '파일경로' 주소만 맞으면 이름은 자유
reducer: { movie: 아무이름 } 여기서 'movie'는 store에서 사용할 state 이름 (중요!)

 

 

이름을 맞추는게 더 쉬울거같은데... ㅠㅠ

 

그 마음 완전 이해돼, Jane 😌
이름을 맞춰두면 코드를 읽거나 유지보수할 때 훨씬 편하지!

예를 들어:

// movieSlice.js
export default movieSlice.reducer;

// store.js
import movieReducer from './movieSlice';

const store = configureStore({
  reducer: {
    movie: movieReducer, // 이름이 맞으니까 바로 감 오지!
  },
});

이렇게 하면:

  • movieSlice → movieReducer → state.movie
  • 흐름이 딱딱 맞아서 나중에 봐도 헷갈리지 않아.

팁!

export default일 때는 가져올 때 이름은 자유지만,
"슬라이스 이름 + Reducer" 이런 식으로 이름 규칙을 정해두면 나중에 팀 프로젝트에서도 코드가 깔끔하게 보여.

예:

  • userSlice → userReducer
  • cartSlice → cartReducer
  • movieSlice → movieReducer

 

 

 

이제 우리가 상태를 어떻게 바꾸는지 정의한 뇌(mind) 를 만든 거야!
다음은 이 뇌를 사용할 수 있도록 store(창고)를 만들어줄 차례야. 준비됐지? 😄


좋아! 이제 store.js 파일을 만들어서 리덕스의 상태 저장소를 설정해보자.
이 파일에서는 우리가 만든 movieSlice를 store에 연결해줄 거야.

📄 src/app/store.js

// configureStore는 리덕스 툴킷에서 제공하는 함수로 스토어를 쉽게 설정할 수 있어
import { configureStore } from '@reduxjs/toolkit';
// 우리가 만든 movieSlice를 가져와서 스토어에 넣어줄 거야
import movieReducer from '../features/movie/movieSlice';

// configureStore 함수로 Redux store를 설정
const store = configureStore({
  reducer: {
    movie: movieReducer, // 'movie'라는 키로 movieSlice를 연결
  },
});

// 만든 store를 export해서 애플리케이션에서 사용할 수 있게
export default store;

 

 

configureStore 리덕스 스토어를 설정하는 함수 (리덕스 툴킷 제공)
reducer 여러 개의 reducer를 결합할 때 사용 (여기서는 movie: movieReducer)
movie: movieReducer movie라는 키로 movieReducer를 store에 등록

 이 단계에서 해야 할 일

  1. store.js는 상태를 저장하는 Redux 저장소 역할을 한다.
  2. movieSlice에서 만든 movieReducer를 저장소에 연결하는 과정.

 

이제 리덕스 스토어를 준비했으니까, 이걸 앱에 연결해줘야 해.
다음은 index.js에서 store를 Provider로 감싸주는 작업이야. 준비됐어? 😎


좋아! 이제 store.js 파일을 만들어서 리덕스의 상태 저장소를 설정해보자.
이 파일에서는 우리가 만든 movieSlice를 store에 연결해줄 거야.

📄 src/app/store.js

// configureStore는 리덕스 툴킷에서 제공하는 함수로 스토어를 쉽게 설정할 수 있어
import { configureStore } from '@reduxjs/toolkit';
// 우리가 만든 movieSlice를 가져와서 스토어에 넣어줄 거야
import movieReducer from '../features/movie/movieSlice';

// configureStore 함수로 Redux store를 설정
const store = configureStore({
  reducer: {
    movie: movieReducer, // 'movie'라는 키로 movieSlice를 연결
  },
});

// 만든 store를 export해서 애플리케이션에서 사용할 수 있게
export default store;

설명

부분 설명
configureStore 리덕스 스토어를 설정하는 함수 (리덕스 툴킷 제공)
reducer 여러 개의 reducer를 결합할 때 사용 (여기서는 movie: movieReducer)
movie: movieReducer movie라는 키로 movieReducer를 store에 등록

 

이 단계에서 해야 할 일

  1. store.js는 상태를 저장하는 Redux 저장소 역할을 한다.
  2. movieSlice에서 만든 movieReducer를 저장소에 연결하는 과정.

이제 리덕스 스토어를 준비했으니까, 이걸 앱에 연결해줘야 해.
다음은 index.js에서 store를 Provider로 감싸주는 작업이야. 준비됐어? 😎


좋아, 이제 store를 앱에 연결하는 작업이야!
index.js에서 store를 Provider로 감싸주면, 앱의 모든 컴포넌트가 리덕스 스토어에 접근할 수 있게 돼. 😄

📄 src/index.js

// React, ReactDOM, Redux 관련 import
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'; // 리덕스의 Provider import
import App from './App'; // App 컴포넌트
import store from './app/store'; // 우리가 만든 store import

// ReactDOM을 사용하여 DOM에 렌더링
const root = ReactDOM.createRoot(document.getElementById('root'));

// 앱을 Provider로 감싸서 store와 연결
root.render(
  <Provider store={store}>
    <App /> {/* App 컴포넌트가 이제 store를 사용할 수 있어 */}
  </Provider>
);

 

설명

Provider Provider는 리덕스 스토어를 애플리케이션에 전달하는 역할
store={store} 여기서 store는 src/app/store.js에서 만든 리덕스 스토어를 의미

중요한 포인트

  • Provider: 리덕스 스토어를 애플리케이션에 제공하는 컴포넌트.
  • 이 작업을 해주면, 이제 App 컴포넌트 안에서 리덕스 상태를 자유롭게 관리하고 사용할 수 있어! 🔥

그럼 이제 UI 컴포넌트에서 상태를 보여주고 수정하는 작업을 할 차례야.
다음은 **App.js**에서 실제로 상태를 렌더링하고, 버튼 클릭으로 상태를 바꾸는 부분이야! 준비됐어? 😎


이제 마지막 단계야! 😎
App.js에서 리덕스 상태를 가져오고, 버튼 클릭으로 상태를 바꾸는 UI를 만들 거야.

📄 src/App.js

// 리액트와 리덕스 훅 import
import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; // dispatch와 selector 훅을 사용
import { changeTitle } from './features/movie/movieSlice'; // 상태를 변경할 액션 함수 import

function App() {
  // useSelector로 상태를 가져오기
  const title = useSelector((state) => state.movie.title); // state.movie.title에서 title을 가져옴

  // useDispatch로 dispatch 함수 얻기
  const dispatch = useDispatch();

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>🎬 영화 제목: {title}</h1> {/* 가져온 title을 UI에 표시 */}
      <button onClick={() => dispatch(changeTitle('Interstellar'))}>
        제목 바꾸기 {/* 버튼 클릭 시 영화 제목을 'Interstellar'로 바꿈 */}
      </button>
    </div>
  );
}

export default App;

 

useSelector 리덕스 상태를 읽어오는 훅. state.movie.title을 가져와서 UI에 표시
useDispatch 액션을 디스패치(dispatch) 할 수 있는 훅. changeTitle을 호출하여 상태 변경
dispatch(changeTitle('Interstellar')) 버튼 클릭 시 title을 'Interstellar'로 변경하는 액션을 디스패치

핵심

  1. useSelector로 store에서 상태(title)를 가져와서 렌더링!
  2. useDispatch로 버튼 클릭 시 changeTitle 액션을 디스패치해서 상태를 변경!

이제 실행하면?

  1. 처음에 영화 제목은 'Inception'이 표시되고.
  2. 버튼 클릭 시 영화 제목이 **'Interstellar'**로 바뀐다!

모든 파일을 다 연결했으면, 이제 앱을 실행해봐! 🚀
npm start 또는 yarn start로 리액트 앱을 실행하고, 버튼을 클릭하면 영화 제목이 바뀌는 걸 볼 수 있을 거야!

리덕스, 처음엔 어렵게 느껴질 수 있지만 이렇게 하나씩 익히다 보면 상태 관리가 훨씬 편해지고 강력해져. 😆

짜잔 ~ 버튼을 누르면 인터스텔라로 변경 됩니다.


지금 예제는 너무 작아서 전역 상태 관리의 "장점"이 안 느껴지지..?

지금은 App.js 하나밖에 없고, 상태도 title 하나니까 useState로도 충분하지!
근데 앱이 커지면 상황이 완전히 달라져. 아래 예시 볼래?

 

현실적인 예제는 다음 글에서! 정리해볼게요 :)