Jun 개발노트

Context

2020-09-03

정의

  • React 컴포넌트 트리 안에서 데이터를 쉽게 공유하는 방법중 하나이다.

  • 컴포넌트가 깊어지면 props를 내리는데 리소스 증가에 따라 사용되는 방법 중 하나

    // context 사용전
    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 사용 후
    const ThemeContext = React.createContext('light');
    
    class App extends React.Component {
      render() {
        return (
          <ThemeContext.Provider value="dark">
            <Toolbar />
          </ThemeContext.Provider>
        );
      }
    }
    
    // props 과정이 사라짐
    function Toolbar() {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    
    // 컨텍스트를 활용하여 value 접근
    class ThemedButton extends React.Component { 
    	static contextType = ThemeContext;
      render() {
        return <Button theme={this.context} />;
      }
    }
    

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

    1. Context에 의존성이 높아지므로 재사용이 어려워짐.
    • context보다는 컴포넌트 합성으로 문제점 해결(props.children)
    1. IOC(제어의 역전)를 이용하여 내리는 props 줄일 수 있으므로, props를 여러개 내리지 않아도 된다.
    • 장점 : props는 수는 줄고 최상위 컴포넌트의 제어력이 커지기 떄문에 클린코드 작성 가능
    • 단점 : 복잡한 로직이 상위에 있기 때문에, 상위컴포넌트는 복잡성이 증대되고, 하위 컴포넌트는 필요이상으로 유연해져야 한다.
    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}
    
    1. Context가 변경이되면, 모든 하위 컴포넌트가 리렌더링이 된다.

    • 한번 변경이 되면 향후 변경되지 않은 데이터를 Context로 활용한다(테마, 데이터캐쉬)

    API

    React.createContext

    const MyContext = React.createContext(dafaultValue);
    
    export const ThemeContext = React.createContext({
      theme: themes.dark,
      toggleTheme: () => {},
    });
    
    • Context 객체 생성하며, dafaultValue는 값이 없을때 사용되며, 객체( = 값)에 값과 메소드를 넣어 사용할수도 있다.

    Context.Privider

    <MyContext.Provider value={}>
    
    • Context를 통해 전달될 값을 넣어주며 Provider 하위 컴포넌트에게 value가 변경될때마다 전파한다.
    • value가 변하면 props가 변하기 때문에 불필요한 렌더링이 발생한다(shouldComponentUpdate 미적용)
      • Object.is를 통해서 Context의 값 변경 여부를 체크한다(true/false)

    Class.contextType

    class MyClass extends React.Component {
      componentDidMount() {
        let value = this.context;
      }
      componentDidUpdate() {
        let value = this.context;
      }
      componentWillUnmount() {
        let value = this.context;
      }
      render() {
        let value = this.context;
      }
    }
    MyClass.contextType = MyContext;
    
    • contextType을 통해 class내에서 사용하는 Context를 지정할 수 있다

    • 모든 컴포넌트의 생명주기 메서드에서 사용가능하다.

      class MyClass extends React.Component {
        static contextType = MyContext;
        render() {
          let value = this.context;
          /* context 값을 이용한 렌더링 */
        }
      }
      
    • public class field 문법으로 contextType을 지정할 수 도 있다.

    Context.Consumer

    <MyContext.Consumer>
      {value => /* context 값을 이용한 렌더링 */}
    </MyContext.Consumer>
    
    • Provider의 하위 트리에서 사용할 수 있는 Cosumer 컴포넌트입니다.
    • Consumer 컴포넌트의 children은 함수형으로 작성되면 props는 해당 Context의 value값이다.
    • value값이 없다면, 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의 value값이 reference일때!!

  • value 값이 객체이면 매번 새로운 객체가 생성되므로 Provider가 렌더링될때마다 리렌더링 된다
  • state를 객체로 생성하고 객체를 가리키고 있는 포인터를 넘기자
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

Hook - useContext

  • 함수형 컴포넌트에서 Context를 사용할때 사용
  • 가장 가까이 있는 Context.Provider의 value를 리턴한다.
  • React.memo, shouldComponentUpdate를 사용해도 최적화가 안되며 리렌더링이 된다
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

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

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

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

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}