만족
[React] Redux-Thunk 를 알아보자 본문
[React] Redux-Thunk 를 알아보자
FrontEnd/React Satisfaction 2022. 3. 4. 13:23redux-thunk에 대해 redux 문서에서는 아래와 같이 설명하고 있다.
For Redux specifically, "thunks" are a pattern of writing functions with logic inside that can interact with a Redux store's dispatch and getState methods.
Redux에서 thunks는 리덕스 스토어의 dispatch, getState와 상호작용하는 내부 로직을 포함한 함수를 작성하는 패턴입니다.
다시 말하면, 함수 내부 로직에서 별도로 redux store와 상호작용하는 로직을 구현하기 위해 사용하는 패턴이라는 뜻이다.
특히 비동기로 스토어와 상호작용할 때 유용하다.
https://github.com/reduxjs/redux-thunk/blob/master/src/index.ts
실제 코드를 보면 50줄 가량밖에 안되는 아주 간단한 모듈이다.
코드 뜯어보기
내용은 middleware를 만들고 리턴하는게 전부다.
createThunkMiddleware 내부를 보면, 미들웨어를 제공함으로써 action을 함수 타입으로도 다룰 수 있게 해준다.
위와 같이 기본적으로 액션은 object 타입이지만,
redux-thunk를 사용하면 function 타입으로도 사용할 수 있다.
redux-thunk 사용해보기
redux-toolkit을 사용한다면 별도 설치나 미들웨어를 추가할 필요 없이 바로 사용할 수 있다.
이전 포스트에서 사용했던 카운터에서 thunk를 추가해보자.
https://satisfactoryplace.tistory.com/344
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
//
//added
//
export const decrementAsync = () => {
return (dispatch, getState) => {
setTimeout(() => {
dispatch(counterSlice.actions.decrement());
}, 1000);
};
};
맨 아래에 decrementAsync를 추가했다.
dispatch를 호출할 때 매개변수로 decrementAsync()를 전달하면,
액션으로 함수인 (dispatch, getState)=> {...} 가 전달된다.
decrementAsync()가 디스패치되면 middleware를 거치게 되는데,
redux-thunk가 추가한 미들웨어는 액션이 function이면 그 function을 호출하도록 되어 있어서
decrementAsync()가 리턴한 함수가 호출되게 된다.
결론적으로 액션에 함수 타입을 사용하여 비동기적으로 리덕스 스토어를 업데이트할 수 있게 되었다.
// /component/Counter
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { decrementAsync, increment } from "../redux/counter";
const Counter = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
const handleIncrement = () => dispatch(increment());
const handleDecrement = () => dispatch(decrementAsync());
return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>increment</button>
<button onClick={handleDecrement}>decrement</button>
</div>
);
};
export default Counter;
handleDecrement에서 decrementAsync를 사용하도록 변경해봤다.
실제로 1초 뒤 decrement액션이 디스패치되는 모습을 확인할 수 있었다.
+ 왜 직접 추가한 미들웨어로 함수타입 액션은 전달되지 않는가?
// /store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./redux/counter";
const loggerMiddleware = (store) => (next) => (action) => {
console.group(action?.type);
console.info("action", action);
console.log("before state", store.getState());
//액션을 리듀서에 전달
let result = next(action);
//next(action)후 state는 정의된 동작에 따라 변한다
console.log("next state", store.getState());
console.groupEnd();
return result;
};
//redux store
export const store = configureStore({
reducer: {
counter: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(loggerMiddleware),
});
분명 loggerMiddleware에서 action, state를 출력하게 되어 있는데,
함수타입 액션을 디스패치한 내용은 콘솔에 찍히지 않는다.
왜 그럴까?
redux-thunk의 코드를 보면 알 수 있다.
redux-toolkit의 createSlice를 이용한 경우,
thunkMiddleware가 기본적으로 추가된다.
그런데 createThunkMiddleware를 보면 함수 타입이 function인 경우(28번 라인)
return action(dispatch, getState, extraArgument)로 로직이 종료된다.
따라서 다음 미들웨어로 넘어가지 않고, 흐름이 종료되기 때문에 별도로 추가한 미들웨어로 진행되지 않는 것이다.
'FrontEnd > React' 카테고리의 다른 글
[React] 검색엔진 최적화(SEO):: Prerendering (react-hydratable) (6) | 2022.03.08 |
---|---|
[React] path alias 사용을 위한 webpack 설정하기 (0) | 2022.03.06 |
[React] redux-toolkit 을 알아보자 (0) | 2022.02.25 |
[React] 애드블록(Adblock) 탐지 hooks 만들기 (0) | 2022.01.29 |
[React] Scss 사용하기 (0) | 2021.12.26 |