Thunk는 프로그래밍에서 작업을 지연시키는 함수이다. 로직을 바로 실행시키지 않고 나중에 실행시킨다.
Thunk는 리덕스 앱에서 비동기 로직을 작성하는 표준 접근 방식이다.
Redux의 Reducer는 사이드 이펙트를 포함해선 안된다. 하지만 실제 어플리케이션에서 사이드이펙트를 발생시키는 로직을 필요로한다. Thunk는 이러한 사이드이펙트를 담아두는 역할을 한다. 예를들면 async 요청같은 경우가 있다.
Redux의 모든과정은 동기적으로 발생한다. 또한 Redux의 Reducer에는 비동기 로직이 존재할 수 없기 때문에 미들웨어를 사용한다. 아래는 Redux 미들웨어 동작원리에 대한 그림이다.
대표적으로 redux-thunk, redux-saga가 존재한다.
npm install redux-thunk
import { createAsyncThunk, createSlice } 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 res = await axios.get('http://localhost:4000/todos');
console.log(res.data)
return thunkAPI.fulfillWithValue(res.data);
} catch (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) => {
// 성공시
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 const {} = todosSlice.actions;
export default todosSlice.reducer;
export const __getTodos = createAsyncThunk(
'getTodos',
async (payload, thunkAPI) => {
try {
const res = await axios.get('http://localhost:4000/todos');
console.log(res.data)
return thunkAPI.fulfillWithValue(res.data);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
createAsyncThunk는 결과에 상관없이 무조건 이행된 프로미스를 반환한다.
따라서 오류처리는 별도로 해주어야 한다.
컴포넌트 내에서 하고 싶다면 리턴할 때에 thunkAPI에 존재하던 fulfillWithValue나 rejectWithValue의 호출결과를 리턴해주면 컴포넌트에서 해당 액션 객체의 메타데이터를 사용하는 것이 가능하다.
export const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {},
extraReducers: {
[__getTodos.pending]: (state, action) => {
// 진행중
state.isLoading = true;
state.isError = false;
},
[__getTodos.fulfilled]: (state, 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
},
},
});
reducers 는 자동으로 action creator를 생성하지만, extraReducers 는 action creator를 생성하지못한다.extraReducers 프로퍼티를 사용하는 경우는 비동기를위해 createAsyncThunk를 사용하여 정의된 액션함수를 사용하거나, 다른 slice에서 정의된 액션함수를 사용하는 경우이다.
//App.jsx
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import './App.css';
import { __getTodos } from './redux/modules/todoSlice';
function App() {
const dispatch = useDispatch();
const { todos, isLoading, isError } = useSelector((state) => state.todos);
useEffect(() => {
dispatch(__getTodos());
}, []);
if (isLoading) {
<div>로딩중입니다....</div>;
}
return (
<div> {
todos.map((item) => {
return <div key={item.id}>
{item.id} : {item.title}
</div>
})}
</div>
);
}
export default App;
useDispatch와 useSelector는 똑같이 사용하면 된다.
Reference : https://ko.redux.js.org/usage/writing-logic-thunks/
따봉👍🏻👍🏻👍🏻👍🏻👍🏻