FE/리액트 인터뷰 가이드
6. 리덕스: 최고의 상태 관리 솔루션 - 3
by Toddler_AD
2025. 10. 4.
리덕스 미들웨어 : 사가와 썽크
- 기본 리덕스 스토어는 오직 액션을 디스패치해서 간단한 동기 상태를 업데이트하는 것만 가능하다. 리덕스 썽크 및 리덕스 사가 같은 미들웨어는 스토어와 상호 작용하기 위한 비동기 로직을 작성해서 스토어의 기능을 확장하는 데 도움을 줄 수 있다. 이러한 미들웨어는 액션이나 액션 생성기, 컴포넌트에서 직접 부수 효과를 일으키는 것을 막는데 유용하다.
리덕스 미들웨어란 무엇인가? 어떻게 미들웨어를 만드는가?
- 리덕스 미들웨어는 서드파티 확장 기능으로, 액션을 수정하거나 취소하는 방식으로 리듀서로 전송되는 모든 액션을 가로채는 기능을 제공한다. 로깅, 오류보고, 라우팅, 비동기 API를 호출할 때 유용하다. 리덕스 미들웨어는 Node.js 미들웨어(예: Express, Koa)와 유사하지만 다른 문제를 해결한다고 볼 수 있다.
- 다음 예제에서는 loggerMiddleware라는 사용자 정의 미들웨어를 생성해서 콘솔에서 다양한 액션을 로깅하는 방법을 단계별로 설명한다.
- 먼저 리덕스 라이브러리에서 applyMiddleware를 불러와야 한다.
import { applyMiddleware } from 'redux'
- 로그를 기록하는 목적으로 액션을 가로채기 위한 loggerMiddleware라는 이름의 미들웨어를 다음과 같은 구조의 문법으로 만든다.
const loggerMiddleware = (store) => (next) => (action) => {
console.log('action', action)
return next(action)
}
- loggerMiddleware 함수가 생성된 이후에는 이를 applyMiddleware 함수로 넘겨야 한다.
const middleware = applyMiddleware(loggerMiddleware)
- 마지막으로, 이렇게 생성한 사용자 정의 미들웨어를 createStore 함수에 전달애햐 한다. 미들웨어가 스토어의 세 번재 인수로 할당돼 있지만 createStore 함수는 타입에 따라 자동으로 미들웨어를 식별한다.
const store = createStore(reducer, middleware)
- 액션이 스토어에 디스패치되기 전에, 미들웨어는 콘솔에 세부사항을 기록하기 시작한다. 미들웨어 내에서 next 함수가 호출됐으므로 리듀서도 실행되어 스토어의 상태를 업데이트 한다.
- 다음과 같이 여러 미들웨어를 applyMiddleware 함수에 전달해서 여러 개의 미들웨어를 생성하는 것도 가능하다.
const middleware = applyMiddleware(
loggerMiddleware,
firstMiddleware,
secondMiddleware,
thirdMiddleware,
)
- 이 코드에서 각 미들웨어는 이전 미들웨어가 실행된 이후에 순차적으로 실행된다.
리덕스에서 비동기 작업은 어떻게 다루는가?
- 대부분의 모던 웹 애플리케이션은 비동기 작업을 처리해야 한다. 리액트에는 이를 처리하기 위해 리덕스 썽크와 리덕스 사가라는 인기 있는 두 가지 라이브러리가 있다.
- 리덕스 썽크 미들웨어는 단순한 액션 객체 대신 함수를 반환하는 액션 생성기를 작성하는 데 사용된다. 이 액션 생성기에서 반환된 함수를 썽크 함수(thunk function)라고 하며, 연산을 지연시키는 데 사용된다. 이러한 함수는 dispatch와 getState 메서드로 총 두개의 인수를 받는다.
const thunkFunction = (dispatch, getState) => {
// The logic is usedThis is the place where you can write logic to dispatch actions or read state
// 여기에 다른 액션을 디스패치하거나 상태를 읽는 로직을 작성할 수 있다.
}
store.dispatch(thunkFunction)
- 모든 썽크 함수는 애플리케이션 코드가 아닌 스토어이 dispatch 메서드를 통해 호출된다. 이전 코드에서도 이와 동일한 동작을 볼 수 있다.
- 액션 생성기가 디스패치를 위한 액션을 생성하는 것과 마찬가지로, 썽크 액션 생성기를 사용해 썽크 함수를 생성할 수 있다. 예를 들어, 특정 사용자가 작성한 게시물 목록은 getPostsByAuthor라는 썽크 액션 생성기를 사용해 익명 썽크 함수를 생성함으로써 데이터를 가져올 수 있다.
export const getPostsByAuthor = (authorId) => async (dispatch) => {
const response = await client.get(`/api/posts/${authorId}`)
dispatch(postsLoaded(response.posts))
}
- 그 다음, UI 컴포넌트 내에서 발생하는 모든 상호 작용을 기반으로 액션 생성기에 접근할 수 있다. 다음 AuthorComponent 컴포넌트는 지연 로딩으로 불러온 모든 게시글 목록에 접근한다.
function AuthorComponent({ authorId }) {
//...
const onLazyLoading = () => {
dispatch(getPostsByAuthor(authorId))
}
}
- 마지막으로 중요한 단계는 리덕스 스토어에 redux-thunk 미들웨어를 구성해서 썽크 함수를 디스패치하는 것이다. 이때 가능한 방법은 두 가지가 있다. 스토어에 썽크 미들웨어를 수동으로 추가하기 위해 applyMiddleware() 메서드에 인수로 전달해야 한다. 그러나 RTK를 사용하는 경우에는 configureStore API가 스토어 생성 과정에 자동으로 썽크 미들웨어를 추가한다. 즉, 추가적인 설정이 필요하지 않다.
리덕스 썽크 활용 사례에는 어떤 것이 있는가?
- 리덕스 썽크는 임의의 로직을 가질 수 있으며, 다양한 용도로 사용될 수 있다. 리덕스 썽크의 가장 일반적인 활용 사례는 다음과 같다.
- 리액트 컴포넌트에 있는 복잡한 로직을 밖으로 옮기려고 할 때
- Ajax 호출 같은 비동기 요청 및 기타 비동기 로직을 수행할 때
- 연속으로 여러 가지로 구별된 액션을 디스패치해야 하는 로직을 작성할 때
- getState나 다른 상태 값에 접근해서 결정을 내려야 하는 로직을 작성할 때
- 요약하자면, 리덕스 썽크 미들웨어의 주요 활용 사례는 동기적으로 처리되지 않는 액션을 처리하기 위한 것이다.
리덕스 사가란 무엇인가?
- 리덕스 사가는 비동기 부수 효과를 처리하기 위한 리덕스 썽크의 경쟁 라이브러리다. 리덕스 사가는 ES6 기능 중 비동기 코드 작성에 도움이 되는 제너레이터(generator)를 사용한다. 이 제너레이터는 함수를 일시 중지, 재개, 실행 중단, 재진입 등을 할 수 있는 기능을 가지고 있다.
- 부수 효과는 redux-saga 패키지의 특별한 헬퍼 함수를 사용해서 생성된다. 자주 사용되는 함수는 다음과 같다.
- Call : 미들웨어에 사가 내의 다른 함수를 호출하도록 지시하는 부수 효과
- Put : 스토어에서 액션을 디스패치하는 데 사용
- Yield : 제너레이터 함수를 순차적으로 사용하도록 해주는 내장 함수
- takeLatest : 최신 데이터로 다시 실행해서 이전 작업을 취소하고 함수 핸들러를 한 번에 한 번만 호출
- takeEvery : 액션이 발생할 때마다 무한히 동시에 함수 핸들러를 호출
- 사가 함수는 디스패치된 액션을 대기하며, 코드에 작성된 부수 효과를 트리거한다. 예를 들어, 다음 postSaga 함수는 GET_POSTS 액션을 대기하고 Posts API를 호출해서 작성자의 게시물을 검색한다.
import { takeLatest, put, call } from 'redux-saga/effects'
import { GET_POSTS } from './actionTypes'
import { getPostsSuccess, getPostsFail } from './actions'
import { getPosts } from '../backend/api/posts '
function* fetchAuthorPosts() {
try {
const response = yield call(getPosts)
yield put(getPostsSuccess(response))
} catch (error) {
yield put(getPostsFail(error.response))
}
}
function* postsSaga() {
yield takeLatest(GET_POSTS, fetchAuthorPosts)
}
export default postsSaga
- 이 코드에서 성공 응답과 실패 응답 모두 스토어로 디스패치된다. 이 응답은 call 헬퍼 함수를 통해 이뤄진 API 호출에 따라 달라진다.
리덕스 사가와 리덕스 썽크 사이의 선택 기준은 무엇인가?
- 리덕스 썽크와 리덕스 사가 미들웨어 모두 리덕스 스토어가 외부 API 호출(또는 부수 효과)과 비동기적으로 상호 작용할 수 있게 도와준다. 하지만 이 둘 중 하나를 선택하는 결정은 전적으로 프로젝트 요구사항과 개인적인 선호에 좌우된다. 리액트 또는 리덕스 생태계에 익숙하지 않거나 프로젝트 규모가 작은 경우 리덕스 썽크가 좋은 선택이 될 수 있다. 또한 리덕스 썽크는 필요한 보일러플레이트 코드의 양이 비교적 적고 이해하기가 쉽다.
- 반면리덕스 사가는 여러 파일로 로직을 분할해야 하는 대규모 프로젝트에 적합하다. 그러나 리덕스 사가가 리덕스 썽크에 비해 나은 장점 중 하나는 비동기 코드에 대한 깨끗하고 읽기 쉬운 테스트 코드를 작성할 수 있다는 것이다.
- 일반적인 리덕스는 상태 관리 요구사항을 충족하기 위해 많은 보일러플레이트 코드를 필요로 한다. 개발자는 직접 스토어 설정, 리듀서 및 액션 등과 같은 일반적인 작업을 구현해야 한다. 또한 필요에 따라 다른 패키지의 API를 가져와야 할 수도 있다. 따라서 이 전체적인 과정은 개발자가 리덕스 솔루션을 배우고 구현하기 어렵게 만든다. RTK는 이 과정을 표준화하고 헬퍼 함수를 통해 간소화한다.