🎬 현재 선택된 영화 제목을 저장하고, 변경할 수 있는 앱
- 기본 영화 제목은 '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;
📜 설명:
- **movieSlice.js**에서 createSlice를 사용해 영화 제목을 관리하는 상태(title)와 제목을 변경하는 액션(changeTitle)을 만든다.
- **store.js**에서 리덕스 스토어를 설정하고, movieReducer를 포함시켜서 상태 관리.
- **index.js**에서 Provider로 앱을 감싸서 리덕스 스토어를 React 앱과 연결.
- **App.js**에서 useSelector로 영화 제목을 가져오고, useDispatch로 제목을 바꾸는 액션을 dispatch.
- store.js:
- 리덕스의 store를 설정하고, movieReducer를 reducer로 등록.
- index.js:
- 리덕스 스토어를 Provider로 감싸서 앱에 제공.
- App.js:
- useSelector로 리덕스 상태에서 영화 제목을 가져오고,
- useDispatch로 제목을 변경하는 액션을 버튼 클릭 시 dispatch.
어떤 파일을 어떤 폴더로 나뉘어야 할까?는, 아래 게시글에 다뤄보았다.
해당 예제는 아래와 같이 폴더를 나뉘었다.
src/
├── app/
│ └── store.js ← 🔹 스토어(store) 위치
├── features/
│ └── movie/
│ └── movieSlice.js ← 🔸 슬라이스(reducer + 액션 정의) 위치
├── App.js
└── index.js
🚀 이제 연습할 준비 완료!
한 컴포넌트씩 코드를 직접 쳐보면서 이해해보자.
- 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에 등록 |
이 단계에서 해야 할 일
- store.js는 상태를 저장하는 Redux 저장소 역할을 한다.
- 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에 등록 |
이 단계에서 해야 할 일
- store.js는 상태를 저장하는 Redux 저장소 역할을 한다.
- 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'로 변경하는 액션을 디스패치 |
핵심
- useSelector로 store에서 상태(title)를 가져와서 렌더링!
- useDispatch로 버튼 클릭 시 changeTitle 액션을 디스패치해서 상태를 변경!
이제 실행하면?
- 처음에 영화 제목은 'Inception'이 표시되고.
- 버튼 클릭 시 영화 제목이 **'Interstellar'**로 바뀐다!
모든 파일을 다 연결했으면, 이제 앱을 실행해봐! 🚀
npm start 또는 yarn start로 리액트 앱을 실행하고, 버튼을 클릭하면 영화 제목이 바뀌는 걸 볼 수 있을 거야!
리덕스, 처음엔 어렵게 느껴질 수 있지만 이렇게 하나씩 익히다 보면 상태 관리가 훨씬 편해지고 강력해져. 😆
지금 예제는 너무 작아서 전역 상태 관리의 "장점"이 안 느껴지지..?
지금은 App.js 하나밖에 없고, 상태도 title 하나니까 useState로도 충분하지!
근데 앱이 커지면 상황이 완전히 달라져. 아래 예시 볼래?
현실적인 예제는 다음 글에서! 정리해볼게요 :)
'Studying > React' 카테고리의 다른 글
리액트 공부하기 - custom hooks? 그냥 함수 차이? (0) | 2025.05.07 |
---|---|
리액트 공부하기 - debounce, throttle (디바운스, 스로틀) (0) | 2025.05.07 |
리액트 공부하기 - 상태 관리 라이브러리 총정리: 장단점 비교 (0) | 2025.05.05 |
리액트, 자바스크립트 공부하기 - JSON, fetch, 비동기 함수(axios) (1) | 2025.05.05 |
리액트 공부하기 - 오류는 없는데, 화면에는 렌더링이 안될때? (0) | 2025.05.05 |