리덕스에 없던 기능들을 추가해주는 역할이다
기존의 컴포넌트 안에서 했던 비동기 처리를 리덕스에서 하도록 해서 사이드 이펙트를 최소화한다.
풀어서 얘기하자면 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업을 실행할 수 있게 해준다.
액션과 리듀서사이의 중간자 역할이다.
그 중 redux-thunk 와 redux-saga를 간단히 설명해보려 한다.
리덕스가 비동기 작업을 처리할 때 가장 많이 사용하는 미들웨어로 액션 객체가 아닌 함수를 디스패치할 수 있다. 함수를 디스패치할 때는, 해당 함수에서 dispatch와 getState를 파라미터로 받아와야하고, 이 함수를 만들어주는 함수를 thunk라고 부른다. 따라서 리덕스 스토어의 상태에 접근하거나 또 다른 액션을 디스패치하는 것이 가능하다.
thunk 미들웨어의 문제점은 action에서 너무 많은 일을 한다는 점이다. 액션 생성자는 type과 payload가 담긴 객체를 생성해서 반환하는 역할을 수행하기로 했는데, thunk 미들웨어에서는 API 요청이나 비동기 처리를 함으로써 본래의 역할이 모호해진다. 어떤 경우는 객체를 반환하고, 또 어떤 경우는 함수를 반환한다. 또한 saga에 비해 기능이 제한적이다.
saga는 thunk보다 다양한 기능들을 제공한다.
제너레이터 함수로 rootSaga를 하나 만들고 그 안에 넣고싶은 비동기 액션들을 넣는다. redux-saga의 effect는 all,fork.call,put 등이 있다. 이 effects들은 미들웨어에서 활용할 수 있는 정보들을 담고 있는 자바스크립트 객체의 일종이다. 이 effect들을 효율적으로 활용하여 saga를 효과적으로 사용할 수 있다 ! 아래는 대표적인 effects 들이다.
요청이 항상 성공하는 것은 아니기 때문에 try catch문을 잊지 말도록 하자 !
성공 결과는 (result).data, 실패 결과는 (err).response.data에 담겨있다.
제너레이터 함수 안에 yield로 함수실행의 중단점을 만들어 낼 수 있다.
yield는 await과 같은 역할?인듯하다.
const gen = function* () {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
yield 4;
}
const generator = gen();
generator
// gen {<suspended>}
generator.next()
// 1
// {value: undefined, done: false}
generator.next()
// 2
// {value: undefined, done: false}
generator.next()
// 3
// {value: 4, done: false}
generator.next()
// {value: undefined, done: true}
동시에 실행할 수 있도록 해줌
이벤트 리스너처럼 기다리는 effect
function* watchLogIn(){
yield take("LOG_IN_REQUEST", logIn);
}
이렇게 코드를 쓴다면 로그인은 최초 한번만 성공하고 재로그인은 되지 않는다. 어떻게 해결해야 할까 ?
function* watchLogIn(){
while (true) {
yield take("LOG_IN_REQUEST", logIn);
}
}
function* watchLogIn(){
yield takeEvery("LOG_IN_REQUEST", logIn);
}
둘의 차이는 while take는 동기적, takeEvery는 비동기로 동작이라는 점이다. 보통 takeEvery를 애용한다 !
로그인 버튼을 여러번 클릭하면 thunk에서는 요청이 누르는대로 여러번 간다. 하지만 saga의 takeLatest는 중복클릭(클릭 후 로딩중인 상황))을 인식했을 때에 가장 마지막 요청만을 보낸다.
액션을 디스패치할 때 사용
비동기 함수 호출, (결과값을 받고 말고 상관없이 다음줄로 넘어간다.)
동기 함수 호출, (결과값을 받을때까지 기다렸다가 다음줄로 넘어간다.)
function loginAPI() {
return axios.post('/api/login')
}
function* login(){
try {
const result = yield call(loginAPI)
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
} catch (error) {
yield put({
type: 'LOG_IN_FAILURE',
data: error.response.data
})
}
}
로그인에 필요한 saga 코드이다. call은 동기 함수 이기때문에 로그인api가 return할 때까지 기다려서 result에 값을 넣어준다.