Context API와 Redux 비교하기
전역 상태 관리는 언제 하면 좋을까?
- React의 useState를 이용하면 지역 상태 관리를 할 수 있다.
- 상태 값은 지역 상태관리를 사용하는 컴포넌트 안, 혹은 props로 전달할 때만 하위 컴포넌트로 사용할 수 있다.
- 모든 상태 관리를 useState만을 이용하여 진행할 수도 있지만, 여러 컴포넌트에서 사용되는 상태라면 props로 하나씩 상태 값을 내려주기에는 한계가 있다.
- 이 때 사용할 수 있는 전역상태관리 도구가 Context API, Redux이다.
Context API의 특징
- React 내장기능이기 때문에 리액트에서만 사용할 수 있다.
- Entry 파일(root)에서 구성한 Provider를 내려주는 형식이다.
- 사용하고자 하는 컴포넌트에서 작성한 Dispatch와 State를 꺼내서 사용한다.
- reducer를 여러 개 만들면 Provider에서 여러 단계로 만들어 사용할 수 있다.
Context API 사용 방법
- Context 생성
- createContext()를 통해 상태 저장
- createContext에는 초기 상태 값을 넣을 수 있다.
import { crateContext } from 'react'; const GameBoardStateContext = createContext({});
- Action 생성
- 액션을 지정하여 Reducer에서 서로 다른 타입일 때 다른 로직을 진행시킬 수 있다.
export type Action = { type : "UPDATE"; payload };
-
Reducer 생성
import { Action } from "@/actions/gameBoardAction"; import { GameBoardState } from "@/contexts/gameBoardContext"; const gameBoardReducer = (state: GameBoardState, action: Action): GameBoardState => { switch (action.type) { case "UPDATE": return { ...action.payload, // 변경되는 payload 로직 작성 }; default: throw new Error("Unhandled action"); } };
- Provider 생성
- 중첩된 Provider로 여러 상태를 넣을 수 있다.
export const ContextProvider = ({ children }: { children: React.ReactNode }) => { const [gameBoard, gameBoardDispatch] = useReducer(gameBoardReducer, initState); return ( <GameBoardStateContext.Provider value={gameBoard}>{children}</GameBoardStateContext.Provider> ); };
-
Entry 파일에 적용
const App = () => { return ( <> <ContextProvider>// 하위 컴포넌트들</ContextProvider> </> ); };
- 하위 컴포넌트에서 상태를 사용할 때는 useContext를 사용해 state 값을 꺼낼 수 있다.
- 💡 useContext를 직접 사용하는 것보다 커스텀 Hook을 사용하는 방법이 좋다!
import { useContext } from "react"; const state = useContext(GameBoardStateContext);
- 하위 컴포넌트에서 상태를 사용할 때는 useContext를 사용해 state 값을 꺼낼 수 있다.
Redux의 특징
- React 뿐만아니라 Vue 등 여러 프레임워크에서 사용할 수 있다.
- 상태를 저장하는 Store를 별도로 가지고 있다.
- thunk, saga와 같은 미들웨어를 추가적으로 사용하여 구성할 수 있다.
- Redux devtool extension을 사용하면 상태에 대한 디버깅이 가능하다.
- 전역 상태 관리 외에도 로컬 스토리지 상태저장, 버그 리포트 첨부 기능 등의 기능들을 사용할 수 있다.
Redux의 사용 방법
- Action 생성
- 상태가 변하는 것에 대한 액션을 함수로 작성한다.
export const focusChange = (payload) => { return { type: "focusChange", payload, }; }
- Reducer 생성
- 초기 상태를 설정하고 Action을 type으로 구별하여 상태를 업데이트하는 로직을 Reducer로 작성한다.
const initialState = { startDate: null, endDate: null, focusedInput: START_DATE, }; const datePickerReducer = (state = initialState, action) => { switch (action.type) { case "focusChange": return { ...state, focusedInput: action.payload } default: return state; } };
- Store 생성
- redux의
createStore
를 통해 store의 인자에 생성한 Reducer를 넣어 생성한다. - 만약 Reducer가 여러 개라면 combineReducer를 통해 Reducer를 하나로 합치는 과정을 추가로 진행한다
import { createStore } from "redux"; const store = createStore(datePickerReducer);
- redux의
- react-redux의 Provider로 root에 store 등록
- Entry 파일에서 Provider에 store를 등록한다.
import { Provider } from "react-redux"; ReactDOM.render( <Provider store={store}> <App /> </Provider> );
-
useSelector로 개별 컴포넌트에서 사용
import { useSelector } from "react-redux"; const { datePickerReducer: { startDate, endDate }, } = useSelector((state) => state);
그렇다면 Context API와 Redux 중 어떤 걸 사용하면 되나요?
- Context API와 Redux는 사용법과 구조에 차이가 있을 뿐 전역 상태를 관리한다는 점이 유사하다.
- Redux가 Context API를 기반으로 만들어졌기 때문이다.
- 단순 전역 상태 관리만 있어도 된다면 Context API, 디버깅이나 로깅 드의 상태 관리 외의 기능이나 미들웨어가 필요하다면 Redux를 사용하는 것이 좋다.
- Context API는?
- React를 사용할 때 추가 dependency 없이 사용할 수 있어서 가볍게 사용할 수 있다.
- 상태를 넘겨줄 때 상태가 여러 개라면 Provider를 중첩해서 내려줘야 하기 때문에 불편함이 있다.
- React는?
- saga, thunk와 같은 미들웨어를 추가적으로 사용할 수 있어 비동기 처리를 따로 Util로 처리할 수 있는 장점이 있다.
- 추가 설정을 통해 디버깅을 할 수 있다.
- 미들웨어를 사용하기 위한 개념을 이해해야 하기 때문에 어려운 점이 있다.
결론
- 전역 상태 관리만 원한다면 Context API를 사용한다.
- 상태 관리 외 여러 기능이 필요하다면 Redux를 사용한다.
- Context API는 high-frequency updates에 좋지 않은 성능을 보인다. high-frequency 어플리케이션은 Redux를 사용한다.