본문 바로가기
이론/Frontend

Context API 개념 잡기

by 유세지 2022. 11. 22.

리액트의 데이터 전달 방식

리액트는 컴포넌트 사이의 데이터를 전달할때 Props라는 이름의 속성을 사용합니다. 이 Props는 부모 컴포넌트에서 자식 컴포넌트 방향으로만 전달되는 특징이 있는데, 이 덕분에 리액트는 단방향 데이터 바인딩 구조를 형성할 수 있게 됩니다.

 

이러한 단방향 데이터 바인딩은 데이터의 흐름을 추적하기 쉽고 디버깅이 편하다는 장점이 있지만, 단점이 하나 있습니다. 바로 아래와 같은 경우입니다.

 

복잡해진 Props 전달 과정

 

 

부모 컴포넌트에 있는 데이터가 아래쪽에 있는 컴포넌트에서도 필요한 경우, 해당 데이터를 한 번에 전달해 줄 방법이 없고 그 사이에 있는 모든 자식 컴포넌트들을 거쳐서 전달해야합니다. 이런 데이터들이 많아질수록 컴포넌트의 인자들은 각종 Props들로 자연스럽게 두툼해지기 마련이죠.

 

이렇게 많아진 Props들은 보기에도 복잡하고 컴포넌트 관리에도 어려움을 주게 되었습니다. 그렇다면 중간중간 Props를 전달하지 않고도 데이터를 받고, 업데이트 할 수 있다면 이러한 복잡성이 많이 해소되지 않을까요? 위 예시 그림에서 ParentComponent에서 Props를 통해 전달하는게 아닌, 데이터가 필요한 각 컴포넌트에서 어떠한 공통된 출처로부터 데이터를 받아올 수 있는 구조라면 가능하지 않을까요?

 

 

이러한 구조라면 어떨까요?

 

 

리액트는 이러한 개념으로 문제를 접근하여 해결할 수 있는 방법을 공식적으로 제공하고 있는데, 바로 Context API 입니다.

 

 

Context API

위의 예시 그림을 통해 Context API의 주요 개념들을 알아보겠습니다.

 

각 컴포넌트에서 참조해야 할 대상이 되는 데이터(Data source)는 Context라는 이름으로 취급합니다. 각 관심사에 맞는 특정한 데이터를 Context라는 이름의 저장소에 넣고, 필요할때 꺼내어 사용하는 방식입니다.

 

Data Source는 리액트에서 Context가 됩니다.

 

 

Context를 이용하기 위해선 먼저 공급자(Provider)를 선언해주어야 합니다. 이를 Provider라고 하고, 아래처럼 사용합니다.

 

const MyContext = createContext(null);

const defaultValue = 'something';

function MyApp() {
  return (
    <MyContext.Provider value={defaultValue}>
      <ParentComponent />
    </MyContext.Provider>
  )
}

 

코드를 보시면 Provider가 ParentComponent를 감싸주고 있는 형태입니다. Context를 사용하더라도 리액트는 항상 단방향 데이터 바인딩을 해주고 있기 때문에 사실 최상단 컴포넌트에서 State를 선언하고 있는것과 같다고 생각하셔도 무방합니다. 따라서 당연하지만 Provider가 감싸지 못한 외부에서 해당 값에 대한 접근은 불가능합니다.

 

const MyContext = createContext(null);

const defaultValue = 'something';

function RootComponent() {
  return (
    <ParentComponent>
      // MyContext에 접근 불가능
      <MyContext.Provider value={defaultValue}>
        // MyContext에 접근 가능
        <ChildrenComponent/>
      </MyContext.Provider>
    </ParentComponent>
  )
}

 

Context가 감싸고 있는 하위 컴포넌트에서만 접근할 수 있습니다.

 

 

 

이렇게 공급해 준 데이터들은 useContext를 이용하여 꺼내 사용할 수 있습니다.

 

const MyContext = createContext(null);

const defaultValue = 'something';

function MyApp() {
  return (
    <MyContext.Provider value={defaultValue}>
      <ChildrenComponent />
    </MyContext.Provider>
  )
}

function ChildrenComponent() {
  const myContextData = useContext(MyContext);
  
  return (
    <p>{myContextData}</p> // 'something'
  )
}

 

부모 컴포넌트에서 자식 컴포넌트로 Props를 넘겨주지 않고도, Provider에서 값을 공급받아 사용하는 모습입니다. 이런 방식을 적절히 사용하면 컴포넌트 사이의 복잡도를 낮춰주는 효과가 있습니다.

 

 

주의할 점

기존처럼 Props를 통해 값을 넘겨주지 않는다고 해서 데이터 흐름의 방향이 바뀌는 것은 아닙니다. 위에서 이야기했듯 useContext를 통해 값을 사용하고 있는 컴포넌트들은 최상단에 state가 선언된 것이라고 생각해보면, Provider의 상태가 변했을때 이를 구독하고 있는 컴포넌트의 경우 일제히 리렌더링이 일어납니다. 따라서 필요하다면 useMemo를 사용하여 불필요한 리렌더링을 막아주는 작업을 고려해 볼 수 있습니다.

 

또한 이런 전역 상태를 사용하게 되면 실제 상태의 위치가 비교적 덜 직관적으로 보이는 효과도 있는데, 개인적으로는 전역 상태가 해결해주는 복잡성의 감소가 훨씬 큰 장점이라고 생각합니다. 이 부분은 그렇게 느껴질 수도 있겠거니... 하고 넘어가주셔도 무방할 것 같네요.

 

다음에는 전역 상태를 관리하는 서드파티 라이브러리들에 대해 알아보겠습니다.

 

읽어주셔서 감사합니다.

반응형

'이론 > Frontend' 카테고리의 다른 글

리액트에서 기본 컴포넌트를 만들때  (1) 2022.12.22
Canvas API 사용해보기  (4) 2022.12.09
Recoil + React-query 삽질기  (6) 2022.10.28
자바스크립트의 index.js  (4) 2022.08.23
JSX 알아보기  (0) 2022.07.24

댓글