예전에 면접 본 곳 중 몇 군데서 받은 질문 중 하나가 redux나 recoil 같은 상태 관리 오픈소스를 사용하지 않고 리액트 자체에서 제공하는 context API 사용 해본적 없냐는 질문이다. 없다고 하면 추가적으로 써볼 필요성을 느껴본 적 없냐는 질문을 여러 번 받았다.
사용한 적 없던 당시에도 굳이 실무에서 상태관리 라이브러리 사용하지 않고 context 훅을 쓸 이유가 없다고 답했던 것 같은데 이건 내가 오픈소스를 만들면서 context API를 직접 사용해보고 그 이유를 보충해서 쓰는 내용이다.
먼저 아래는 실제로 내가 오픈 소스를 만들면서 컨텍스트 API를 사용한 코드다. 해당 패턴은 실제로 context를 사용하는 권장 패턴 중 하나에 속한다.
import React, { createContext, useState } from 'react';
export const ConfigContext = createContext({
room: "",
handleRoom: (room) => { },
imageSize: 1 * 1024 * 1024,
handleImageSize: (imgsize) => { },
});
const ConfigContextProvider = ({ children }) => {
const [room, setRoom] = useState('');
const handleRoom = (room) => setRoom(room);
const [imageSize, setImageSize] = useState(1 * 1024 * 1024);
const handleImageSize = (imgsize) => {
if (imageSize > 0) setImageSize(Math.floor(imgsize * 1024 * 1024));
}
return (
<ConfigContext.Provider
value={{
room,
handleRoom,
imageSize,
handleImageSize
}}
>
{children}
</ConfigContext.Provider>
);
};
export default ConfigContextProvider;
채팅 관련 오픈 소스를 만들면서 컴포넌트 간의 config 변수들을 props drilling을 피하기 위해서 컨텍스트를 이용해서 만든 글로벌 상태 코드이다. context api 자체를 사용하기 위해서는 기본적으로 해당 컨텍스트를 사용할 컴포넌트에 해당 컨텍스트.Provider라는 형식으로 컨텍스트를 주입하는 것과 createContext로 만든 컨텍스트를 내보내고 사용할 컴포넌트에서 useContext로 가져와서 사용해야 한다.
이 자체는 솔직히 recoil이나 redux등의 상태 관리 오픈소스와 별 차이가 없는 편이다. recoil이나 redux 모두 루트 Provider로 앱과 스토어 간의 연결을 추가하고 해당 스테이트를 사용할 컴포넌트들에서 상태 관리 관련한 로직들을 불러와서 사용하는 방식이니 말이다. 그러나, 상태가 늘어난다면 어떻게 될까? 하나의 context에 넣을 수 있는 상태들의 갯수가 딱히 제한이 있는 것은 아니지만 우리는 항상 코드의 가독성을 고려해서 개발을 진행 할 필요가 있다.
즉, 하나의 컨텍스트에 너무 많은 state를 넣는 것은 바람직하지 않다는 것이다. 여러 개의 컨텍스트를 사용할 경우 아래의 코드와 같은 모습이 된다.
import React from 'react';
import ModalContextProvider from './modalContext';
import ConfigContextProvider from './configContext';
const RootContextProvider = ({ children }) => {
return (
<ConfigContextProvider>
<ModalContextProvider>
{children}
</ModalContextProvider>
</ConfigContextProvider>
);
};
export default RootContextProvider;
나는 컨텍스트를 여러 개로 나누고 provider들을 임의로 묶은 RootContextProvider를 만들었다. 물론 이렇게 하지 않고 하나의 컨텍스트를 사용하고 그 내부의 state와 handle(reducer)를 분리해서 import 하는 방식도 가능하긴 하다.
어쨌든 동일한 파일(NAME SPACE)에 많은 양의 코드와 여러 책임이 혼재되는 것이 문제가 되는 것이니까.
또는 아예 위와 같은 방식이 아닌 컴포넌트 내부에서 context를 바로 만들고 export 해줄 수 도 있을 것이다.
또는 위와 같이 Root로 묶지 않고 각 Provider를 사용하는 컴포넌트들 마다 묶어주는 방식 등 여러 가지 방법이 존재한다.
내가 해당 방식들을 사용하지 않는 이유는 아래와 같다.
1. 컨텍스트 내부의 state를 개별 파일로 분리해서 하나의 context에 import 하는 방식으로 관리하는 경우:
기존 오픈소스와 가장 유사한 방법이다. 상태 갱신 관련한 서포터가 없다거나 추가적으로 해당 state와 handle함수를 따로 모아서 provider와 context에 바인딩하는 로직 정도가 필요하다는 점 정도 제외하고는 1 store 구조에 해당하니 말이다. 그런데, 비슷한 걸 만들어서 사용할 것이라면 굳이 context를 쓸 이유가 있을까? 기존 상태 관리 오픈소스들이 제공해주는 추가적인 기능(saga, thunk)들과 보편적인 기술 스택이 주는 협업의 이점을 무시하고 쓸 이유가 있을까?
사실 없다.
2. 특정 컴포넌트에서 바로 context를 내보내거나 Root가 아닌 특정 부분에서만 주입해서 받아서 쓰는 경우:
처음 프로젝트를 접하는 사람이나 한동안 해당 프로젝트를 관리하지 않고 있다가 다시 접할 때 해당 context가 어디에 있는지 쉽게 알아볼 수 있을까? 없다. 굳이 위와 같은 패턴으로 context를 쓰길 권장하는 이유도 컴포넌트 코드에 state 로직을 같이 넣을 경우 구조가 복잡해지는 경우 context에 대한 로직들을 알아보기 어려워서 분리하는 것인데 그렇게 쓸 경우 알아보기 힘들다. 거기다가 특정 state가 다른 컴포넌트에서 쓰이는데 state를 provider 해주는 부분이 더 상위의 컴포넌트로 이동해야 한다면? 또 코드를 수정해야 한다. 사실 이것과 props drilling이 별 차이가 없다. 컴포넌트 내부에서 상태가 drilling 되나 컴포넌트 컨텍스트 외부에서 props대신 export import 되나의 차이이다.
결국 사진과 같이 store(이름은 별 상관없다.)를 따로 구분해서 context(store)에 관한 로직을 관리해주는 게 가장 베스트에 해당한다. 그렇기에 상태 관리 오픈소스들이 대부분 react root에서 Root Provider를 주입하는 방식을 따른다.
그러므로 실무에서 Context Api를 쓸 일은 없다. 사용법이 매우 쉬운 것도 아니며, 제대로 쓸려면 결국 기존의 상태관리 구조에 따라서 패턴을 짜야한다. 아주 작은 단위의 앱의 경우 굳이 라이브러리를 추가해서 쓸 이유가 없다고 느낄 수 있지만, 결국 해당 프로젝트 규모가 조금이라도 커지는 순간 라이브러리를 도입해야 한다. 보통 코드를 작성할 때는 가독성 있고, 확장성 있게 짜라고 하는데 규모가 조금이라도 커질 경우 바로 갈아엎어야 할 요소를 쓸 이유가 있을까?
그렇기에 나는 리액트 상태 관리에 대해서 자료들을 수집 후 비교한 후에 context를 쓸 이유가 없다고 판단했고 아무리 작은 프로젝트라도 redux와 recoil을 사용했다.
지금 와서 오픈소스에 context hook을 사용한 이유는 해당 오픈 소스의 의존성 라이브러리를 하나라도 줄이고 싶어서 정도의 이유이다. 또는 공부용으로 쓰긴 할거 같다. 공부할 게 있긴 하다면 차라리 리액트 소스코드를 뜯어보는 게 맞지 않을까?
'프론트엔드' 카테고리의 다른 글
[프론트 엔드] 주소,Map,Local API 차이 간단 정리 (0) | 2022.01.21 |
---|---|
[리액트][함수] 컴포넌트 컨텍스트 & 사이드 이펙트 컨텍스트 (0) | 2022.01.21 |
[프론트엔드][개발환경]웹팩 & 웹팩 보일러 플레이트 (0) | 2021.11.15 |
[리액트] 제어 컴포넌트, 비제어 컴포넌트 (0) | 2021.10.28 |
[프론트엔드] ajax & axios (0) | 2021.09.16 |