[BlockCar][상태관리] Redux
by 한만섭
Q. Redux가 무엇이라고 생각하시나요?
- redux는 하향식 props 전달을 하게 될 경우 생기는 비효율성을 해결하기 위해 만들어진 중앙 상태 관리 라이브러리라고 생각합니다.
- redux를 사용하지 않으면 어떤 비효율성이 있을까?
- 상위 컴포넌트가 하위 컴포넌트에게 props를 전달하게 될 경우 중간 컴포넌트는 해당 props가 필요하지 않는데 상위 컴포넌트에게 받아서 하위 컴포넌트에게 넘겨줘야 하는 상황이 발생합니다.
- 개발자가 props의 흐름을 다 알고 있어야 합니다. 동료 개발자는 props의 흐름에 대해서 알지 못하게 됩니다.
- 여러 컴포넌트에서 공유하는 State를 사용하기 위해 상위 컴포넌트에서 여러 컴포넌트로 props를 전달하는 불필요한 반복이 나타나게 됩니다.
1. redux를 사용하지 않은 경우
2. redux를 사용한 경우
-
여러 컴포넌트에서 하나의 State를 사용할 경우 Redux의 Store를 가르키기만 하면 됩니다.
-
상위 컴포넌트가 데이터 처리 요청 후 Redux의 Store에 State를 Set해놓으면 해당 State를 사용하는 컴포넌트는 모두 Render됩니다.
Q. Redux를 어떻게 사용했나요?
-
크게 보면 페이지 별로 Reducer를 만들고 rootReducer에서 모든 Reducer들을 컴바인 해서 하나의 rootReducer로 사용했습니다.
-
각각의 Reducer 파일 구조는 아래와같이 Types, Actions, Reducer 이 세개의 파일로 작성했습니다.
-
reducer/auth/authTypes.tsx
// action의 type의 type export const TOGGLE_SIGNUP_POPUP = "TOGGLE_SIGNUP_POPUP"; export const TOGGLE_SIGNIN_POPUP = "TOGGLE_SIGNIN_POPUP"; export const LOGIN = "LOGIN"; export const LOGOUT = "LOGOUT"; // authReducer의 initialState type export interface IAuthState { isSignUpPopUpShow: boolean; isSignInPopUpShow: boolean; loggedInUser: user; } // actionCreator 의 type interface showSignUpPopUpAction { type: typeof TOGGLE_SIGNUP_POPUP; isSignUpPopUpShow: boolean; } interface showSignUpPopInAction { type: typeof TOGGLE_SIGNIN_POPUP; isSignInPopUpShow: boolean; } export interface user { id: string; seq: number; type: string; } interface logInAction { type: typeof LOGIN; user: user; } interface logOutAction { type: typeof LOGOUT; } export type AuthActionType = | showSignUpPopUpAction | showSignUpPopInAction | logOutAction | logInAction;
-
로그인, 회원가입 화면을 보여주거나 숨기는 액션의 이름, Type 정의
-
AuthReducer에서 사용될 State의 Type 정의
-
Action은 각각 export type 하는 것이 아니라 하나의
AuthActionType
이라는 Type으로 사용했습니다.export type AuthActionType = | showSignUpPopUpAction | showSignUpPopInAction | logOutAction | logInAction;
-
-
reducer/auth/authActions.tsx
import { AuthActionType, TOGGLE_SIGNUP_POPUP, TOGGLE_SIGNIN_POPUP, LOGIN, LOGOUT, user } from "./authTypes"; // actionCreator 정의 export const toggleSignUpPopUp = ( isSignUpPopUpShow: boolean ): AuthActionType => { return { type: TOGGLE_SIGNUP_POPUP, isSignUpPopUpShow }; }; export const toggleSignInPopUp = ( isSignInPopUpShow: boolean ): AuthActionType => { return { type: TOGGLE_SIGNIN_POPUP, isSignInPopUpShow }; }; export const logInAction = (user: user) => { return { type: LOGIN, user }; }; export const logOutAction = () => { return { type: LOGOUT }; };
Actions.tsx
파일에서는 Action을 return 해주는 ActionCreator를 정의하는 곳입니다.
-
reducer/auth/authReducer.tsx
import { IAuthState, AuthActionType, TOGGLE_SIGNUP_POPUP, TOGGLE_SIGNIN_POPUP, LOGIN, LOGOUT } from "./authTypes"; const initialState: IAuthState = { isSignUpPopUpShow: false, isSignInPopUpShow: false, loggedInUser: JSON.parse(sessionStorage.getItem("LoggedInUser")) }; const authReducer = ( state = initialState, action: AuthActionType ): IAuthState => { switch (action.type) { case TOGGLE_SIGNUP_POPUP: return { ...state, isSignUpPopUpShow: !state.isSignUpPopUpShow }; case TOGGLE_SIGNIN_POPUP: return { ...state, isSignInPopUpShow: !state.isSignInPopUpShow }; case LOGIN: sessionStorage.setItem("LoggedInUser", JSON.stringify(action.user)); return { ...state, loggedInUser: action.user }; case LOGOUT: sessionStorage.removeItem("LoggedInUser"); return { ...state, loggedInUser: null }; default: return state; } }; export default authReducer;
Reducer.tsx
파일에서는 Reducer에 사용될 초기 State인 initialState를 정의했고, Dispatch에 의한 Action이 들어왔을 때 어떤 동작을 할 것인지 정의하는 Reducer를 만들었습니다.
-
reducer/index.tsx
import HeaderReducer from "./header/headerReducer"; import SearchPageReducer from "./searchPage/searchPageReducer"; import AuthReducer from "./auth/authReducer"; import MyPageReducer from "./myPage/myPageReducer"; import { IMyPageState } from "./myPage/myPageTypes"; import { combineReducers } from "redux"; import { IHeaderState } from "./header/headerTypes"; import { ISearchPageState } from "./searchPage/searchPageTypes"; import { IAuthState } from "./auth/authTypes"; export interface rootState { HeaderReducer: IHeaderState; SearchPageReducer: ISearchPageState; AuthReducer: IAuthState; MyPageReducer: IMyPageState; } const rootReducer = combineReducers({ HeaderReducer, SearchPageReducer, AuthReducer, MyPageReducer }); export default rootReducer;
index.tsx
에서는 프로젝트에 사용하는 모든 Reducer를 import 해서 redux의 combineReducers를 사용해 rootReducer로 묶어줍니다.- rootReducer도 type을 정해줘야 하는데 rootReducer에 사용되는 모든 reducer의 initialState를 사용하면 됩니다.
-
Q. Redux를 사용하며 느낀 점.
- 기존에 일일히 하위 컴포넌트로 props를 넘겨주는 방식은 데이터가 많아질 경우 데이터의 흐름을 파악하기 어려웠던 경향이 있었습니다.
-
Redux를 사용하게 될 경우, 백엔드에서 요청한 데이터를 Redux의 Store에 넣어주기만 하면 해당 State를 사용하는 모든 컴포넌트가 자동으로 Render되기 때문에 데이터를 Store에 넣어주는 로직만 생각하면 돼서 편했습니다.
- redux를 사용해보기 전에 Context를 사용해서 상태관리를 했었습니다. Context를 사용했을 때보다 action, type, reducer와 같은 어려운 개념이 나와서 개념을 익히는데 좀 어려웠습니다.
- TypeScript와 함께 사용하다보니 action의 type, state의 type까지도 다 정해줘야 해서 번거로웠지만, 익숙해지니 편했습니다.
- Redux를 TypeScript, ReactHooks와사용한 이슈에 대해서는 TypeScript + Redux + ReactHooks = ???에서 자세히 확인하실 수 있습니다.
Subscribe via RSS