과제

요구사항

무비앱을 리덕스를 사용하여 데이터 흐름 관리해보기

구조

movie-app
├─ env
│  └─ .env
├─ src
│  ├─ index.css
│  ├─ main.jsx
│  ├─ App.css
│  ├─ App.jsx
│  ├─ routes.jsx
│  ├─ components
│  │  ├─ Gnb.jsx
│  │  ├─ Pagination.jsx
│  │  └─ movies
│  │     ├─ detail.jsx
│  │     └─ item.jsx
│  ├─ hooks
│  │  ├─ useFetchMovies.js
│  │  └─ usePagination.js
│  ├─ pages
│  │  ├─ index.jsx
│  │  ├─ cart
│  │  └─ movies
│  │     ├─ [id].jsx
│  │     └─ index.jsx
│  └─ store
│     ├─ index.js
│     └─ modules
│        └─ favoriteMovie.js
├─ index.html
├─ .eslintrc.cjs
├─ .gitignore
├─ .prettierrc.json
├─ package-lock.json
├─ package.json
└─ vite.config.js

리덕스와 관련해서 보아야 할 폴더구조는 store이다

당연히 하나의 앱에는 하나의 스토어만이 존재해야 하지만, 데이터의 특성에 따라 나누고 끌어다가 쓸 분리가 필요하다고 생각하여 modules폴더를 두었다
(사실 당장에는 즐겨찾기 하는 저장소만이 필요해서 이런 분리가 반드시 필요했냐고 묻는다면 대답은 No이다_그러나 미래를 위해서)

이 모듈들을 index.js에서 끌어다가 createStore를 해주면 된다

코드

// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import favoriteMovieReducer from './modules/favoriteMovie';

const store = configureStore({
	reducer: {
		favoriteMovie: favoriteMovieReducer,
	},
});

export default store;

configureStore에는 reducer뿐만 아니라 middleware도 설정 가능하다

// src/store/modules/favoriteMovie.js
import { createSlice } from '@reduxjs/toolkit';

export const favoriteMovie = createSlice({
	// action.type에 아래의 name + '/addFavorite' 요런식으로 붙어서 실행 됨
	name: 'favoriteMovie',
	initialState: {
		list: [],
	},
	reducers: {
		addFavorite: (state, action) => {
			console.log(action);
			state.list.push(action.payload);
		},
		deleteFavorite: (state, action) => {
			state.list = state.list.filter(movie => movie.id !== action.payload);
		},
	},
});

export const { addFavorite, deleteFavorite } = favoriteMovie.actions;

// 여기서의 state.favoriteMovie의 favoriteMovie index에서 createStore에서 설정
export const selectFavoriteMovie = state => state.favoriteMovie.list;

export default favoriteMovie.reducer;

redux-toolkit의 미친점(?)은 createSlice의 내부 코드에 immer 라이브러리를 사용하여서 불변성을 지키면서 코드를 작성하지 않아도 된다

Remember: reducer functions must always create new state values immutably, by making copies! It's safe to call mutating functions like Array.push() or modify object fields like state.someField = someValue inside of createSlice(), because it converts those mutations into safe immutable updates internally using the Immer library, but don't try to mutate any data outside of createSlice!
출처 - 리덕스 공식문서

// src/pages/movies/[id].jsx
import { useParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { addFavorite, deleteFavorite, selectFavoriteMovie } from '../../store/modules/favoriteMovie';
// Hook
import useFetchMovies from '../../hooks/useFetchMovies';
// Component
import MovieDetail from '../../components/movies/detail';

const MovieDetailPage = () => {
	// routes
	const { id } = useParams();
	// redux
	const favoriteList = useSelector(selectFavoriteMovie);
	const dispatch = useDispatch();
	// TODO: delete line when deploy
	console.log(favoriteList);

	const { data, isLoading, error } = useFetchMovies('/movie_details.json', `movie_id=${id}`);

	if (isLoading) return <p>Loading...</p>;
	if (error) console.error(error);

	return (
		<>
			{data ? (
				<>
					<MovieDetail
						item={data?.movie}
						dispatch={dispatch}
						addFavorite={addFavorite}
						deleteFavorite={deleteFavorite}
					/>
				</>
			) : null}
		</>
	);
};

export default MovieDetailPage;

selectFavoriteMovie를 모듈에서 가져와서 react-reduxuseSelector에 넣어준다
dispatch를 발생시키려면 react-reduxuseDispatch()를 통해 사용가능하다

// src/components/movies/detail.jsx
const MovieDetail = ({ item, dispatch, addFavorite, deleteFavorite }) => {
	return (
		<div>
			<img src={item.background_image_original} alt={`${item.background_image_original}의배경화면`} />
			<h3>{item.title}</h3>
			<p>{item.year}</p>
			<p>{item.description_full}</p>
			<p>{item.rating}</p>
			<p>{item.runtime}</p>
			<img src={item.large_cover_image} alt={`${item.large_cover_image}의커버이미지`} />
			<button
				onClick={() => {
					dispatch(addFavorite(item));
				}}>
				Add favorite
			</button>
			<button
				onClick={() => {
					dispatch(deleteFavorite(item.id));
				}}>
				Delete favorite
			</button>
		</div>
	);
};

export default MovieDetail;

본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

profile
😂그냥 직진하는 (예비)개발자

0개의 댓글