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/
따봉👍🏻👍🏻👍🏻👍🏻👍🏻