Redux
React에 Redux를 입혀서 상태관리하는 법을 다루어 보았f다.
들어가기 앞서 살짝 정리해보면
- src/actions/
- action 객체를 리턴하는 함수들의 집합
- src/reducers/
- state 를 관리하는 reducer 생성
- src/index.js
- createStore() 로 저장소 생성, Provider로 전달
- src/App.js
- userSelector , useDispatch 를 이용해 state 사용
dispatch()의 인자로 액션 생성 함수를 넣었고, 액션 객체가 리듀서 안으로 들어가 상태를 관리하는 형태
thunk
비동기 로직을 state에 넣기 위해서 우리는 미들웨어인 thunk를 사용했다.
클로저 형태로 되어있는 이 친구는 액션 생성 함수 내에서 dispatch를 인자로 받는 함수를 리턴해 비동기 처리를 구현했다.
const fetchingPopularMovies = () => {
return dispatch => {
return axios
.get (
`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=en-US`
)
.then (response => {
dispatch(fetchPopularMovies(response.data.results));
})
.catch(error => {
throw error;
})
}
}
이 방식이 살짝 맘에 안드는데 액션 생성 함수는 말그대로 퓨-어한 액션 객체를 만들기만 하면 그만이지만. 비동기 연산을 여기서
수행하기 떄문에
또 ES6에서 이런 패턴은 callback 지옥을 개선하기 위해 Promise라는 개념도 생겼다. 저 코드는 옛날 스타일이라는 느낌이 온다.
코드를 모던하게 바꿀 saga를 이용해보자.
saga
- saga를 이해하기 위해서 ES6 - Genreator에 대한 개념이 필요하다
- UnsPlash의 API를 이용해 비동기 로직이 포함된 상태관리를 해보자.
action
src/actions/imageAction.js
// rootSaga 제너레이터 함수 작성시 사용
const loadImages = () => {
return {
type: "LOAD_IMAGES"
};
};
const loadImageSuccess = imgs => {
return {
type: "LOAD_IMAGES_SUCCESS",
images: imgs
};
};
const loadImagesFail = error => {
return {
type: "LOAD_IMAGES_SUCCESS",
error
};
};
export default { loadImages, loadImageSuccess, loadImagesFail };
언뜻 보면 필요없어 보이는 loadImages 함수는 추후 rootSaga 제너레이터 함수를 작성할때 필요하다.
reducer
리듀서도 빠르게 만들어주자.
src/reducers/imageReducer.js
const images = (state = [], action) => {
switch (action.type) {
case "LOAD_IMAGES_SUCCESS":
return [...state, ...action.images];
case "LOAD_IMAGES_FAIL":
return [...state, action.error];
default:
return state;
}
};
export default images;
여태까지 일반 Redux를 작성할 때와 똑같다.
Store, MiddleWare
이제 미들웨어를 store에 추가해준다.
src/index.js
.
.
import createSagaMiddleware from "redux-saga";
import rootSaga from "./saga";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
compose(
applyMiddleware(sagaMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
sagaMiddleware.run(rootSaga);
.
.
우선 createSagaMiddleware() 함수로 미들웨어를 생성한다.
그 후 store thunk 와 똑같이 applyMiddleware로 미들웨어를 넣어준다.
compose 안에 window, ~~ 구문은 리덕스 개발 툴을 사용하기 위해 작성한것이다.
이후 미들웨어에서 run 함수를 실행한다.
이것은 마치 이벤트 리스너를 열어서 rootSaga에 해당하는 액션이 올때를 기다리는 것이다.
일반 리덕스를 사용할 때와는 다르게 두 가지가 추가되었다.
- createSagaMiddleWare() 로 미들웨어 실행
- 미들웨어 run 함수 실행
saga
이제 계속해서 언급된 rootSaga를 작성해보자.
src/saga/index.js
import { takeEvery } from "redux-saga/effects";
function* rootSaga {
yield takeEvery("LOAD_IMAGES", workerSaga);
}
export default rootSaga;
제너레이터 함수로 rootSaga를 만들었다.
takeEvery는 redux-saga의 이펙트 중 하나로
dispatch에 의해 action.type이 "LOAD_IMAGES"d인 객체가 올때 workerSaga를 실행시키란 의미이다.
import { takeEvery, put, call } from "redux-saga/effects";
import allActions from "../actions";
import api from "../api";
function* workerSaga() {
console.log("Hello, worker!");
try {
const { data } = yield call(api.getImages);
console.log(data);
yield put(allActions.imageActions.loadImagesSuccess(data));
} catch (error) {
yield put(allActions.imageActions.loadImagesFail(error));
}
}
// rootSaga
workerSaga 에선 call로 API를 호출하고 put으로 dispatch를 한다.
먼저 call 부터 보면 이 함수 인자는 Promise를 반환해야한다.
이때문에 axios.get()을 리턴하면 잘 처리가 된다.
이렇게 비동기로 받은 데이터를 put, 즉 dispatch 하는 것이다.
이제 App.js에서 뿌려주면 끝이다.
src/App.js
const images = useSelector(state => state.images);
const dispatch = useDispatch();
useEffect(() => {
dispatch(allActions.imageActions.loadImages());
}, []);
액션 생성 함수 만들때 loadImages() 의 역할이 없어 보이는데
여기서, useEffect는 컴포넌트가 마운드 될때 아래코드를 실행한다.
dispatch({ type: "LOAD_IMAGES" });
saga 미들웨어가 존재하기 떄문에 dispatch 가 rootSaga로 넘어간다.
그안에서 takeEvery() 를 만나 type이 일치하는 것이 확인되고 workerSaga로 넘어감
여기선 비동기로 로직을 실행한 다음에 put으로 loadImagesSuccess()를 dispatch한다.
마지막으로 리듀서로 넘어가 state가 업데이트가 되는것이다.
'프론트 엔드 > React' 카테고리의 다른 글
React localhost실행 변수주기 (0) | 2021.08.28 |
---|---|
React 상태관리를 위한 필수 라이브러리 - Redux (0) | 2021.08.28 |
API.1 - React에서 사용하는 많이쓰는 Http 통신라이브러리 - use-http (0) | 2021.08.26 |