본문 바로가기

카테고리 없음

전역관리 어떻게 할것인지 고민해보기(Context, Recoil)

기능 확인

구현하고 싶었던 기능은, 일정 / 휴가 대한 테마를 변경하고 싶었습니다. 일정을 누르면 파란색 테마, 휴가를 누르면 빨간색 테마로 변경하고 싶었습니다. 그렇게 구현하기 위해서 Main Page Header에 Tab Button을 만들었고, Tab 버튼을 클릭할 때마다, props로 각 컴포넌트에 전달했습니다.

 

 

 

문제점

처음 만들 때는 컴포넌트의 수가 적어 간단하게 props로 상태에 대한 boolean 값을 전달해 배경색을 변경할 수 있었습니다.

하지만, 프로젝트의 구조가 복잡해지고 컴포넌트의 수가 많아지면서 props 깊이가 깊어지는 문제가 발생했습니다. 

물론 리액트 공식 홈페이지에서 말하는 적정 깊이(최대 3~4개) 정도였지만, 프로젝트가 커질 수 록 더 많은 컴포넌트가 생길거라 생각했고, 

전역관리를 통해서 관리하는 편이 더 좋겠다 라는 생각이 들었습니다. 

 

개선방안 생각해보기

처음에 생각했던 기능은 Context 였습니다. 일일이 props로 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있기에 

전역관리용으로 괜찮다고 생각했습니다. 그리고 다른 redux와 같은 전역관리를 사용하는것보다 보일러플레이트에 대한 걱정도 줄일 수 있겠다 라는 생각을 했습니다. 

Context API

  1. props로 내려주던 tab 값을 전역관리해서 관리한다.
  2. provider로 MainPage을 감싸주고, 그안에만 한정해서 tab 값을 전달한다.
  3. 테마 변경이 필요한곳에서 요청해 사용한다.
import React, { createContext, useState } from 'react';

type State = boolean;
type Dispatch = React.Dispatch<React.SetStateAction<State>>;

export const ChangeTabContext = createContext<[State, Dispatch]>([
  false,
  () => {
    //
  },
]);

interface ContextProps {
  children: React.ReactNode;
}
export function TabContext({ children }: ContextProps) {
  const [tab, setTab] = useState(false);

  return (
    <ChangeTabContext.Provider value={[tab, setTab]}>
      {children}
    </ChangeTabContext.Provider>
  );
}

Main 에서 다음과 같이 정의하고 값을 전달.

const Main = () => {
  const [tab, setTab] = useContext(ChangeTabContext);
...

  return (
    <TabContext>
      <CalendarContext.Provider value={filterData}>
        <StWrap>
          <StButtonBlcok></StButtonBlcok>
          {tab === false ? <SubMain view={'month'} /> : <SubMain view={'month'} />}
        </StWrap>
      </CalendarContext.Provider>
    </TabContext>
  );

}`

function Header(props: HeaderProps) {
  const [tab, tabHandler] = useContext(ChangeTabContext);

 

Context을 사용하면서 겪었던 문제 

Context API를 활용해서 props로 전달해야만 했던 문제를 해결했고, 각 컴포넌트에서 context를 불러와 Tab 버튼의 상태를 불러 올 수 가 있었습니다. 하지만 사용하면서 느꼈던 한가지 불편한점이 있었습니다. context  API는 상태에 변경이 있을 때마다 provider로 묶어놓은 컴포넌트 전체가 렌더링 된다는 것을 알게되었습니다. 

 

내가 원하는 컴포넌트 뿐만 아니라, provider로 묶어놓은 부모 컴포넌트 기준 자식컴포넌트들 전체가 렌더링 된다는 불편한점이 있어서 이것을 개선하고자 다른 방법을 찾아보게 되었습니다. 

 

 

Recoil 도입 배경 

전역관리를 할 수 있는 것들 중, 간단하게 전연관리를 할 수 있고 쉽게 사용가능한 라이브러리를 찾아보다가 Recoil이라는 것을 알게 되었습니다. Atom이라는 상태를 나타내는 컴포넌트를 생성해 두고, 내가 원하는 컴포넌트에서 상태를 변경 혹은 불러올 수 있었습니다. 사용 방법도 useState를 사용하는 방법과 거의 동일해서 사용하기도 편했습니다. 


그리고 다음과 같은 방법으로 변경했습니다. 

import React from 'react';
import { FeedTitleProps } from './interfaces';
import * as UI from './style';
import { recoilTabState } from '../../states/recoilTabState';
import { useRecoilValue } from 'recoil';

const FeedTitle = ({ clickFn }: FeedTitleProps) => {
  // 테마 세팅용 recoilState
  const tab = useRecoilValue(recoilTabState);
  return (
    <UI.StFeedTitleBlock>
      <UI.StFeedTitleH1 tab={tab}>todo</UI.StFeedTitleH1>
      <UI.StPlusSpan tab={tab} onMouseDown={clickFn}>
        +
      </UI.StPlusSpan>
    </UI.StFeedTitleBlock>
  );
};

export default FeedTitle;

각각의 상태변화가 필요한 컴포넌트에서 useRecoilValue를 호출해서 값만 받아 올 수가 있고,

function Header(props: HeaderProps) {
  const [tab, setTab] = useRecoilState(recoilTabState);
  const selectedDate = useRecoilValue(recoilSelectedDateState);
  const currentTab = useRef<string | number>();

이런듯, useRecoilState를 통해서 tab의 값을 변경할 수 도 있다.

 

회고

context api 나 recoil을 사용해봤습니다. 이 두가지의 장점은 사용하기가 정말 편했다는 점입니다. 
다만 공식 홈페이지나 다른 블로그 혹은 github을 레퍼런스 하면서 느낀점은 정해진 패턴이 없다는 점입니다. 

정해진 패턴이 없다는 말은 사용자가 원하는 대로 커스터마이징 할 수 있다는 말과 같습니다.

그렇기에 사람들마다 작성하는 스타일이 다 달랐습니다. 

이전 프로젝트에서 사용하던 Redux는 flux 패턴이라는 정해진 패턴이 있어서 누가 코드를 장성하든 코드량이 많더라도 이해하는 것은 어렵지가 않았습니다. 

 

하지만 이 두가지는 정해진 패턴이 없기 때문에 작성자의 주관에 따라 다르게 코드를 작성할 수 있었습니다. 저만 생각해도 많이 써보지 못해서 제가 하고싶은데로 로직을 구현한 느낌이었습니다. 다른사람이 만든다면 또 달라지지 않았을 까 생각이 들었습니다.

하지만 사용면에서는 확실히 편했던거 같다. 특히 recoil은 react에서 제공하는 useState와 굉장히 유사하게 사용이 가능해서 사용면에서 정말 편했다. 그래도 대규모로 가면 Redux가 패턴이 정해져 있어서 협업면에서는 편하지 않을까 싶다.