MiddleWare
MiddleWare는 Redux의 핵심적인 기능으로 액션과 Reducer 프로세스 사이에 위치하며, 액션이 dispatch 된 후 MiddleWare를 통해 비동기 작업을 처리하고 이 후 Reducer로 작업 결과를 보내준다. 쉽게, 본래 dispatch는 액션만을 받을 수 있지만, MiddleWare를 사용하면 함수를 입력받을 수 있다.
기본 형태
import { createStore, applyMiddleware } from 'redux';
const middleWare = (store) => (next) => (action) => {
//다음 MiddleWare로 액션을 전달해준다.
//만약 다음 MiddleWare가 없다면 Reducer로 액션을 전달한다.
//next()를 사용하지 않는다면 Reducer를 동작하지 않는다.
let res = next(action);
return res; //dispatch(action)의 결과 값으로 전달된다.
};
let store = createStore(rootReducer, applyMiddleware(middleWare));
일반적으로 Thunk, Saga와 같은 MiddleWare 라이브러리를 사용하며 사용하게 될 MiddleWare의 구조는 다음과 같은 형태이다. Dispatch가 발생할 때 MiddleWare가 동작하며 이를 통해 본래 Redux에서 불가능한 Deispatch에서의 비동기 작업이 여기에서 이루어 진다.
Redux Thunk
$ yarn add redux-thunk
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
export const store = createStore(
combineReducers({
counterReducer,
userReducer,
}),
applyMiddleware(thunk)
);
export type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Thunk를 설치하고 store에 applyMiddleware 함수의 매개변수로 thunk를 삽입해준다.
위와 같이 TypeScript에서 type을 명시한 useSelector를 사용하면 reducer들의 타입을 추론해준다.
export const fetchLogin
= (userID : string, userPW :string)
= async (dispatch : Dispatch<AccountDispatchType>) => {
const userStateFromServer = await ... //비동기 처리
dispatch(login(userStateFromServer));
};
그리고 다음과 같이 fetch라는 접두어를 가진 비동기 Action 함수를 작성하고 action을 return 해준다.
여기서 dispatch 액션에 함수가 삽입된 부분과 dispatch의 타입에서 AccountDisaptchType를 주목해보자.
일반적으로, "Login"의 형태로 액션의 타입을 명시하지만 효율 증대와 실수를 방지하기 위해 TypeScript에서는 다음과 같이 타입을 정확히 해준다.
//타입
const LOGOUT = "user/Logout" as const;
const LOGIN = "user/Login" as const;
const ADD_FAVORITE_ITEM = "user/AddFavoriteITEM" as const;
const DEL_FAVORITE_ITEM = "user/DelFavoriteITEM" as const;
//액션
export const login = (userStateFromServer : any) => ({
type: LOGIN,
payload : userStateFromServer
});
export const logout = () => ({
type: LOGOUT,
});
export const addFavoriteItem = (items: any) => ({
type: ADD_FAVORITE_ITEM,
payload: items,
});
export const delFavoriteItem = (id: string) => ({
type: DEL_FAVORITE_ITEM,
payload: id,
});
//액션 타입 그룹
export type UserDispatchType =
| ReturnType<typeof login>
| ReturnType<typeof logout>
| ReturnType<typeof addFavoriteItem>
| ReturnType<typeof delFavoriteItem>;
액션을 함수화 및 타입을 묶어 사용을 더 편리하게 만들었다.
export default function userReducer(
state = initialState,
action: UserDispatchType
): InitialState {
switch (action.type) {
case LOGIN:
return {
...state,
isLoggedIn: true,
initialize: true,
};
Reducer 선언시에도 타입을 정확히 지정해주자.
dispatch(fetchLogin() as any);
그럼 위와 같이 비동기 액션을 취할 수 있다. 사실은 redux-toolkit을 사용해 store를 configureStore로 선언하고 정확한 비동기 반환 타입으로 명시하는 것이 일반적이지만, redux-toolkit을 사용하지 않은 경우 as any로 타입 지정을 해줘야했다.
참고
5. redux-thunk로 프로미스 다루기 · GitBook
5. redux-thunk로 프로미스 다루기 이번에는 redux-thunk를 사용하여 프로미스를 다루는 방법을 알아보겠습니다. 이 튜토리얼은 여러분이 프로미스에 대하여 잘 알고있다는 전제하에 진행됩니다. 혹시
react.vlpt.us
위의 글에서는 중복 코드를 줄이는 목적으로 조금 더 체계적인 로직을 제시하고 있다. 다만 오히려 불필요한 코드를 작성하게 될 수도 있으니 규모와 필요에 맞게 적절하게 사용하자.
How to dispatch an Action or a ThunkAction (in TypeScript, with redux-thunk)?
Say I have code like so: import { Action, Dispatch } from 'redux'; import { ThunkAction } from 'redux-thunk'; interface StateTree { field: string; } function myFunc(action: Action | ThunkActio...
stackoverflow.com
앞서 dispatch에서 as any를 사용한 이유는, thunk의 ThunkAction 타입이 dispatch에서 요구하는 AnyAction 타입과 충돌이 발생했기 때문이다. 여러가지 솔루션들이 있었지만 해결된 솔루션이 없었다. thunk가 default가 되는 redux-toolkit을 설치하는 것이 일반적인 솔루션인듯 하다.
'React > React Tech' 카테고리의 다른 글
[React] 다국어 변환 라이브러리 i18n (0) | 2022.06.02 |
---|---|
[React] Redux 이해하기 (0) | 2022.04.17 |
웹사이트 성능 최적화하기 with LightHouse (0) | 2022.04.13 |
[React] React.Lazy() (Code Splitting) (0) | 2022.04.11 |