React 상태관리 라이브러리 Redux의 설명과 사용법
❓Redux란 무엇인가요?
Redux(리덕스)란 JavaScript(자바스트립트) 상태관리 라이브러리이다.
Redux(리덕스)의 본질은 Node.js 모듈이다.
💡Redux를 사용하는 이유
👉 먼저 상태란?
- React에서 State는 component 안에서 관리되는 것이다.
- 자식 컴포넌트들 간의 다이렉트 데이터 전달은 불가능 하다.
- 자식 컴포넌트들 간의 데이터를 주고 받을 때는 상태를 관리하는 부모 컴포넌트를 통해서 주고 받는다.
- 그런데 자식이 많아진다면 상태 관리가 매우 복잡해진다.
- 상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야한다. => Props drilling 이슈
💡리덕스를 쓰면, 상태 관리를 컴포넌트 바깥에서 한다!
리덕스를 사용하면 상태값을, 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.
1. 전역 상태 저장소 제공
2. Props drilling 이슈 해결
🔖Redux 흐름
🧺 Store (스토어)
Store(스토어)는 상태가 관리되는 오직 하나의 공간이다.
- 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
- 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.
📃 Action (액션)
- Action(액션)은 앱에서 스토어에 운반할 데이터를 말한다. (주문서)
- Action(액션)은 자바스크립트 객체 형식으로 되어있다.
🎉 Reducer (리듀서)
- Action(액션)을 Store(스토어)에 바로 전달하는 것이 아니다.
- Action(액션)을 Reducer(리듀서)에 전달해야한다.
- Reducer(리듀서)가 주문을 보고 Store(스토어)의 상태를 업데이트하는 것이다.
- Action(액션)을 Reducer(리듀서)에 전달하기 위해서는 dispatch() 메소드를 사용해야한다.
Action(액션) 객체
가 dispatch()
메소드에 전달된다.
dispatch(액션)
를 통해 Reducer
를 호출한다.
Reducer
는 새로운 Store
를 생성한다.
예를 들어서 B 에서 일어나는 변화가 G 에 반영된다고 가정을 해보자.
1.스토어 설정
리덕스를 프로젝트에 적용하게 되면 이렇게 스토어가 생긴다. 스토어 안에는 프로젝트의 상태에 관한 데이터들이 담겨있다.
2. 컴포넌트의 스토어 구독
G 컴포넌트는 스토어에 구독을 한다. 구독을 하는 과정에서, 특정 함수가 스토어한테 전달이 된다. 그리고 나중에 스토어의 상태값에 변동이 생긴다면 전달 받았던 함수를 호출해준다.
3. 스토어에 상태 변경하라고 알려주기
이제 B 컴포넌트에서 어떤 이벤트가 생겨서, 상태를 변화 할 일이 생긴다. 이 때 dispatch 라는 함수를 통하여 액션을 스토어한테 던져준다. 액션은 상태에 변화를 일으킬 때 참조 할 수 있는 객체다. 액션 객체는 필수적으로 type 라는 값을 가지고 있어야 한다.
4. 리듀서를 통하여 상태를 변화시키기
액션 객체를 받으면 전달받은 액션의 타입에 따라 어떻게 상태를 업데이트 해야 할지 정의를 해줘야 한다. 이러한 업데이트 로직을 정의하는 함수를 리듀서라고 부른다.
리듀서 함수는 두가지의 파라미터를 받습니다.
- state: 현재 상태
- action: 액션 객체
그리고, 이 두가지 파라미터를 참조하여, 새로운 상태 객체를 만들어서 이를 반환합니다.
5. 상태가 변화가 생기면, 구독하고 있던 컴포넌트에게 알림
상태에 변화가 생기면, 이전에 컴포넌트가 스토어한테 구독 할 때 전달해줬었던 함수 listener 가 호출된다. 이를 통하여 컴포넌트는 새로운 상태를 받게되고, 이에 따라 컴포넌트는 리렌더링을 한다.
🔥Redux 간단하게 시작해보기
# NPM
npm install redux
npm install react-redux
# Yarn
yarn add redux react-redux
먼저 리덕스를 설치한다.
1. RootReducer 정의
- 여러 reducer을 사용하는 경우 reducer을 하나로 묶어주는 메소드
// reducers/index.js
import { combineReducers } from "redux";
import counter from "./counter";
const rootReducer = combineReducers({
counter
});
export default rootReducer;
2. 세부 reducer 정의
// reducers/counter.js
export const INCRESE = "COUNT/INCRESE";
export const increseCount = count => ({ type: INCRESE, count });
const initalState = {
count: 0
};
const counter = (state = initalState, action) => {
switch (action.type) {
case INCRESE:
return {
...state,
count: action.count
};
default:
return state;
}
};
3. store 만들기
import { compose, createStore, applyMiddleware } from "redux";
import rootReducer from '../reducers/index';
import thunk from "redux-thunk";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
4. app에 store 넣고, 만든 reducer 반영
<Provider> 안써서 엄청나게 눈물을 흘리면서 왜 안될까 찾았던…
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import logger from "redux-logger";
import { composeWithDevTools } from "redux-devtools-extension";
import App from "./App";
import rootReducer from "./reducers";
// 배포 레벨에서는 리덕스 발동시 찍히는 logger를 사용하지 않습니다.
const enhancer =
process.env.NODE_ENV === "production"
? compose(applyMiddleware())
: composeWithDevTools(applyMiddleware(logger));
// 위에서 만든 reducer를 스토어 만들때 넣어줍니다
const store = createStore(rootReducer, enhancer);
ReactDOM.render(
// 만든 store를 앱 상위에 넣어줍니다.
<Provider store={store}>
<App />
</Provider>
document.getElementById('root'),
);
5. 컴포넌트에서 redux 사용하기
import { useSelector, useDispatch } from "react-redux";
import { increseCount } from "reducers/count";
// dispatch를 사용하기 위한 준비
const dispatch = useDispatch();
// store에 접근하여 state 가져오기
const { count } = useSelector(state => state.counter);
const increse = () => {
// store에 있는 state 바꾸는 함수 실행
dispatch(increseCount());
};
const Counter = () => {
return (
<div>
{count}
<button onClick={increse}>증가</button>
</div>
);
};
export default Counter;
➕추가 ) Redux-thunk?
요약 :
redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어입니다.
이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있습니다.
함수를 디스패치 할 때에는, 해당 함수에서 dispatch
와 getState
를 파라미터로 받아와주어야 합니다. 이 함수를 만들어주는 함수를 우리는 thunk 라고 부릅니다.
➕추가 ) Redux-saga?
요약 ) redux-saga는 redux-thunk 다음으로 가장 많이 사용되는 라이브러리입니다.
- 비동기 작업을 할 때 기존 요청을 취소 처리 할 수 있습니다
- 특정 액션이 발생했을 때 이에 따라 다른 액션이 디스패치되게끔 하거나, 자바스크립트 코드를 실행 할 수 있습니다.
- 웹소켓을 사용하는 경우 Channel 이라는 기능을 사용하여 더욱 효율적으로 코드를 관리 할 수 있습니다
- API 요청이 실패했을 때 재요청하는 작업을 할 수 있습니다.
이 외에도 다양한 까다로운 비동기 작업들을 redux-saga를 사용하여 처리 할 수 있습니다.
❗️결론 : Redux의 장점
- 상태를 예측 가능하게 만든다. (순수함수를 사용하기 때문)
- 유지보수 (복잡한 상태 관리와 비교)
- 디버깅에 유리 (action과 state log 기록 시) → redux dev tool (크롬 확장)
- 테스트를 붙이기 용의 (순수함수를 사용하기 때문)
Reference
- https://kyounghwan01.github.io/blog/React/redux/redux-basic/#%E1%84%8F%E1%85%A5%E1%86%B7%E1%84%91%E1%85%A9%E1%84%82%E1%85%A5%E1%86%AB%E1%84%90%E1%85%B3%E1%84%8B%E1%85%A6%E1%84%89%E1%85%A5-redux-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5
- https://hanamon.kr/redux%EB%9E%80-%EB%A6%AC%EB%8D%95%EC%8A%A4-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/
- https://velopert.com/3528