프로젝트를 시작하기전 항상 상태 관리 도구를 무엇을 쓸까?를 고민한다.
최근 프로젝트에서는 전역으로 관리할 상태가 거의 없으므로 리액트에서 제공하는 Context API를 사용하기로 했다.
이 때 까지만해도 Context가 상태 관리 도구인줄 알았으나 Context는 상태관리 도구가 아니라 의존성을 주입해주는 역할을 하는 것을 알게되었다.
해당 포스팅에서는 Why React Context is Not a "State Management" Tool를 읽고 왜 Context가 상태 관리 도구가 아닌지 정리한다.
또, Context + useReducer와 redux의 차이와 리액트의 상태 관리 라이브러리(redux, jotai, recoil)가 어떻게 구성되어있는지 간단하게 살펴본다.
상태 관리를 한다고 하기 위해서는 다음의 세 가지 역할을 할 수 있어야한다.
리액트의 useState와 useReducer는 상태 관리의 예시이다. 두 개의 hook은 아래의 역할을 한다.
redux는 현재 값을 getState로 확인할 수 있으며 dispatch를 통해 값을 업데이트할 수 있다.
또, subscribe를 통해 해당 state를 구독하고 있는 컴포넌트에 업데이트가 되었음을 알려줄 수 있으므로 상태 관리툴이다.
이제 상태 관리가 무엇인지 알게되었으니 React의 Context에 대해 알아보자.
공식 문서에 의하면 Context는 필요한 정보를 부모로부터 props로 받을 필요 없이 데이터를 받을 수 있게 해주는 것이라고 나온다.
공식 문서에 Context가 상태 관리를 한다라는 문구가 없는 것을 확인할 수 있다.
단순히 값을 전달하는 역할을 할 뿐이다.
App에서 CountContext를 만들고 Provider로 count, setCount를 전달해보자.
이 때, Provider의 value로 하위 컴포넌트로 전달할 값을 설정할 수 있다.
import { createContext, useState } from "react";
import Child from "./Child";
// Context 생성
export const CountContext = createContext();
function App() {
const [count, setCount] = useState(0);
const countState = { count, setCount };
return (
<>
<CountContext.Provider value={countState}>
<Child />
</CountContext.Provider>
</>
);
}
export default App;
그 후, Child 컴포넌트에서 CountContext를 useContext를 통해 가져와 사용해보자.
import { useContext } from "react";
import { CountContext } from "./App";
function Child() {
// Context 가져오기
const { count, setCount } = useContext(CountContext);
return (
<div>
{count}
<button onClick={() => setCount((prev) => prev + 1)}>+ 1</button>
</div>
);
}
export default Child;
이제 props로 state를 전달하지 않고도 해당 state를 자식에서 읽거나 업데이트할 수 있다.
위 예제를 다시 확인해보면 Context에서는 어떠한 값도 저장하지 않는다.
Context는 아래와 같이 만들어진 state를 단순히 전달할 뿐이다.
const [count, setCount] = useState(0);
결론적으로 state를 관리하는 것은 Context가 아니라 App 컴포넌트의 count State다.
즉, 실제로 상태관리는 useState와 useReducer hook을 통해 이루어진다.
많은 사람들이 상태관리를 위해 Context를 사용하고 있다 말한다.
Context를 상태 관리로 하고 있다는 말은 사실 useReducer로 상태를 관리하면서 Context로 value를 전달해주고 있다는 의미다.
useReducer로 상태를 관리하지만 useReducer를 사용한다는 말을 생략하고 "난 Context를 이용해 상태 관리를 해"라고 말하는 것이 Context를 상태 관리 툴로 생각하는 원인 중 하나이다.
그렇다면 Context + useReducer와 react-redux를 비교해보자.
공통점은 다음과 같다.
차이점은 다음과 같다.
Context + useReducer
react-redux
단순히 props drilling을 피하기 위해 사용한다면 Context를 사용하자.
특정 컴포넌트만 리렌더링 하도록하거나, side effect를 관리하고 싶다면 다른 상태관리 툴을 이용하자.
우리가 흔히 아는 상태 관리 라이브러리(redux, recoil, jotai)가 Context API를 사용하는 것을 이번에 알게되었다.
react-redux 9.0.4 버전을 기준으로 src -> components -> Provider.ts를 보자.
해당 Provider의 props 타입을 확인해보면 context가 있다.
context는 React의 Context를 사용할 지 선택할 수 있다.
그 다음 Provider 컴포넌트에서 해당 인터페이스를 사용한다.
빨간색 박스에서 React의 context를 사용하는지 Redux의 Context를 사용하는지 확인한다.
그 후, 사용하는 Context를 return한다.
recoil 0.7.7 버전을 기준으로 packages -> recoil -> core -> Recoil_RecoilRoot.js를 보자.
121 line에서 React.createContext를 이용해 Context를 생성하는 것을 확인할 수 있다.
519 line에서 앞서 만든 Context를 Provider로 값을 지정하는 것을 확인할 수 있다.
jotai 2.6.1 버전을 기준으로 src -> react -> Provider.ts를 보자.
7 line에서 React의 createContext를 사용하고 29 line에서 앞서 만든 Context를 Provider로 값을 지정하는 것을 확인할 수 있다.