Redux
리덕스는 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리이다. 한편, 리액트 프로젝트의 경우 대부분의 작업시 부모 컴포넌트를 통해 하위 컴포넌트의 데이터를 업데이트하는 props 방식이 사용된다.
그러한 사용 방식은 프로젝트의 규모가 작으면 문제가 되지 않지만, 프로젝트의 규모가 커질수록 유지보수의 어려움이 발생한다. 한편 리덕스와 같은 상태 관리 라이브러리를 사용하여 데이터 상태를 컴포넌트 외부에서 관리하면 글로벌 상태관리를 비교적 쉽게 처리할 수 있다.
Redux와 useReducer
useReducer와 context API로 redux의 기능을 대부분 구현이 가능하다. 하지만 분명하게 redux와 useReducer의 차이는 존재한다.
Redux | useReducer |
store가 있어 전역상태 관리에 용이 | store가 없어 로컬 상태관리에 용이 |
유용한 함수와 Hooks가 내재 | 따로 함수나 hooks를 제작하여 관리 |
참고로 둘 중 하나만 사용해야하는 것은 아니며, 두 가지 모두 사용하여 프로젝트를 작업해도 문제는 없다.
Redux가 필요한 상황은 언제?
1. 복잡한 상태 관리가 필요한 대규모 애플리케이션에 적합하다.
2. 여러 구성 요소/페이지에서 전역 상태를 관리해야 하는 애플리케이션에 적합하다.
3. 비동기 작업을 자주 하는 프로젝트에서 적합하다.
3. 디버깅 및 성능 최적화를 위해 미들웨어 및 고급 도구가 필요한 애플리케이션에 적합하다.
Redux와 Redux Toolkit 설치
// redux 설치
npm install react-redux
npm install @reduxjs/toolkit
npm install --save redux-logger
redux-logger 사용 이유는?
redux의 reducer 실행 되기 전과 후의 log를 보기 편하도록 바꿔주기 때문에 해당 라이브러리를 사용한다.
Redux: 데이터 저장하고 불러오는 방법
createSlice
redux-toolkit을 사용하면 createSlice라는 함수를 사용할 수 있다. 이 함수는 reducer 함수의 객체, slice 이름, 초기 값을 받고 해당 action 생성자와 action type으로 슬라이스 reducer를 자동으로 생성한다.
이전 redux와 달리 createSlice를 사용하면 action과 reducer를 하나의 파일에서 관리할 수 있다.
createSlice parameters
- name: slice 이름을 나타내는 문자열로 저장. reducer가 undefined 값으로 호출될 때마다 사용됨.
- initialState: 초기 상태 값
- reducers: reducer 함수를 저장하는 객체, action type은 위의 name을 prefix로 사용하므로 자동 생성됨.
configureStore
redux toolkit에서는 store에 저장하는 값을 createStore대신에 configureStore 내장 함수로 저장한다.
configureStore parameters
- reducer: createSlice로 만든 리듀서들을 묶은 index 파일을 가져옴.
- middleware: 액션이 실행될 때 로그를 찍는 logger 등의 사용할 미들웨어를 나열하는 프로퍼티.
- devTools: 기본 설정값은 true이며, 개발자 도구의 사용 여부를 정함.
Provider
- 저장한 데이터 값을 불러오기 위해서는 반드시 Provider 태그를 index.js(App.js의 부모)에서 사용해야한다.
import store from '..'
// index.js
root.render(
<Provider store={store}>
<App />
</Provider>,
);
Redux로 state 저장 (예시)
- MovieStore.js와 같은 파일을 생성하여 저장하고 싶은 값을 파일 내에 저장한다.
- redux에서 데이터를 저장할 때 createSlice를 사용한다.
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
movies: [],
}
const MovieSlice = createSlice({
name: 'movies'
initialState, // property shorthand
reducers: {
// 필요한 reducer 함수들 저장
updateMovie: (state, action) => {
return (state = {
...state,
...action.payload,
});
},
resetMovie: () => {
return initialState;
},
}
})
// action과 reducer 내보내기
export const { updateMovie, resetMovie } = MovieSlice.actions;
export default MovieSlice.reducer
- 앞에서 export한 action을 dispatch 함수의 파라미터로 넣는다.
dispatch(updateMovie({movies: json.data, isLoading: false}));
- configureStore, logger를 사용하여 저장한 데이터를 export한다.
import { configureStore } from "@reduxjs/toolkit";
import { logger } from "redux-logger";
import MovieStore from "../redux/MovieStore.jsx"; // 이전에 저장한 곳에서 데이터 import
const store = configureStore({
reducer: {
MovieStore: MovieStore,
// createSlice로 만든 변수 명 = export한 데이터
// 여기서 예시는 변수명과 export한 데이터 명이 같다. MovieStore === MovieSlice.reducer
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(logger),
});
export default store;
저장한 데이터 불러오기
- configureStore로 저장한 데이터를 useSelector로 불러온다.
import { useSelector } from "react-redux";
// configureStore로 저장한 데이터 useSelector로 불러오기
const { movies } = useSelector(state => state.MovieStore);
실습1 - redux로 카운터 만들기
export default function ReduxCounter() {
const dispatch = useDispatch();
const { count } = useSelector((state) => state.CountStore);
return (
<div>
<h1>숫자 세기</h1>
<p>count: {count}</p>
{/* dispatch 안의 reducer 함수를 실행하려면 ()를 추가해야함! */}
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(reset())}>reset</button>
</div>
);
}
- 처음에 onClick에 reducer 함수들을 실행시켜주지 않아서 작동하지 않았다.
- dispatch의 state를 변경하기 위해서는 함수를 실행시켜야한다는 것을 잊지말자.
실습 2 - redux로 로그인 인증 앱 만들
import { createSlice } from "@reduxjs/toolkit";
const initial = {
auth: {
state: false,
btn: "로그인",
content: "되지 않았습니다.",
},
};
const AuthSlice = createSlice({
name: "auth",
initialState: initial,
reducers: {
// 필요한 reducer 함수들 저장
login: (state) => {
state.auth.state = true;
state.auth.btn = "로그아웃";
state.auth.content = "상태입니다.";
},
logout: (state) => {
state.auth.state = false;
state.auth.btn = "로그인";
state.auth.content = "되지 않았습니다.";
},
},
});
export const { login, logout } = AuthSlice.actions;
export default AuthSlice.reducer;
const toggleAuth = () => {
if (auth.state !== true) {
dispatch(login());
} else {
dispatch(logout());
}
};
실습 3 - redux로 할 일 목록 만들기
- 할 일 목록 만들기를 할 때에 추가할 때 id를 생성하는 것을 못했었다. 지난 시간에 배웠던 Date.now()를 사용하면 쉽게 해결할 수 있었지만, 이것을 발견하지 못해서 오랜 시간이 걸렸다.
// 목록의 id를 생성할 때에는 Date.now()를 사용하면 랜덤한 id가 생성된다.
const addTodo = (e) => {
if (todo.text !== "") {
dispatch(add({ id: Date.now(), text: todo.text }));
onReset();
}
};
- 추가하는 reducer를 객체로 저장하기 때문에 deleteTodo를 할 때 어떤 property를 삭제할지 { id : ... }와 같이 명시를 해주어야 한다.
- 그리고 그 값을 받아오는 함수에서도 객체의 property를 제대로 명시하지 않으면 에러가 발생하면서 값을 읽지 못한다.
<button
id={item.id}
onClick={(e) => {
dispatch(deleteTodo({ id: e.target.id }))
}}
>
삭제
</button>
//
deleteTodo: (state, action) => {
const nextState = state.filter((todo) => {
return todo.id !== Number(action.payload.id);
});
return nextState;
},
- 또한 객체의 다른 값은 제외하고 오로지 하나의 Property만 받아온다면 명시하지 않되, 받아오는 함수에서도 property를 생략해주어야 한다.
<button
id={item.id}
onClick={(e) => {
dispatch(deleteTodo((e.target.id))
}}
>
삭제
</button>
//
deleteTodo: (state, action) => {
const nextState = state.filter((todo) => {
// id 하나만 받아왔으므로 action.payload.id 대신에 action.payload로 해야 id를 인식한다.
return todo.id !== Number(action.payload);
});
return nextState;
},
실습4 - redux로 쇼핑카트 만들기
- 투두리스트와 유사하게 리스트에 id값, price 값으로 event.target.value 값을 전달했다. 상품의 명은 name 속성으로 전달했다.
const initialState = {
cart: [],
};
const CartSlice = createSlice({
...
reducers: {
addCart: (state, action) => {
state.cart = [
// state는 initialState 이므로 cart에 접근하기 위해서는 state.cart로 접근
...state.cart,
{
id: action.payload.id,
name: action.payload.name,
price: action.payload.price,
},
];
},
...
})
- initialState를 객체로 만들었을 경우에는 reducer 함수 안에서 데이터를 '='로 할당하지 않고 그냥 return만 하면 에러가 발생함.
const CartSlice = createSlice({
name: "cart",
initialState,
reducers: {
...
deleteCart: (state, action) => {
state.cart = state.cart.filter((item) => {
return item.id !== Number(action.payload);
});
},
}
})
본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.
출처 및 참고자료
1. 리덕스, https://react.vlpt.us/redux/
2. useReducer와 Redux의 차이점, https://kindjjee.tistory.com/136
useReducer와 Redux의 차이점
리액트로 코딩을 하던중, useReducer와 Redux를 헷갈리고 말았다. 분명 겉보기엔 비슷한데 (둘다 리듀서랑 디스패치를 쓰니깐) 다르다는 제로초님의 답변.... useReducer와 Redux는 다르다. 그리고 어떠한
kindjjee.tistory.com
3. [React] Redux와 useReducer의 차이, Context API 사용하기, https://recoderr.tistory.com/50
[React ] Redux와 useReducer의 차이, Context API 사용하기
Redux를 사용하는 이유 리액트 프로젝트의 경우 대부분의 작업시 부모 컴포넌트를 통해 하위 컴포넌트의 데이터를 업데이트 한다. (데이터의 연관이 있는 컴포넌트끼리 ref를 사용하여 전달할 수
recoderr.tistory.com
4. Redux 2: 데이터 저장하고 불러오기, https://velog.io/@jian09/Redux-2-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B3%A0-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0
Redux 2 : 데이터 저장하고 불러오기
Redux를 이용해 state를 저장하고 꺼내보자
velog.io
5. [Redux] Redux Toolkit - createSlice(), https://velog.io/@__dan_n/Redux-Toolkit-createSlice
[Redux] Redux Toolkit - createSlice()
Redux Toolkit 중 createSlice에 대한 정리
velog.io
6. Redux toolkit 을 React에 적용하기 ep.2 (counter 예제), https://velog.io/@super_iaan/Redux-toolkit-%EC%9D%84-React%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-ep.2-counter-%EC%98%88%EC%A0%9C
Redux toolkit 을 React에 적용하기 ep.2 (counter 예제)
CRA(create-react-app) +Typescript 위에서 redux-toolkit 공식문서에 있는 간단한 카운터를 따라해보자
velog.io
7. React - configureStore를 사용한 스토어 생성, https://phsun102.tistory.com/103
'외부활동 > 유데미X스나이퍼팩토리' 카테고리의 다른 글
[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 30일차 - firebase, 인증 서비스 구현 (0) | 2023.07.05 |
---|---|
[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 29일차 - 무비앱을 리덕스를 사용하여 데이터 흐름 관리해보기 (0) | 2023.07.04 |
[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 27일차 - 무비앱에 실시간 채팅기능 구현, 커스텀 훅 만들기 (0) | 2023.07.02 |
[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 26일차 - KPT, 회고 (0) | 2023.07.01 |
[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 25일차 - Component lifecycle에 대한 이해, Express, custom Hook 실습 (0) | 2023.07.01 |