전 시간에는 redux-thunk라는 redux에서 비동기 작업을 할 경우에 쓰이는 미들웨어에서 정리하고, 작업했던 TodoApp에 setTimeout
으로 간단하게 진행을 했다.
redux-thunk는 정말 사용하기는 너무 간단하고, 추가적으로 배워야 할 문법 또한 없다.
하지만 기능적인 부분이 한계가 있기 때문에, 그 부분을 더 채워줄 수 있는 redux-saga에 대해서 정리해보고, TodoApp에 적용까지 해보자.
redux에서 비동기 작업을 처리 할 경우 쓰이는 미들웨어다.
thunk에 비해 더 추가적인 기능이 있다.
첫번째
npm i redux-saga
store 설정
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers/index';
import rootSaga from './saga/index';
const sagaMiddleware = createSagaMiddleware();
const middleware = [logger, sagaMiddleware];
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(...middleware)),
);
// 루트 사가 실행
// store가 생성 된 다음에 코드를 실행해야 한다.
sagaMiddleware.run(rootSaga);
export default store;
export const ADD_LIST_REQUEST = 'ADD_LIST_REQUEST';
export const ADD_LIST_SUCCESS = 'ADD_LIST_SUCCESS';
export const ADD_LIST_FAILURE = 'ADD_LIST_FAILURE';
// 나머지 ADD_LIST_SUCCESS, ADD_LIST_FAILURE는
// saga에서 호출해주기 때문에 요청만 action을 생성한다.
export const addTodoRequest = (payload) => {
return {
type: ADD_LIST_REQUEST,
payload,
};
};
import { takeLatest, put, delay, fork } from 'redux-saga/effects';
import {
ADD_LIST_REQUEST,
ADD_LIST_SUCCESS,
ADD_LIST_FAILURE,
} from '../actions';
// action - 아래에서 전달받은 action에 대한 정보를 가지고 있다.
function* addList(action) {
try {
yield delay(2000); // 비동기 흉내를 위한 시간 지연 이펙트
yield put({
type: ADD_LIST_SUCCESS,
payload: action.payload,
});
} catch (err) { // 테스트여서 실제 에러날 일은 없다..
yield put({
type: ADD_LIST_FAILURE,
data: err,
});
}
}
// 두번째 인자 들어간 addList에는 함수를 호출하며, action에 대한 정보를 보낸다.
function* watchAddList() {
yield takeLatest(ADD_LIST_REQUEST, addList);
}
export default function* rootSaga() {
yield fork(watchAddList);
}
take
일회용 이펙트 (한번 실행 후 지워버려서 실행할 수 없다.)
all
여러 이펙트를 동시에 실행할 경우
put
dispatch와 동일하게 사용
delay
시간 지연 이펙트
takeEvery
일회용이 아닌 반복문처럼 계속 사용할 수 있다.
takeLatest
요청은 전부 가지만, 마지막 호출에 대해서만 응답한다.
takeLeading
맨 처음 호출만 실행한다.
throttle
정해진 시간내에 마지막만 요청을 보낸다.
더 많지만, 이 정도만 정리를 하도록 하겠다.
사실 이렇게 적어도 다 쓰진 않는 것 같다. 필요할 때 찾아보고 사용해보자!
import React, { useState, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import shortId from 'shortid';
import { AddInput, AddContainer, AddButton, Loading } from '../style';
import { addTodoRequest } from '../actions/index';
const AddList = () => {
// loading 기능을 추가해보고자 불러온다.
const loading = useSelector((state) => state.loading);
const [value, setValue] = useState('');
const dispatch = useDispatch();
console.log(loading);
const onChangeInput = useCallback(
(e) => {
setValue(e.target.value);
},
[value],
);
const onAddList = useCallback(() => {
if (value === '') {
return alert('내용을 입력해주세요.');
}
dispatch(
addTodoRequest({ // 변경된 요청 부분
id: shortId.generate(),
title: value,
done: false,
}),
);
setValue('');
}, [value]);
return (
<AddContainer>
{loading ? (
<Loading>
<p>리스트 추가 로딩 중...</p>
</Loading>
) : null}
<AddInput type="text" value={value} onChange={onChangeInput} />
<AddButton onClick={onAddList}>+</AddButton>
</AddContainer>
);
};
export default AddList;
그렇다면 실행!!
좀더 해상도가 좋은 GIF 캡쳐 앱이 있을까... 유료를 써야하나...
실제 API로 요청을 보내고, 받게되면 saga의 현재 패턴에서 문장이 더 추가됀다.
물론 API로 보내는 문장도 추가하게 되고, 응답을 받는 부분도 추가로 try catch문에도 넣어줘야한다.
복잡하게 느껴질 수 있겠지만 saga의 패턴을 사용하다보면 복사 붙혀넣기로 진행이 될 정도로 단순하다.
어떻게 요청을 보내고, 어떻게 데이터를 받아서 UI를 만들 것 인지 이제는 심화과정을 배워보고, 다른 프로젝트로 넘어갈 필요도 있을 것 같다.