
리덕스에서 dispatch를 하면 action이 리듀서로 전달이 되고, 리듀서는 새로운 state를 반환한다.
근데 미들웨어를 사용하면 이 과정 사이에 우리가 하고 싶은 작업들을 넣어서 할 수 있다.
만약 counter 프로그램에서 더하기 버튼을 클릭했을 때 바로 +1를 더하지 않고 3초를 기다렸다가, +1이 되도록 구현하려면 미들웨어를 사용하지 않고서는 구현할 수 없다.
왜냐하면 dispatch가 되자마자 바로 action이 리듀서로 달려가서 새로운 state를 반환해버리기 때문이죠. 즉 여기서 “3초를 기다리는 작업" 이 작업을 미들웨어가 해주는 것
서버와의 통신을 위해서 사용하는 것이 대부분
ex) redux-saga, react-query
리덕스에서 많이 사용하고 있는 미들웨어중에 하나
thunk를 사용하면 우리가 dispatch를 할때 객체가 아닌 함수를 dispatch 할 수 있게 해준다.
그래서 중간에 우리가 하고자 하는 작업을 함수를 통해 넣을 수 있고, 그것이 중간에 실행이 되는 것
그리고 이 함수를 thunk 함수라고 부른다.
dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)
thunk 함수의 역할은 “3초를 기다리는 것”
그리고 3초가 지나면 원래 하려고 했던 ADD_NUMBER를 해주는 것 까지가 thunk함수가 해야 할 일이다.
툴킷에서는 createAsyncThunk 라는 API 함수를 사용해서 thunk 함수를 생성할 수 있다.
첫번째 인자에는 Action Value, 두번째 인자에는 함수가 들어간다. 이 함수에 우리가 하고 싶은 작업들을 구현하면 된다.
두번째 들어가는 함수에서도 인자를 꺼낼 수 있는데, 첫번째 인자(arg)는 이 thunk함수가 외부에서 사용되었을 때 넣은 값을 여기에서 조회할 수 있고, 두번째 인자에서는 thnuk가 제공하는 여러가지 API 기능들이 담긴 객체를 꺼낼 수 있다.
// thunk 함수는 createAsyncThunk 라는 툴킷 API를 사용해서 생성합니다.
// __가 함수 이름에 붙는 이유는 이 함수가 thunk 함수라는 것을 표시하기 위한
// 개인의 convention 입니다. 함수의 이름은 본인이 편한 이름으로 명명하세요.
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(arg, thunkAPI)=>{},
);
우리가 원래 하려고 했던 3초를 기다리는 thunk 함수를 만들어볼게요. 아래 코드를 참고해주세요. 첫번째 자리에는 action value를 넣었습니다. 그리고 두번째에는 함수를 넣었어요.
함수안에는 setTimeout 라는 Web API를 이용해서 3초를 기다리게 했고, 이후에 thunkAPI 안에 있는 dispatch를 통해서 우리가 원래 하려고 했던 addNumber라는 action creator를 넣었습니다.
[22.8.6 업데이트] - 코드 수정 : 생략했던 모듈 코드 추가, arg를 payload로 변경
// src/redux/modules/counterSlice.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
// thunk함수는 createAsyncThunk 툴킷api를 사용해 생성한다.
export const __addNumber = createAsyncThunk(
// 첫번째 인자 : action value
"ADD_NUMBER_WAIT",
// 두번째 인자 : 콜백함수
(payload, thunkAPI)=>{
// 수행하고싶은 동작: 3초 기다리게 할 예정
setTimeout(()=> {
// 컴포넌트로부터 받은 payload를 넣어준다.
thunkAPI.dispatch(addNumber(payload)); // action creator를 넣어준다.
},3000);
}
);
export const __minusNumber = createAsyncThunk(
"MINUS_NUMBER_WAIT",
(payload, thunkAPI)=>{
// 수행하고싶은 동작: 3초 기다리게 할 예정
setTimeout(()=> {
thunkAPI.dispatch(minusNumber(payload));
},3000);
}
);
// 초기값
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: 'counter', // 모듈 이름
initialState, // 모듈의 초기 상태값
reducers: { // 모듈의 reducer 로직
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
export const {addNumber, minusNumber} = counterSlice.actions;
export default counterSlice.reducer;
arg는 컴포넌트에서 사용자가 input에 입력한 더하고자 하는 값입니다. 예전에 우리가 구현했던 것인데요, 이런식으로 payload 값을 받아올 수 있습니다. 이 코드가 어떻게 동작하는지 한번 확인해볼까요?
컴포넌트에서의 코드는 아래와 같습니다. 기존에는 addNumber 라는 action creator를 dispatch했다면, 이제는 __addNumber 라는 thunk함수를 dispatch 해줍니다.
App.jsx
파일이름 수정 counter → counterSlice
// src/App.jsx
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { __addNumber, __minusNumber, addNumber, minusNumber } from './redux/modules/counterSlice';
function App() {
const globalNumber = useSelector((state) => state.counter.number);
const [number, setNumber] = useState(0);
const dispatch = useDispatch();
const onChangeHandler = (e) => {
const { value } = e.target;
setNumber(+value);
}
const onPlusButtonClickHandler = () => {
// dispatch(addNumber(1));
// thunk함수를 디스패치한다. payload는 thunk함수에 넣어주면 리덕스 모듈에서 payload로 받을 수 있다.
dispatch(__addNumber(number));
};
const onMinusButtonClickHandler = () => {
// dispatch(minusNumber(1));
dispatch(__minusNumber(number));
};
return (
<div>
<h1>{globalNumber}</h1>
<input type="number" onChange={onChangeHandler} />
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
</div>
);
}
export default App;
createAsyncThunk 를 이용한다.createAsyncThunk() 의 첫번째 자리에는 action value, 두번째에는 함수가 들어간다.