TL;DR


리액트에서 제공하는 ContextAPI를 사용하면 원하는 상태(값)를 전역적으로 사용할 수 있다. 컴포넌트끼리 특정 상태를 공유할 때도 유용하다. 하지만 Context.Provider 하위에서 컨텍스트를 구독하는 모든 컴포넌트는 값을 변경할 때마다 리렌더링 된다. Context.Provider 값이 업데이트되면 useContext 훅이 최신 컨텍스트 값을 사용해 컴포넌트 리렌더를 트리거하기 때문이다.

이처럼 ContextAPI는 렌더링 최적화에 일일이 신경써야하는 단점이 있다. 여러 최적화 방법중에서 상태/디스패치 단위로 컨텍스트를 쪼개는 방식이 가장 간단하다. 대신 Provider가 너무 많아지는 단점(Wrapper Hell)이 있다.

리렌더링 발생 상황


FamilyName 폼만 변경했는데 FirstName 폼도 같이 리렌더링되고 있다

FamilyName 폼만 변경했는데 FirstName 폼도 같이 리렌더링되고 있다

FamilyName, FirstName 2개의 input이 있고, 컨텍스트 역시 2개를 만들어서 각 input의 상태로 사용하고 있다. 이렇게 form 데이터를 컴포넌트 상태로 두는 방식을 controlled form라고 한다(반대로 value 변경을 구독하지 않고 필요할 때만 pull해서 사용하는 방식을 uncontrolled라고 한다).

FamilyName, FirstName 2개의 컨텍스트를 만들었지만, 둘 중 1개 컨텍스트의 상태가 변경되면 Provider 안쪽에서 상태를 구독하는 모든 컴포넌트가 리렌더링 된다.



import { factoryUseContext } from './factoryUseContext';

const FamilyNameContext = createContext(null);
const FirstNameContext = createContext(null);

const useFamilyNameCtx = factoryUseContext(FamilyNameContext);
const useFirstNameCtx = factoryUseContext(FirstNameContext);

const Provider = ({ children }) => {
  const [familyName, setFamilyName] = useState(''); // familyName input에 사용
  const [firstName, setFirstName] = useState(''); // firstName input에 사용

  return (
    <FamilyNameContext.Provider value={[familyName, setFamilyName]}>
      <FirstNameContext.Provider value={[firstName, setFirstName]}>
        {children}
      </FirstNameContext.Provider>
    </FamilyNameContext.Provider>
  );
};