ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] Context Api에 대해서
    Frontend 2020. 2. 9. 16:42

    오늘은 React의 Context에 대해서 공부해보려고 한다. 이 블로그 포스트는 React documents의 Context 챕터를 번역하고 예시를 따라해 본 글이니 굳이 내 블로그 글을 안보고 아래 링크를 봐도 된다.

     

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

     

    Context – React

    A JavaScript library for building user interfaces

    reactjs.org

     

    Summary

    Context는 모든 레벨의 컴포넌트 트리에 props를 통하지 않아도 데이터를 전달할 수 있는 방법을 제공해준다.

     

    대부분의 리액트 어플리케이션은 데이터를 위에서부터 아래로 (부모에서 자식까지) props를 통해 전달한다. 그리고 이것을 top-down 방식이라고 부른다. 그러나 특정 경우에 이러한 방식은 매우 성가신 일이 된다. 만약 원하는 컴포넌트 구조가 단순하다면 1~2개의 컴포넌트를 거쳐 쉽게 원하는 데이터를 하위 컴포넌트에 전달할 수 있다. 그러나 만약에 컴포넌트 구조가 매우 복잡해진다면 엄청 많은 컴포넌트를 거쳐야지만 원하는 컴포넌트에 데이터 전달이 가능해진다. Context는 바로 이러한 불편함을 해소해주는 역할을 하는데, 컴포넌트 트리의 모든 레벨을 prop으로 거치지 않아도 원하는 데이터를 컴포넌트들에 공유할 수 있게 해준다.

     

     

    Example

    만약에 App 컴포넌트 하위에 Header 컴포넌트, 그리고 Header 컴포넌트 하위에 Button 컴포넌트가 있다고 생각해보자.

    그리고 맨 하위 Button 컴포넌트의 button 색깔을 상위 컴포넌트의 prop으로 받는다고 했을 때 아래와 같은 구조가 될 것이다.

     

     

    * Button 컴포넌트

    import React from 'react';
    
    function Button(props) {
      return (
        <button
          type="button"
          style={{
            backgroundColor: props.btnColor
          }}
        >
          Hello World!
        </button>
      );
    }
    
    export default Button;

     

    * Header 컴포넌트

    import React from 'react';
    import Button from './Button';
    
    function Header(props) {
      return (
        <header>
          <h1>Title</h1>
          <Button btnColor={props.btnColor} />
        </header>
      )
    }
    
    export default Header;

     

    * App 컴포넌트

    import React from 'react';
    import Header from './Header';
    
    function App() {
      return (
        <div className="App">
          <Header btnColor="yellow" />
        </div>
      );
    }
    
    export default App;
    

     

     

    이제 여기에 Context를 적용해서 App -> Header -> Button으로 prop을 내려주지 않아도 Button 컴포넌트에서 값을 꺼내쓸 수 있도록 해보자.

     

    import React from 'react';
    
    export const BtnColorContext = React.createContext('red');	// default value: 'red'
    

     

    먼저 context.js라는 파일을 만들어서 React.createContext()를 사용하여 Context object를 생성하여 export한다.

    이 메소드에 인자로 넘기는 값이 default 값이 된다.

     

    React가 해당 Context object를 구독하고 있는 컴포넌트를 render할 때

    Context value를 가장 가까운 Provider에서 찾게 되는데 Provider가 존재하지 않는 경우에만 default 값이 적용된다.

     

     

    import React from 'react';
    import Header from './Header';
    import { BtnColorContext } from './context';
    
    function App() {
      return (
        <BtnColorContext.Provider value="yellow">
          <div className="App">
            <Header />
          </div>
        </BtnColorContext.Provider>
      );
    }
    
    export default App;
    

     

    이제 상위 컴포넌트인 App 컴포넌트에서 Context object를 import한 후 Context object의 Provider로 래핑해준다.

     

    위 예시에서는 Provider에서 yellow라는 value를 지정했으므로 버튼 색깔은 yellow가 될 것이다.

    만약에 Provider를 아무데서도 사용하지 않았다면 (즉, 저 Provider 코드를 지우면) default value인 red가 적용된다.

     

     

    import React from 'react';
    import Button from './Button';
    
    function Header() {
      return (
        <header>
          <h1>Title</h1>
          <Button />
        </header>
      )
    }
    
    export default Header;
    

     

    이제 Header 컴포넌트에서 prop을 전달할 필요가 없으므로 관련 코드를 지워준다.

     

     

    import React, { useContext } from 'react';
    import { BtnColorContext } from './context';
    
    function Button(props) {
      const btnColor = useContext(BtnColorContext);
    
      return (
        <button
          type="button"
          style={{
            backgroundColor: btnColor
          }}
        >
          Hello World!
        </button>
      );
    }
    
    export default Button;
    

     

    이제 하위 컴포넌트에서 App 컴포넌트에서 생성한 Context object를 import한 후,

    useContext()를 사용하여 value값을 가지고 올 수 있다.

     

    이렇게 React Hooks를 사용하면 아주 간단하게 context를 사용할 수 있다.

    그러나 만약에 class 컴포넌트에서 사용하고 싶다면 어떻게 해야할까?

     

    Context value를 retrieve하는 가장 전통적인 방법은 자식 컴포넌트를 Consumer로 wrapping하는 것이다.

     

     

    import React from 'react';
    import { BtnColorContext } from './context';
    
    class Button extends React.Component {
      render() {
        return (
          <BtnColorContext.Consumer>
            {btnColor => (
              <button
                type="button"
                style={{
                  backgroundColor: btnColor
                }}
              >
                Hello World!
              </button>
            )}
          </BtnColorContext.Consumer>
        );
      }
    }
    
    export default Button;
    

     

    이 경우에는 마치 prop을 받아오는 것처럼 위와 같이 사용할 수 있다.

     

    그런데 만약에 render 메소드 바깥에서 Context value값을 가져오고 싶다면 어떻게 해야할까?

    이 때 사용하는 것이 contextType이라는 class의 static variable이다.

     

     

    import React from 'react';
    import { BtnColorContext } from './context';
    
    class Button extends React.Component {
      static contextType = BtnColorContext;
    
      componentDidMount() {
        const btnColor = this.context;
        console.log(btnColor);  // 'yellow'
      }
    
      render() {
        return (
          <button
            type="button"
            style={{
              backgroundColor: this.context
            }}
          >
            Hello World!
          </button>
        );
      }
    }
    
    export default Button;
    

     

    contextType으로 원하는 context object를 지정해주면

    lifecycle 메소드 내에서 자유롭게 this.context를 통해서 value에 접근할 수 있다.

     

     

    React.createContext

    const MyContext = React.createContext(defaultValue);

     

    createContext Api는 Context object를 생성한다.

     

    React가 Context object를 구독하는 컴포넌트를 렌더할때

    컴포넌트 트리 상에서 가장 가까운 Provider의 context value값을 읽는다.

     

    defaultValue는 오직(!) 컴포넌트가 트리 상에서 어떠한 Provider와도 매칭되지 않을때 사용된다.

    (즉, Provider의 value로 undefined를 넘겨준다고 해서 defaultValue가 사용되는 것이 아니다.)

     

     

    Context.Provider

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

     

    모든 Context object는 Context 변화를 구독하기 위해 Provider React Component와 함께 사용된다.

    하나의 Provider에 여러 개의 Consumer 컴포넌트를 연결할 수 있다.

     

    Providers는 트리 상에서 value값을 override하기 위해 nested될 수 있다.

     

    모든 Consumer들은 Provider의 자손들로 Provider의 value 값이 변할 때마다 re-render된다.

    이 때의 Provider에서 자손으로의 전파(.contextType과 useContext를 포함해서)는

    shouldComponentUpdate 메소드와는 상관없이 발생한다.

     

    즉, 조상 컴포넌트에서 update를 건너뛰더라도 Consumer는 새로 업데이트된다.

     

     

    Class.contextType

    class MyClass extends React.Component {
      static contextType = MyContext;
      
      componentDidMount() {
        let value = this.context;
        /* perform a side-effect at mount using the value of MyContext */
      }
      componentDidUpdate() {
        let value = this.context;
        /* ... */
      }
      componentWillUnmount() {
        let value = this.context;
        /* ... */
      }
      render() {
        let value = this.context;
        /* render something based on the value of MyContext */
      }
    }
    MyClass.contextType = MyContext;

     

    클래스의 contextType 속성은 React.createContext()로 생성된 Context object에만 할당된다.

    이 속성은 this.context를 통해 해당 Context의 가장 가까운 value를 가져올 수 있도록 해준다.

    render 메소드를 포함한 모든 lifecycle 메소드 내에서 사용 가능하다.

     

    이 Api를 이용하면 오직 하나의 Context만 구독 가능하다.

    여러 개의 Context를 구독하려면 아래의 방법을 사용해야 한다.

    function Content() {
      return (
        <ThemeContext.Consumer>
          {theme => (
            <UserContext.Consumer>
              {user => (
                <ProfilePage user={user} theme={theme} />
              )}
            </UserContext.Consumer>
          )}
        </ThemeContext.Consumer>
      );
    }

     

     

    Context.Consumer

    <MyContext.Consumer>
      {value => /* render something based on the context value */}
    </MyContext.Consumer>

     

    Consumer는 context 변화를 감지, 구독하는 React component이다.

    Consumer는 자식으로 함수를 필요로하며, 이 함수는 현재 context value를 매개변수로 받고 React node를 리턴한다.

    컴포넌트 트리 상에서 가장 가까운 Provider의 value를 받으며, 만약 가까운 Provider가 존재하지 않으면 createContext로 전달받은 defaultValue를 받는다.

     

     

    Context.displayName

    const MyContext = React.createContext(/* some value */);
    MyContext.displayName = 'MyDisplayName';
    
    <MyContext.Provider> // "MyDisplayName.Provider" in DevTools
    <MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

     

    Context object는 displayName string 속성을 받는다.

    React DevTools는 이 string을 사용하여 어떤 context를 display 상에서 보여줄지 결정한다.

    예를 들어 위 예시의 Component는 MyDisplayName이라는 이름으로 DevTools에 보여질 것이다.

    반응형

    COMMENT