[React] Context 개념 및 특징

    Context


    Context란,

    기존의 일반적인 리액트 컴포넌트는, 컴포넌트의 props를 통해 데이터를 전달한다.

    하지만 이러한 전달 방식은 '부모 → 자식'일방향이기 때문에, 아래로 들어갈수록 코드가 복잡해진다.

    그러므로 여러 컴포넌트에 걸쳐 자주 사용되는 데이터의 경우, Context를 사용한다.

    기존 방식(props) 사용

     

     

    Context 사용

     

    언제 Context를 사용해야 할까?

    여러개의 컴포넌트들이 접근해야 하는 데이터

    로그인 여부, 로그인 정보, UI 테마, 현재 언어 등

     

     

    📌 prop를 통해 데이터를 전달하는 경우 ( 기존 방식 )

    function App(props){
      return <Toolbar theme="dark" />
    }
    
    function Toolbar(props) {
      // 이 Toolbar 컴포넌트는 ThemeButton에 theme를 넘겨주기 위해서 'theme' prop을 가져야만 합니다.
      // 현재 테마를 알아야 하는 모든 버튼에 대해서 props로 전달하는 것은 굉장히 비효율적입니다.  
      return(
        <div>
            <ThemedButton theme={props.theme} />    
        </div>
      );
    }
    
    function ThemedButton(props) {
      return <Button theme={props.theme} />;
    }

    props를 통해서 데이터를 전달하는 기존 방식은,

    • 실제 데이터를 필요로 하는 컴포넌트의 깊이가 깊어질수록 복잡해진다.
    •  반복적인 코드를 계속해서 작성해야 하기 때문에 비효율적이고 직관적이지 않다.

     

    📌 context를 통해 데이터를 전달하는 경우

    // context를 사용하면 모든 컴포넌트를 일일이 통하지 않고도
    // 원하는 값을 컴포넌트 트리 깊숙한 곳까지 보낼 수 있습니다.
    // light를 기본값으로 하는 테마 context를 만들어 봅시다.
    const ThemeContext = React.createContext('light');
    
    function App(props) {
        // Provider를 이용해 하위 트리에 테마 값을 보내줍니다.
        // 아무리 깊숙히 있어도, 모든 하위 컴포넌트가 이 값을 읽을 수 있습니다.
        // 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다.
        return (
          <ThemeContext.Provider value="dark">
            <Toolbar />
          </ThemeContext.Provider>
        )
    }
    
    // 이젠 중간에 있는 컴포넌트가 일일이 테마를 넘겨줄 필요가 없습니다.
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    
    function ThemedButton (props) {
      // React는 가장 가까운 상위 테마 Provider를 찾아 그 값을 사용할 것입니다.
      // 만약 해당되는 Provider가 없을 경우, 기본값("light")를 사용합니다.
      // 이 예시에서 현재 선택된 테마는 dark입니다.
      return(
        <ThemeContext.Consumer>
          {value=> <Button theme={value} />}
        </ThemeContext.Consumer>
      )
    }

    React.createContext를 사용해 context 생성한다.

     

    그 다음, context를 사용할 컴포넌트의 상위 컴포넌트에서 Provider로 감싸준다.

    여기서는 최상위 컴포넌트인 App을 ThemeContext.Provider로 감싸주었다.

    이렇게 하면, Provider의 모든 하위 컴포넌트가 context의 데이터를 읽을 수 있게 된다.

     

    ThemeContext.Consumercontext 변화를 구독하는 React 컴포넌트로,

    이 컴포넌트를 사용하면 함수 컴포넌트 안에서 context를 구독할 수 있다.

    <MyContext.Consumer>
      {value => /* context 값을 이용한 렌더링 */}
    </MyContext.Consumer>

     

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

    context와 컴포넌트가 연동되면 재사용성이 떨어진다.

    그러므로 다른 레벨의 많은 컴포넌트가 데이터를 필요로 하는 게 아니라면,

    기존에 사용하던 방식대로 props를 통해 데이터를 전달하는 게 낫다. ( Component Composition )

     

    📌 예제1-1. props로 데이터 전달

    // Page컴포넌트는 PageLayout컴포넌트를 렌더링
    <Page user={user} avatarSize={avatarSize} />
    
    // PageLayout컴포넌트는 NavigationBar컴포넌트를 렌더링
    <PageLayout user={user} avatarSize={avatarSize} />
    
    // NavigationBar컴포넌트는 Link컴포넌트를 렌더링
    <NavigationBar user={user} avatarSize={avatarSize} />
    
    // Link컴포넌트는 Avatar컴포넌트를 렌더링
    <Link href={user.permalink}>
      <Avatar user={user} size={avatarSize} />
    </Link>

    이러한 경우, Avatar 컴포넌트를 변수에 저장하여 직접 넘겨준다. ( Element Variable )

    이렇게 되면 중간 단계에 있는 컴포넌트들은 user와 avatarSize에 대해 몰라도 된다.

    수정된 코드는 아래와 같다.

     

     

    📌 예제1-2. 예제1-1 수정 ( Element Variable )

    function Page(props) {
      const user = props.user;
      const userLink = (
        <Link href={user.permalink}>
          <Avatar user={user} size={props.avatarSize} />
        </Link>
      );
      // Page 컴포넌트는 PageLayout 컴포넌트를 렌더링
      // 이때, props로 userLink를 함께 전달함.
      return <PageLayout userLink={userLink} />;
    }
    
    //PageLayout 컴포넌트는 NavigationBar 컴포넌트를 렌더링
    <PageLayout userLink={...} />
    
    // NavigationBar 컴포넌트는 props로 전달 받은 userLink element를 리턴
    <NavigationBar userLink={...} />

    가장 상위 컴포넌트인 Page만 user와 avatar size에 대해 알고 있으면 된다.

    하지만 이러한 방식 또한 데이터가 많을 수록 최상위 컴포넌트가 복잡해진다는 단점이 있다.

    그러면, 아래와 같이 하위 컴포넌트를 여러 개의 변수로 나눠서 전달한다.

     

     

    📌 예제2.

    function Page(props) {
      const user = props.user;
      const content = <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를 사용하여 렌더링 전부터 상위 컴포넌트가 하위 컴포넌트와 통신하게 할 수 있다.

    하지만 같은 데이터를 트리 안 다양한 레벨의 컴포넌트에 주어야 할 때에는 context를 사용한다. ( broadcast )

    일반적으로 context를 사용하는 데이터는 현재 지역정보, 캐싱된 데이터, UI 테마 등이 있다.

     



    🚀 참고

    댓글