본문 바로가기
FE/React

전역 상태 관리

by ideal_string 2022. 10. 9.

전역 상태 관리. 그 말대로 상태를 전역에서 한다는 뜻이다. 보통 우리가 state를 통해 상태 관리한다면 스코프 단위, 혹은 파일 단위다. 물론 props를 통해 자식 요소에게 해당 상태를 전달할 수 있으나, 코드가 늘어나고 props drilling이 지속되면 추후 어디에서 넘어 온 데이터인지 인지하기 어렵다. 따라서 전역으로 상태를 관리한다면 이를 방지하고 사용성도 높일 수 있다. 리액트 기본 기능인 context api가 있고, 라이브러리로는 한참 많이 쓰였던 redux, 최근 많이 쓰이는 recoil, mobx이 있으며 이 외에도 Jotai, swr, constate 등도 있다. 

context api

위에서 언급했듯 리액트에서 기본으로 제공하는 전역 상태 관리 기능이다. createContext로 전역 값을 설정하고 사용할 컴포넌트에서 useContext로 사용한다. 

// 설정 시
export const WindowSizeContext = createContext({
  isNormalScreen,
  isTablet,
  isMobile,
});

// 사용 시
const { isNormalScreen, isTablet, isMobile } = useContext()

 

물론, 설정한 context를 어느 컴포넌트에서 사용될 지 설정해야 한다. 나는 프로젝트 전체서 사용할 값이라 아래와 같이 설정했다. 

// context를 설정한 파일
export const WindowSizeContext = createContext({
  isNormalScreen: false,
  isTablet: false,
  isMobile: false,
});

export default function WhatViewPortSize(props: any) {
  const isNormalScreen = useMediaQuery({ query: "(min-width: 1025px)" });
  const isTablet = useMediaQuery({
    query: "(max-width: 1024px) and (min-width: 769px)",
  });
  const isMobile = useMediaQuery({ query: "(max-width: 768px)" });
  const value = { isNormalScreen, isTablet, isMobile };

  return (
    <WindowSizeContext.Provider value={value}>
      {props.children}
    </WindowSizeContext.Provider>
  );
}

// context를 어디에서 사용할 지 설정한 _app 파일
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <WhatViewPortSize>
              <Component {...pageProps} />
    </WhatViewPortSize>
  );
}

export default MyApp;

위와 같이 createContext 값을 설정하고, _app에 전체 컴포넌트를 감싸두면 useContext를 통해 페이지 내 어느 컴포넌트에서도 사용할 수 있다. 그 말 대로 전역 에서 상태 값을 사용할 수 있게 된다. props drilling이 더이상 필요 없다. 

물론, contexta api의 단점도 존재한다. 위 코드를 예시로 들어본다. 현재 출력 중인 페이지에서 isTablet의 값을 사용하고 있다고 가정하자. isTablet의 값이 바뀌어서 현재 출력 중인 페이지가 다시 리렌더링 된다. 여기까진 정상이다. 다만 같은 context 내에 isMobile 값이 업데이트 된다면 어떨까? 현재 출력 중인 페이지에서 isMobile값을 사용하지 않더라도 다시 리렌더링된다. 같은 곳에서 존재하기 때문이다. 이를 방지하고자 context를 별도로 만들기도하는데, 이는 매우 번거로워진다. 이를 보완하고자 라이브러리리들이 나타났다.

 

전역 상태 관리 라이브러리

redux

redux는 react와 궤를 같이 했다고 무방할 정도로 오랜기간 많이 쓰인 전역 상태 라이브러리다. 위에서 언급한 불필요한 리렌더링이 없어 성능면에서도 낫다. react와 redux는 한 몸이라 여길 정도였단다. react를 처음 설치할 때부터 함께 설치할 정도였으니. 다만, redux는 전역 상태 관리에 대한 개념 없이 바로 접근하기엔 학습 난이도(reduser, action 객체 등 먼저 이해야할 개념 필요)가 있는 편이다. 따라서 지금 react를 처음 배우는 입장이라면 러닝커브가 더 쉬운 라이브러리부터 접근해도 나쁘지 않을 듯하다.

출처 :&nbsp;https://ko.redux.js.org/tutorials/essentials/part-1-overview-concepts

redux 공식 문서에서 가져온 redux 흐름 이미지다. UI에서 Deposit을 클릭하면 이벤트 핸들러가 발생하고, 핸들러 안에 있는 미리 설정한 함수를 통해 action을 발생시킨다. 그리고 값을 어떻게 처리할 지 담아둔 store에 가서 해당 action을 수행하고 state를 반환한다. 값을 받환 받았으니 기존 값을 새로 업데이트 한다. 기본 흐름은 state와 비슷함을 알 수 있다. (redux 코드 살펴보기)

 

Mobx

redux는 사용하기 위한 세팅, 즉 보일러플레이트가 많이 필요했다. 이런 단점을 보완하고자 나타난 라이브러리다. redux는 reducer와 action 등 여러가지 세팅이 번거로웠다면 Mobx는 이런 점을 완화시켰다. 

출처 :&nbsp; https://ko.mobx.js.org/README.html

공식 문서에서 가져온 MobX 흐름이다. 어떤 action이 실행되면, 관찰할 수 있는 상태 값이 업데이트된다. 그 후에 이 값을 바라보고 있는 z컴포넌트에 알림(notify)이 가고, 렌더링한다. redux와 다르게 Mobx는 action이 직접 state 업데이트 한다. reducer에 대한 정의가 줄어들었단 소리다. 

 

SWR

Next.js 개발한 zeit에서 만든 라이브러리다. 기본 전역 상태 관리는 다른 라이브러리와 같다. 다만, 통신 부분에서 차별점을 갖고 있다. 처음 cache로 값을 받아온다. 이때 UX가 그려지는 부분에서 강점을 갖는다. 먼저 화면에 cache로 받아온, 혹은 cache에 있는 정보를 먼저 띄운 후, 정보가 갱신될 때 값을 다시 출력한다. 

 

recoil

비교적 최신 라이브러리다. 사용 방법이 간편해 사용률이 높아지는 추세다. 더불어 기본 사용법은 context api, Mobx 등 보다 간편하다. 마치 useState를 사용하는 것 만큼 쉽다.  아래 코드만으로 정리되니 얼마나 간편한지 볼 수 있다. 

// recoil 설치 후 RecoilRoot 가져와 recoil 사용할 컴포넌트를 감싸준다.
import { RecoilRoot } from "recoil";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <WhatViewPortSize>
      <RecoilRoot>
              <Component {...pageProps} />
      </RecoilRoot>
    </WhatViewPortSize>
  );
}
  // 전역 상태로 관리할 변수와 기본 값을 설정한다. 이때 recoil에서 제공한 atom 함수를 활용.
import { atom } from "recoil";

export const VisitedPageState = atom({
  key: "VisitedPageState",
  default: "",
});
// 사용할 페이지 혹은 컴포넌트에서 useState 사용하듯 사용. 다만, 초기값 자리에 미리 설정한 변수 적용.
const {state, setState} = useRecoilState(VisitedPageState)

 

 

마무리

전역 상태 관리는 크게 두 가지로 나뉘는 듯하다. 프론트엔드에서만 쓰일 전역 상태, 백엔드에서 받아온 데이터를 관리할 전역 상태다. 만약 통신이 필요 없는 간단한, 그리고 프론트엔드 안에서 처리되어야할 전역 상태 값들이라면 recoil같이 가볍고 간단한 라이브러리로도 문제 없고 무리가 없다. 다만 백엔드 데이터의 경우 좀 다른 듯하다.

처음 redux가 나왔을 당시 redux를 만든 사람을 페이스북에서 데려갔고, react 기본서에 redux가 포함될 정도였다. 번거로운 세팅이 발목 잡았다. 이후 Mobx와 SWR 등이 나타났다. 그리고 현재는 어떤 API를 쓰느냐에 따라 나뉘는데, graphql을 쓴다면 apollo/client, rest API를 쓴다면 react-query를 사용한다. 이렇게 나뉘게 된 이유는 redux를 통한 백엔드에 대한 데이터 처리다.

redux만 있었을 시절. 게시판 데이터가 redux에 있다면 해당 데이터를 가져와 컴포넌트에 뿌리고, 없다면 다시 백엔드에 데이터를 요청한다. 다른 페이지에서도 해당 데이터가 필요하다면 먼저 redux를 살펴 본 후 없다면 다시 백엔드에 요청한다. 이를 fetch policy라고 불렀다. 그러나 이 과정을 일일이 수동으로 해주어야 하는게 단점이었다. 프로젝트 규모가 작다면 굳이 redux를 쓰지 않았단다. 그러면서 mobx와 SWR등이 나타났고, 현재는 fetch policy가 내장된 react-query와 apollo/client을 사용하고 있다. 

여기서 또 의견이 나뉜다. 프론트엔드에서 사용할 전역 값을 어디에서 관리할 것인가. 1. 프론트엔드 데이터는 얼마 안되니 apollo/client나 react-query를 활용한다. 2. 기존처럼 프론트엔드는 redux를 활용한다. 3. 프론트엔드 상태 관리하려고 굳이 힘들게 redux를 다시 세팅하기 어려우니, 작은 리덕스(recoil) 같은게 있으면 그것을 활용하자. 다만, redux toolkit이 나오면서 redux를 쉽게 사용하는 방법이 생기면서 또다른 작은 의견이 생겼다.

 

전역 상태 관리를 배우고 찾아보니 생각보다 전역 상태 관리를 어떻게 하느냐가 중요한 듯했다. 많은 것들이 얽혀 있는 느낌이다. 전역 상태 관리가 어떻게 되어 왔는지 흐름을 알게 되니, 조금이나마 앞으로 어떻게 전역 상태를 바라봐야할 지 감이 온다. UX 관리할 때 전역 변수, 그리고 서버와 통신할 때 데이터, 이 두 가지로 정리된다. UX에서는 테마나 컴포넌트 단순 상태를 조절하기 위한 변수가 필요한 것이니 가벼울 수록 좋고, 백엔드 데이터는 통신량을 줄여 비용을 절감하는 게 핵심인 듯하다. 다만, 엄청 큰 프로젝트가 아니면 비용면에서 이득이 크지 않을 수 있으니 인건비 혹은 작성 시간 면에서 어느 쪽이 더 유리한 지 고려하고 결정해야할 사안으로 보인다.

 

※ 잘못된 내용이 있을 경우 댓글로 알려주세요. 배우고 익히고 수정하겠습니다:)

'FE > React' 카테고리의 다른 글

LazyLoad & PreLoad  (0) 2022.10.24
HOC & HOF  (0) 2022.10.19
Lifecycle(feat. DOM)  (0) 2022.10.03
Class Component & Functional Component  (0) 2022.10.01
useEffect  (1) 2022.09.30