Context를 사용하면 자식 컴포넌트가 멀리 있는 부모 컴포넌트로부터
props로 전달받지 않으면서 정보를 받을 수 있습니다.
예를 들어, 애플리케이션의 최상위 컴포넌트는 현재 UI 테마를 아래의 모든 컴포넌트의 깊이와 상관없이
전달할 수 있습니다.
const ParentComponent = () => {
const [someState, setSomeState] = useState("전달 완료!");
return <SomeComponent someState={someState} />;
};
const SomeComponent = ({ someState }) => {
return <SomeComponent2 someState={someState} />;
};
const SomeComponent2 = ({ someState }) => {
return <SomeComponent3 someState={someState} />;
};
const SomeComponent3 = ({ someState }) => {
return <SomeComponent4 someState={someState} />;
};
const SomeComponent4 = ({ someState }) => {
return <div>{someState}</div>;
};
SomeComponent4에 someState 변수가 필요하고, Props drilling을 통해서 theme 변수를 전달하는 예제입니다.
전역적인 상태가 someState 말고도 여러개라면 어떨까요?
const ParentComponent = () => {
const [someState, setSomeState] = useState("전달 완료!");
const [someState2, setSomeState2] = useState("전달 완료2!");
return <SomeComponent someState={someState} someState2={someState2} />;
};
const SomeComponent = ({ someState, someState2 }) => {
return <SomeComponent2 someState={someState} someState2={someState2} />;
};
const SomeComponent2 = ({ someState, someState2 }) => {
return <SomeComponent3 someState={someState} someState2={someState2} />;
};
const SomeComponent3 = ({ someState, someState2 }) => {
return <SomeComponent4 someState={someState} someState2={someState2} />;
};
const SomeComponent4 = ({ someState, someState2 }) => {
return <div>{someState}{someState2}</div>;
};
보기만해도 현기증이 일어나는 코드가 완성되었습니다.
위 예제에서는 state 만 전달해주지만, 만약 setState 와 같이 상태를 변경해주는 함수를 props로 넘겨주고,
중간중간의 컴포넌트에서 사용한다고 하면 가독성은 수직으로 떨어지고 유지보수의 난이도는 정말 높아질 것 같습니다.
또한, 위 문제점 말고도 중요한 문제가 발생하는데,
실질적으로 state를 사용하는 컴포넌트는 SomeComponent4 이지만 만약 state 가 변경된다면
SomeComponent4 만 리렌더링 되는것이 아닌 1,2,3,4 컴포넌트가 모두 리렌더링 되는 이슈가 발생합니다.
로는 Redux, Recoil, zustand 와 같은 라이브러리들이 위 문제를 해결하기 위해 등장하였습니다.
정확히는 Redux가 가장 처음 등장하였고, Redux를 사용하려면 너무나도 복잡해
그와 비슷한 전역 관리 라이브러리이고 조금 더 간편한 Recoil, zustand 등과 같은 라이브러리들이 등장하였죠.
Context API 자체만으로는 전역 상태 관리 랑은 조금 거리가 있습니다.
공식 문서에서는 Context API를 이렇게 설명하고 있습니다.
보통의 경우 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 정보를 전달합니다. 그러나 중간에 많은 컴포넌트를 거쳐야 하거나, 애플리케이션의 많은 컴포넌트에서 동일한 정보가 필요한 경우에는 props를 전달하는 것이 번거롭고 불편할 수 있습니다. Context를 사용하면 명시적으로 props를 전달해주지 않아도 부모 컴포넌트가 트리에 있는 어떤 자식 컴포넌트에서나 (얼마나 깊게 있든지 간에) 정보를 사용할 수 있습니다.
Context API는 정보를 전달해주고 읽을 수 있게"만" 만들어줍니다.
Redux 같은 전역 상태 관리 라이브러리는 정보를 전달해주고, 읽게끔해주고, 수정할수도 있게,
즉, 관리 를 해주는 것이죠. Context API는 전역 상태 공유 라고 생각하면 편할 것 같습니다.
하지만 이러한 Context API를 전역 상태 관리 라이브러리처럼 사용할 수 있는 방법이 있습니다.
바로 Context API와 useState, useReducer 와같은 hook을 같이 사용하는 방법입니다.
위에서 본 예제를 Context와 useState hook으로 리팩토링해보겠습니다.
const SomeStateContext = createContext({
someState: "전달 완료!",
someState2: "전달 완료!",
handleChangeSomeState: () => {},
});
const useSomeState = () => {
return useContext(SomeStateContext);
};
const SomeStateProvider = ({ children }) => {
const [someState, setSomeState] = useState("첫번째 state 전달 완료!");
const [someState2, setSomeState2] = useState("두번째 state 전달 완료!");
const handleChangeSomeState = () => setSomeState("첫번째 state 수정 완료!");
return (
<SomeStateContext.Provider
value={{ someState, someState2, handleChangeSomeState }}
>
{children}
</SomeStateContext.Provider>
);
};
const ParentComponent = () => {
return (
<SomeStateProvider>
<SomeComponent />
</SomeStateProvider>
);
};
const SomeComponent = () => {
return <SomeComponent2 />;
};
const SomeComponent2 = () => {
return <SomeComponent3 />;
};
const SomeComponent3 = () => {
return <SomeComponent4 />;
};
const SomeComponent4 = () => {
const { someState, someState2, handleChangeSomeState } = useSomeState();
return (
<div>
<p>{someState}</p>
<p>{someState2}</p>
<button onClick={handleChangeSomeState}>수정하기</button>
</div>
);
};
Props drilling과는 다르게 props로 전달되는 상태가 사라진 것을 알 수 있습니다.
또한, 상태가 바뀌었을때에는 부모 컴포넌트의 자식 컴포넌트들이 전부 리렌더링 되는것이 아닌,
상태를 사용하고있는 SomeComponent4만 리렌더링되어 성능적으로도 좋아졌다고 말할 수 있겠네요!
전달 해줄 수 있는 API 이다. (전역 상태 관리가 아니다.)useState hook 으로 전역 상태 관리를 할 수 있다.Props drilling과는 다르게 불필요한 리렌더링을 줄일 수 있고, 가독성과 유지보수성이 올라간다.하지만 모든 상황에서 props drilling이 아닌 Context API를 사용하는 것은 아닙니다.
오히려 Context API를 사용하는것보다 props drilling을 사용하는 것이 좋은 상황도 있죠.
공식 문서에서는 이렇게 말해줍니다.

그리고 Context API + useState or useReducer 훅을 사용하는 것보다
전역 상태 관리 라이브러리를 사용하는것이 좋은 상황일때도,
전역 상태 관리 라이브러리를 사용하는것보다 위 조합이 좋은 상황일때도 있습니다.
그리고 Redux를 사용하는것보다 zustand를 사용하는 것이 나은 상황일때도 있죠.(...)
배울건 많지만 천천히 포스팅하며 공부해나아가면 될 것 같습니다.