늘 겸손하게

React - Context API 본문

Programming/React

React - Context API

besforyou999 2022. 8. 2. 14:48

Context API

 

일반적으로 React 데이터는 부모로부터 자식으로 props를 통해 전달됩니다. 하지만 여러 자식 컴포넌트 혹은 깊이가 깊은 트리에서 자식 컴포넌트에 데이터를 전달해야하는 경우에는 이 과정이 번거로울 수 있습니다. Context API는 모든 자식 컴포넌트에 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다. 

 

다른 프로그래밍 언어에서 전역변수와 비슷한 역할입니다.

 


언제 context를 써야 할까?

 

context는 컴포넌트 트리에서 전역적인 데이터를 공유할 수 있는 방법입니다. 그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어등이 있습니다.

 

예로 아래의 코드는 버튼 컴포넌트를 꾸미기 위해 테마 props를 명시적으로 넘겨주고 있습니다.

 

 

// 최상위 컴포넌트
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

// 중간 컴포넌트
function Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

// 버튼 컴포넌트
class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

 

 

버튼 컴포넌트에 테마 props를 전달하기 위해 App 컴포넌트 -> Toolbar 컴포넌트 -> ThemedButton 버튼 컴포넌트를 거치고 있습니다. 컴포넌트 트리가 깊어지면 깊어질수록 props를 명시해야하는 컴포넌트가 늘어날 가능성이 있습니다.

 

context를 이용하여 여러 컴포넌트를 거쳐 props로 데이터를 필요가 없어집니다

 

 

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

Context를 사용하기 전에 고려할 것

 

context를 사용하면 컴포넌트는 context 데이터에 의존하게되어 재사용하기 어려워집니다. 꼭 필요한 경우에만 쓰는것이 좋다고합니다.

여러 세대를 거쳐 props를 넘기는 걸 대체하는 일은 컴포넌트 합성이 더 간단한 해결책을 수도 있습니다.

 

컴포넌트 합성 예

 

예로 여러 컴포넌트 아래에 있는 Link와 Avatar 컴포넌트에게 user와 avatarSize라는 props를 전달해야 하는 Page 컴포넌트가 있다고 가정합니다.

 

<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

 

 

실제로 props가 사용되는 컴포넌트는 Avatar뿐이지만 user와 avatarSize props를 Page -> PageLayout -> NavigationBar -> Link -> Avatar 순으로 여러 컴포넌트를 거쳐 보내줘야하는 번거로움이 있습니다. 게다가 Avatar 컴포넌트에 또 다른 props를 전달해야한다면 중간에 존재하는 모든 컴포넌트에 props를 추가해야합니다.

 

Avatar 컴포넌트 자체를 넘겨주면 context를 사용하지 않고 props를 여러 레벨을 거쳐 전달하는 일을 해결할 수 있습니다. 그러면 중간에 있는 컴포넌트들이 user나 avatarSize에 대해 전혀 알 필요가 없습니다.

 

 

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// 이제 이렇게 쓸 수 있습니다.
<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout userLink={...} />
// ... 그 아래에 ...
<NavigationBar userLink={...} />
// ... 그 아래에 ...
{props.userLink}

 

 

위와 같은 방식을 제어의 역전(inverstion of control)라고 합니다. 이 방식은 명시해야하는 props는 줄고 최상위 컴포넌트의 제어력은 커지기 때문에 코드가 깔끔해지는 장점이있지만 항상 좋은 방식이라고 할 수는 없습니다. 복잡한 로직을 상위 컴포넌트로 옮긴다면 상위 컴포넌트는 난해해지고 하위 컴포넌트는 필요 이상으로 유연해져야한다고 합니다.

 

 

그러면 언제 context를 사용할까요?

 

같은 데이터를 트리 안에 있는 깊이가 다양하고 많은 컴포넌트에게 주어야할때, 데이터값이 변하면 많은 컴포넌트에게 "방송"해야할때 context를 사용하기 좋습니다. 흔한 예시로 선호 로케일, 테마, 데이터 캐시 등을 관리할때 context를 사용합니다.

 

 

로케일 : 사용자의 언어, 국가, 사용자 인터페이스에서 사용자가 선호하는 사항을 지정한 매개 변수의 모임


API

 

React.createContext

 

const MyContext = React.createContext(defaultValue);

 

Context 객체 생성자. Context 객체를 구독하고(의지하고) 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이맞는 Provider로부터 현재값을 읽습니다.

 

매개변수로 주어지는 defaultValue는 트리 내부에서 context가 생성된 상위에 매칭되는 Provider가 없는 경우 사용됩니다. 

 

defaultValue는 고립된 컴포넌트를 테스트하는데 도움이됩니다.

 

주의 : Provider value로 undefined를 전달해도 context를 소비하는 컴포넌트가 defaultValue를 사용하게 하지는 않습니다

 

 

 

Context.Provider

 

<MyContext.Provider value={/* some value */}>

 

 

Context 객체에 포함된 React 컴포넌트인 Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을합니다.

 

 

Provider 컴포넌트는 value props을 받아서 하위에 있는 컴포넌트에게 전달합니다. 이 값을 전달받을 수 있는 컴포넌트 수에 제한은 없습니다 Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이러한 경우 하위 Provider의 value가 우선됩니다.

 

Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop이 바뀔 때마다 리렌더링됩니다.

 

Provider로부터 하위 consumer(context 구독중인 컴포넌트)로의 데이터 전파는 shouldComponentUpdate 메소드가 적용되지 않아 상위 컴포넌트가 업데이트를 건너 뛰더라도 consumer가 업데이트됩니다.

 

 

consumer : context를 구독중인 컴포넌트

 

 

 

 

 

 

출처 

https://ko.reactjs.org/docs/context.html

 

 

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

React - Prop Drilling & Context API v.s Redux  (0) 2022.08.17
Redux - Redux 장단점  (0) 2022.08.17
React - useLayoutEffect vs. useEffect  (0) 2022.08.01
React - React 18 새로운 점  (0) 2022.08.01
React - React-Query  (0) 2022.07.30