React - Context

이소라·2022년 8월 9일
0

React

목록 보기
6/23

Context

  • 일반적으로 React 애플리케이션에서 데이터는 위에서 아래로 props를 통해 전달됨
  • 애플리케이션 안의 여러 컴포넌트들에 props를 전달해주는 경우, context를 이용하면 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 데이터를 공유할 수 있음

context의 용도

  • React 컴포넌트 트리 안에서 전역적으로 사용하는 데이터를 공유할 때 context를 사용함
    • 예) 현재 로그인한 유저 정보, 테마, 선호하는 언어 등
  • context에서 공유하는 데이터의 값이 변할 때마다 context는 모든 하위 컴포넌트에게 널리 방송
// before
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} />;
  }
}
  • context를 사용하면 컴포넌트 트리의 중간에 있는 엘리먼트들에게 props를 넘겨주지 않아도 됨
// after
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider>
        <Toolbar theme="dark" />
      </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를 넘기는 것을 대체하는 데는 context보다 컴포넌트 합성이 더 좋은 방법임
// before
<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>
  • 위 코드는 여러 단계 아래에 있는 Link와 Avatar 컴포넌트에 user와 avatarSize props를 전달해야 하는 Page 컴포넌트를 보여줌
    • 실제 user와 avatarSize props가 사용되는 것은 Link와 Avatar 컴포넌트 뿐인데 여러 단계를 거쳐서 보내줘야 한다는 것이 번거로움
      • 중간에 있는 컴포넌트들은 user나 avartarSize에 대해 알 필요 없음
    • context를 사용하지 않고 Link와 Avartar 컴포넌트 자체를 넘겨줘서 해결할 수 있음
// after
function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avartar user={user} size={user.avartarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout userLink={...} />
// ... 그 아래에 ...
<NavigationBar userLink={...} />
// ... 그 아래에 ...
{props.userLink}
  • 이렇게 바꾸면 Linkdhk Avartar 컴포넌트가 ink와 AvatarSize를 쓴다는 것을 알고 있는 것은 page 컴포넌트뿐임 : 제어의 역전(inverse of control)

    • 장점 : 넘겨줘야 하는 props 수는 줄고, 최상위 컴포넌트의 제어력이 더 커지기 때문에 더 깔끔한 코드를 작성 가능함
    • 단점 : 복잡한 로직을 상위로 옮기면 상위 컴포넌트들이 난해해지고, 하위 컴포넌트들은 필요 이상으로 유연해야함
  • 자식으로 둘 수 있는 컴포넌트의 개수는 제한 없음

function Page(props) {
  const user = props.user;
  const context = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}
  • 이 패턴은 자식 컴포넌트와 그 바로 위 부모 컴포넌트를 분리할 때 사용함
  • render props를 이용하면 렌더링 되기 전부터 자식 컴포넌트가 부모 컴포넌트와 소통하게 할 수 있음



API

React.createContext

const MyContext = React.createContext(defaultValue);
  • Context 객체를 생성하는 API

    • Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때, React는 트리 안에서 컴포넌트 상위의 가장 가까운 Provider로부터 현재 context 값을 읽음
  • defaultValue 매개변수 : 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값

    • Provider의 값으로 undefined를 전달한다고 해도 구독 컴포넌트들이 defaultValue를 사용하지 않음

Context.Provider

<MyContext.Provider value={/* 어떤 값 */}>
  • Context 객체마다 Provider 컴포넌트가 함께 함

  • Provider 컴포넌트는 구독 컴포넌트에 context의 변화를 알리는 역할을 함

  • Provider는 value props를 받아서 이 값을 하위에 있는 구독 컴포넌트에게 전달함

  • Provider은 여러 구독 컴포넌트와 연결될 수 있음

  • Provider 하위에 또 다른 Provider를 배치할 수 있음

    • 이 경우 하위 Provider의 값이 우선시됨
  • Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value props가 바뀔 때마다 리렌더링됨

  • Provider로부터 하위 consumer로의 전파는 shouldComponentUpdate 메서드가 적용되지 않으므로, 상위 컴포넌트가 업데이트를 건더 뛰더라도 consumer가 업데이트됨

    • consumer : .contextTypeuseContext를 포함한 컴포넌트
    • shouldComponentUpdate : props 또는 state를 변경했을 때 리렌더링을 시작할지 여부를 지정하는 메서드
      • 기본값은 true이고 False를 반환하면 React가 갱신 작업을 건너뜀
      • false를 반환해도 자식 컴포넌트들이 각자가 가진 state의 변화에 따른 리렌더링을 막지 않음
  • constext 값이 바뀌었는지의 여부는 Object.is와 동일한 알고리즘을 사용하여 이전 값과 새로운 값을 비교해서 측정함

    • 이러한 방식으로 변화를 측정하기 때문에 객체를 value로 보내는 경우 문제가 생길 수 있음
class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    )
  }
}
  • 위 코드에서 value가 바뀔 때마다 매번 새로운 객체가 생성되므로 Provider가 렌더링될 때마다 하위에서 구독하고 있는 모든 컴포넌트가 리렌더링됨
    • 값을 부모 컴포넌트의 state로 끌어올려서 이 문제를 피할 수 있음
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
    value: {something: 'something'},
    };
  }
  
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    )
  }
}

리렌더링 여부를 정할 때 참조(reference)를 확인하기 때문에, Provider의 부모 컴포넌트가 렌더링될 때마다 불필요하게 하위 컴포넌트가 리렌더링되는 문제가 발생할 수 있음

0개의 댓글