Props 드릴링과 contextAPI

eeensu·2023년 8월 4일
0

React 기본

목록 보기
14/22
post-thumbnail

Props 드릴링이란?

컴포넌트를 제작하고 react 애플리케이션을 만들어 나갈 때, 컴포넌트가 계속 깊어진다는 불편이 발생할 수 있다. 이때 상태를 공유하기 위해 props를 계속 넘겨주는, props가 드릴처럼 하위 컴포넌트에 지속적으로 파고드는 번잡함이 발생한다. 이를 props 드릴링이라고 한다.

또한, state와 관련하여 만약 자식 컴포넌트가 props와 관련된 state를 바꿀 수도 있는 역전현상이 일어날 수 있는데, 이럴 때 어디서 state가 바뀌었는지 추측할 수 없고 이는 버그를 발생시킨다. 이를 해결하기 위해 react는 contextAPI 라는 기능을 제공한다. 이 기능은 현재 로그인한 유저, 테마, 선호하는 국가 언어 등 공통적으로 광범위하게 사용되는 데이터들을 공유하고자 할 때 사용하면 좋다.


contextAPI의 장점

  • 복잡한 데이터 전달 최소화
    컴포넌트 트리의 깊은 곳까지 props를 통해 지속적으로 전달할 필요 없이, useContext() 를 사용하여 쉽게 데이터에 접근할 수 있다. 이로써 프로퍼티 전달의 복잡성을 줄이고 가독성을 향상시킬 수 있다.

  • 전역 상태 관리: 컴포넌트 간에 데이터를 공유할 수 있어서, 상태를 전역적으로 관리할 수 있다. 이로 인해 중복된 데이터를 여러 컴포넌트를 통해 반복적인 props로 넘기지 않아도 되며, 컴포넌트 트리 전체에 데이터를 제공할 수 있다.

  • 중앙 집중적인 데이터 관리
    Context를 사용하면 데이터의 중앙 집중적인 관리가 가능해진다. 이로 인해 어떤 컴포넌트에서 데이터가 변경되더라도 해당 데이터를 사용하는 다른 컴포넌트들이 즉시 업데이트된다.




사용법

react 웹 애플레케이션에서 전역적으로 사용하는 대표적인 상황인 다크모드에 관한 예제를 통해 contextAPI를 사용하는 법을 살펴보자.

1. Context 생성

React.createContext() 는 context 객체를 만든다. context 객체는 데이터를 넣어두는 메모리를 생성한 것이다. 이 함수는 ProviderConsumer 컴포넌트를 반환한다.

export interface IColorCode {
    foreground: string,
    background: string,
}

export interface ITheme {
    light: IColorCode,
    dark: IColorCode,
}

export const themes: ITheme = {
    light: {
        foreground: '#000000',
        background: '#eeeeee',
    }, 
    dark: {
        foreground: '#ffffff',
        background: '#222222',
    }
};

// thmes.dark 값을 init 값으로 갖는 ThemeContext 생성
// Provider로 감싸진 자식에서 따로 값을 설정하지 않으면 사용되는 디폴트 값이다.
export const ThemeContext = createContext<IColorCode>(themes.dark);   

2. Provider 제공

컴포넌트에서 중앙 컨텍스트의 값을 사용하려면, 해당 컴포넌트의 부모로 Provider 컴포넌트를 둔다. 이때 Provider의 value 속성을 통해 컨텍스트의 데이터를 전달하여 어디서든 값을 사용할 수 있도록 만들어준다. 그리고 각각의 테마의 상태를 useState() 로 저장하여 이를 관리한다.

const ContextExample: FC = () => {
  const [theme, setTheme] = useState<IColorCode>(themes.light);

  const onClick = useCallback(() => {
    setTheme(prev => prev === themes.dark ? themes.light : themes.dark);
  }, [theme]);

  return (
    <div>
      <ThemeContext.Provider value={theme}>
        <ThemedButton changeTheme={onClick}/>     
      </ThemeContext.Provider>           
    </div>
  );
};

3. 자식에게 컨텍스트 전달

이 예제에서는 2단계로 자식 컴포넌트가 걸쳐져 있다. 먼저<ThemedButton /> 의 컴포넌트 다음과 같다.

const ThemedButton: FC<{ changeTheme: () => void }> = ({ changeTheme }) => {

    const theme = useContext<IColorCode>(ThemeContext);

    return (
        <>
            <button 
                onClick={changeTheme}
                style={{
                    backgroundColor: theme.background,
                    color: theme.foreground
                }}
            >
                button click!
            </button>
            <ChildComponent />
        </>
   
    );
};

<ThemeButton /> 의 안에 있는 <ChildComponent />는 다음과 같다.

const ChildComponent: FC = () => {
    const theme = useContext<IColorCode>(ThemeContext);

    return (
        <p style={{
            color: theme.foreground,
            background: theme.background
        }}>
            안녕하세요!
        </p>  
    );
};

결과는 아래와 같다.

버튼을 클릭하면 아래와 같이 변경된다. props로 전달하는 과정없이, context의 효과로 컴포넌트의 테마가 변경이된 것을 확인할 수 있다.




ContextAPI에서 제공하는 Consumer 컴포넌트를 통해 <ThemedButton />를 다른 방법으로 을 구성할 수 있다. Consumer 안에는 함수가 자식으로 오는데, 이때 매개변수로 ThemeContext의 값이 온다. 하지만 이 방법은 이전 클래스 컴포넌트에서 사용했던 방법으로, 함수형 컴포넌트에서는 useContext() 를 사용하는 것이 더욱 가독성이 좋다.

    return (
        <ThemeContext.Consumer>
            {(value) => (
                <>
                   <button 
                    onClick={changeTheme}
                    style={{
                        backgroundColor: value.background,
                        color: value.foreground
                    }}
                >
                    button click!
                </button>
                <ChildComponent />
                </>              
            )}
        </ThemeContext.Consumer>
    );



주의 사항

이처럼 contextAPI는 매우 좋은 최적화 기능을 제공하는 것처럼 보인다. 하지만 무조건 이를 남용하는 것은 좋지 않다. contextAPI를 사용할 때 고려해야할 주의사항이 있다.

  • 합성
    여러 레벨에 걸쳐 props를 넘기는 걸 대체하는 데에 context보다 컴포넌트 합성이 더 간단한 해결책일 수도 있다. 때문에 개발자는 각자의 상황에 따라 더 효율적인 방법을 선택해야 한다.

  • 불필요한 리렌더링
    Context를 사용하면 데이터 변경 시 해당 Context를 사용하는 모든 컴포넌트가 리렌더링된다는 단점이 있다. 따라서 불필요한 리렌더링이 발생하지 않도록 추가적인 메모이제이션 훅을 사용하거나, 조금 더 글로벌하고 중앙 집중 트리에서 운영되는 추가적인 상태 관리 라이브러리를 사용해야한다.

  • 재사용성 감소
    Context.Provider를 입힌 순간 해당 컴포넌트는 항상 이 Context값과 같이 다녀야 한다는 점이 있기 때문에 react의 특성인 재사용성에 불편함을 준다.

profile
안녕하세요! 26살 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글