Redux Thunk는 Redux 팀에서 만든 미들웨어이다. 따라서 미들웨어에 대해 먼저 간단하게 알아보자.
미들웨어가 없는 Redux는 다음과 같은 과정을 반복한다.
여기까지가 Redux가 하는 일반적인 일이다. 하지만 문제점이 하나 있다. reducer는 순수 함수 이어야 한다.
즉, 같은 Input에 대해서는 언제나 동일한 Output이 나와야 하고, 그렇기 때문에 reducer안에서는 네트워크 통신이나 랜덤 값 생성 등이 일어나서는 안된다.
이러한 점을 보완하기 위해 있는 것이 미들웨어다. 미들웨어는 reducer 이전에 위치하며, 미들웨어에서도 dispatch를 할 수 있다.
미들웨어를 사용하면 action이 dispatch 된 다음, reducer에서 해당 action을 받아와서 업데이트 하기 전에 추가적인 작업을 할 수 있다.
추가적인 작업이란 다음과 같다.
보통 리덕스에서 미들웨어를 사용하는 주된 사용 용도는 비동기 작업을 처리 할 때이다. 예를 들어 리액트 앱에서 우리가 백엔드 API를 연동해야 된다면, 리덕스 미들웨어를 사용하여 처리한다.
여러가지 미들웨어가 존재하지만, Redux Thunk는 비동기 작업을 처리할 수 있는 대표적인 미들웨어다.
우선 Thunk라는 용어가 뭔지 알아보자. 위키백과에서는 다음과 같이 설명하고 있다.
컴퓨터 프로그래밍에서 썽크(Thunk)는 기존의 서브루틴에 추가적인 연산을 삽입할 때 사용되는 서브루틴이다. 썽크는 주로 연산 결과가 필요할 때까지 연산을 지연시키는 용도로 사용되거나, 기존의 다른 서브루틴들의 시작과 끝 부분에 연산을 추가시키는 용도로 사용되는데, 컴파일러 코드 생성시와 모듈화 프로그래밍 방법론 등에서는 좀 더 다양한 형태로 활용되기도 한다.
위에서 설명한 용어에 빗대어서 말하자면 Redux Thunk도 어떠한 동작을 지연시켜주는 녀석 이라고 보면 되겠다.
Redux에서 제공하는 createAsyncThunk
라는 함수로 쉽게 비동기 작업을 하는 Thunk를 만들 수 있다.
todos.js
export const __findAllTodos = createAsyncThunk("todos/findAll", async (payload, thunkAPI) => {
try {
const {data} = await instance.get(`todos`);
// Promise -> resolve (네트워크 응답이 성공한 경우, dispatch 해주는 기능을 가진 API)
return thunkAPI.fulfillWithValue(data);
} catch (error) {
console.error(error)
return thunkAPI.rejectWithValue(error);
}
});
const todosSlice = createSlice({
name: 'counter',
initialState,
reducers: {
},
extraReducers: {
[__findAllTodos.pending]: (state, action) => {
state.isLoading = true;
state.isError = false;
}, [__findAllTodos.fulfilled]: (state, action) => {
console.log(state, action)
state.isLoading = false;
state.isError = false;
state.todos = action.payload;
}, [__findAllTodos.rejected]: (state, action) => {
state.isLoading = false;
state.isError = true;
}
}
})
App.jsx
useEffect(() => {
// thunk를 dispatch한다!
dispatch(__findAllTodos());
}, [dispatch]);
thunk 안에서 다른 thunk를 dispatch할 수도, reducer를 dispatch할 수도 있다.
export const __addTodo = createAsyncThunk("todos/add", async (payload, thunkAPI) => {
try {
const {data} = await instance.post(`todos`, payload);
thunkAPI.dispatch(__findAllTodos());
thunkAPI.dispatch(todosSlice.actions.log());
return thunkAPI.fulfillWithValue(data)
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
})
Redux Toolkit을 설치한 경우 RTK Query라는 기능을 이용할 수 있다. 안에 createAsyncThunk
가 내장되어 있어 createAsyncThunk
를 사용하는 것은 같지만, 위에서 작성한 내용들을 더 쉽게 작성할 수 있게 도와준다.
createApi
메서드를 사용해 아래 내용들을 채워준다.export const todosApi = createApi({
reducerPath: 'todosApi',
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_SEVER_URL // http:localhost:4000
}),
endpoints: (build) => ({
findAllTodos: build.query({
query: (arg) => 'todos' // http:localhost:4000/todos
})
}),
})
// endpoint에 적은 메서드의 이름을 따른 hook이 자동 생성된다.
// use[endpoint 이름]Query
export const { useFindAllTodosQuery } = todosApi
configureStore
설정const store = configureStore({
reducer: {
[todosApi.reducerPath]: todosApi.reducer
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(todosApi.middleware)
})
const {data, error, isLoading} = useFindAllTodosQuery();
useEffect(() => {
console.log('todos is loading... ', isLoading)
if (!isLoading) {
console.log('로딩 완료!')
console.log('데이터는...', data)
}
}, [isLoading]);
RTK가 자동으로 만들어준 hook을 사용하면 isLoading
여부와 error
그리고 data
를 반환해준다.
이를 useEffect
와 함께 사용하면 쉽게 데이터를 불러올 수 있다.
오우 미들웨어 완벽한 설명 이해 쏙쏙됩니다!