Redux-thunk, Redux-saga 개념 정리
이번달 초에 좋은 기회로 카카오 현직 개발자에게 커리어코칭을 받았는데, 당시에 내 포트폴리오를 첨삭받으며 Redux-thunk와 Redux-saga에 대해 알게 됐었다. 이번에 개인 프로젝트를 진행해보며 추가적인 공부가 필요한 기술들을 발견해서, 본격적으로 학습하기 전에 기본적인 개념부터 여기에 정리해두려고 한다.
일단 Redux-thunk, Redux-saga 둘 다 Redux에서 비동기 작업을 처리하는데 도움을 주는 미들웨어인데, 작업 처리 방식이 약간 다르다.
Redux-thunk
가장 간단하고 기본적인 Redux 미들웨어로, "thunk"는 일반적으로 함수가 함수로서 사용되는 패턴을 의미한다.
액션 생성자가 함수가 될 수 있도록 해준다. 이 함수는 dispatch를 호출하여 액션을 보낼 수 있고, 비동기 작업을 처리한 후에 액션을 다시 dispatch할 수 있다. (dispatch와 액션 사이에서 중계 역할을 하고 있어서 '프록시 서버'와 비슷하다고 생각했다.)
chatGPT가 작성해준 사용 예시를 보자면,
// 액션 타입 정의
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST';
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';
// 액션 생성자
const fetchDataRequest = () => ({ type: FETCH_DATA_REQUEST });
const fetchDataSuccess = (data) => ({ type: FETCH_DATA_SUCCESS, payload: data });
const fetchDataFailure = (error) => ({ type: FETCH_DATA_FAILURE, payload: error });
// 비동기 액션 생성자
const fetchData = () => {
return async (dispatch) => {
dispatch(fetchDataRequest());
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
dispatch(fetchDataSuccess(data));
} catch (error) {
dispatch(fetchDataFailure(error));
}
};
};
여기서 fetchData()는 비동기 작업을 처리하고, try~catch로 결과에 따라 다른 액션을 dispatch한다.
Redux-thunk는 이러한 함수형 액션 생성자를 지원한다. 간단하고 직관적이고, 설정이 쉬운 편이다.
Redux-saga
복잡한 비동기 로직을 더 잘 관리할 수 있는 Redux 미들웨어.
saga는 제너레이터 함수를 사용하여 비동기 작업을 처리한다. 제너레이터 함수는 yield 키워드로 비동기 작업을 중단하고, 결과를 처리한 후에 다시 시작할 수 있게 한다.
역시 chatGPT가 작성해준 예시 코드를 살펴보자면,
import { call, put, takeEvery } from 'redux-saga/effects';
// 액션 타입 정의
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST';
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';
// 액션 생성자
const fetchDataRequest = () => ({ type: FETCH_DATA_REQUEST });
const fetchDataSuccess = (data) => ({ type: FETCH_DATA_SUCCESS, payload: data });
const fetchDataFailure = (error) => ({ type: FETCH_DATA_FAILURE, payload: error });
// 비동기 함수
const fetchDataFromApi = () => fetch('https://api.example.com/data').then(response => response.json());
// saga 함수
function* fetchDataSaga() {
try {
const data = yield call(fetchDataFromApi);
yield put(fetchDataSuccess(data));
} catch (error) {
yield put(fetchDataFailure(error));
}
}
// root saga
function* rootSaga() {
yield takeEvery(FETCH_DATA_REQUEST, fetchDataSaga);
}
// saga 미들웨어 설정
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
call과 put을 사용하여 비동기 함수를 호출하고, 액션을 dispatch한다. takeEvery는 특정 액션이 발생할 때마다 saga를 실행하도록 한다. 비동기 로직을 더 세밀하게 제어할 수 있기 때문에 복잡한 비동기 로직에 적합하다.
💡 여기서 잠깐!
thunk와 saga는 기본적으로 서로 호환이 가능하지만, thunk는 간단한 로직을 처리하는데 적합하고, saga는 복잡한 로직이나 다양한 작업의 조합이 필요한 경우에 더 적합하다.
매우 복잡한 비동기 흐름도 thunk로 구현할 수는 있지만 코드가 복잡하고 관리하기 어려울 수 있다. 이런 경우에 saga로 작성하면, 제너레이터 함수를 사용해 비동기 로직을 명령형 스타일로 작성할 수 있다. 여러 비동기 작업을 병렬로 실행하거나, 특정 조건에 따라 비동기 작업을 제어하는 복잡한 로직을 처리하기에 좋다.
기본적인 개념을 습득하고 예시 코드를 보니까 thunk가 더 쉬워서 며칠만 연구하면 이해하기 어렵지 않을 것 같다. 그리고 npm trend에서 thunk와 saga의 사용률을 비교했더니 thunk 사용률이 압도적으로 높다.
thunk와 saga 모두 알아둬야할 것 같지만, 일단 상대적으로 쉬운 thunk부터 익혀봐야겠다!
https://npmtrends.com/redux-saga-vs-redux-thunk
