기존 리덕스에서 액션 객체를 dispatch하면 액션이 리듀서에게 전달되고 전달된 값을 통해 스토어에 새로운 state를 반환한다.
액션 객체를 바로 리듀서에 전달되기 때문에 중간과정에서 우리가 하고싶은 작업을 할 수 없다.
Redux 미들웨어를 사용하면 이 과정 사이에 우리가 원하는 작업들을 넣을 수 있다.
예를 들어, counter 프로그램에서 특정 숫자를 더할 때 바로 값이 업데이트되는 것이 아니라 몇초 기다리는 작업을 미들웨어에서 수행할 수 있다.
➡️ 서버와의 통신할 때 주로 이용되며 대표적인 리덕스 미들웨어는 Redux-thunk가 있다.
dispatch(함수) 👉 함수 실행 👉 함수 안에서 dispatch(객체)
thunk를 사용하면 dispatch 시 액션 객체가 아닌 함수를 dispatch할 수 있게 된다.
➡️ 즉, 함수를 통해 원하는 동작을 실행할 수 있다.
- thunk 함수 만들기 (createAsyncThunk)
- extraThunk에 thunk 등록
- dispatch
counter.js
import { createSlice } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';
const initialState = {
number: 0
};
//thunk 함수 생성
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT", //이름
(payload, thunkAPI) => { //함수
//수행할 작업 작성
setTimeout(() => {
//액션 creator 전달
thunkAPI.dispatch(addNumber(payload));
}, 5000);
}
);
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
}
}
});
//리듀서를 바깥으로 내보냄
export default counterSlice.reducer;
export const { addNumber, minusNumber } = counterSlice.actions;
요약
- thunk 함수 구현
- 리듀서 로직 구현 (extraReducers)
👉 isLoading, isError, data를 통해 제어- 기능 확인(네트워크 탭) + devtools
- Store의 값을 조회 + 화면에 렌더링
todosSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
//로딩, 에러 여부 데이터도 관리
const initialState = {
todos: [],
isLoading: false,
isError: false,
error: null,
};
export const __getTodos = createAsyncThunk(
'getTodos',
async (payload, thunkAPI) => {
try {
const response = await axios.get('http://localhost:4000/todos');
console.log('response', response);
//네트워크 요청 성공한 경우 dispatch 해주는 기능을 가진 API
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log('error', error);
return thunkAPI.rejectWithValue(error);
}
}
);
export const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {},
extraReducers: {
//진행 중인 경우
[__getTodos.pending]: (state, action) => {
state.isLoading = true;
state.isError = false;
},
//성공한 경우
[__getTodos.fulfilled]: (state, action) => {
console.log('fullfilled : ', action);
state.isLoading = false;
state.isError = false;
state.todos = action.payload;
},
//실패한 경우
[__getTodos.rejected]: (state, action) => {
state.isLoading = false;
state.isError = true;
state.error = action.payload;
}
}
});
export default todosSlice.reducer;
response.data
), 에러(error
)를 인자로 갖는다. 반드시 리턴을 해줘야한다! 👉 성공/실패/진행 여부를 판단할 때 , extraReducers에 작성한다. 로딩/에러 여부 값을 지정한다.
APP 컴포넌트에서 store에 접근한 todos를 통해
isLoading,error 데이터 값도 접근할 수 있다.
export default function App() {
const dispatch = useDispatch();
//로딩 중, 에러 여부 데이터도 접근 가능
const { isLoading, error, todos } = useSelector((state) => state.todos);
useEffect(() => {
dispatch(__getTodos());
}, [])
if (isLoading) return <p>Loading...</p>;
if (error) return <p>{error}</p>
return (
<>
<h1>
Thunk로 실습하기
</h1>
<ul>
{todos.map(todo => {
return (<li key={todo.id}>
{todo.id} : {todo.todo}
</li>)
})}
</ul>
</>
);
}
로딩 중일 경우, 로딩 중이라고 표시되며 아래 코드는 실행되지 않는다. 만약 요청이 성공되면 todos 데이터를 화면에 보여준다.
➡️ 옵셔널 체이닝 없이 isLoading,error가 있는지 판단한 다음 모두 false인 경우에만 todos 데이터를 보여줄 수 있다.