Context

js·2022년 6월 15일
0
post-thumbnail

어마무시한 contex 관련글

https://velog.io/@velopert/react-context-tutorial

언제 사용하는가?

  • 전역적인 상태관리가 필요할 때

  • 예를 들어 로그인한 유저 정보, 테마, language, loacale 정보 등이 있다

Context 사용 전 고려사항

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 객체의 Providervalue prop으로 넘겨주는 값은 createContext()의 기본값 보다 우선한다.

// defaultValue: 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 매개변수
const MyContext = React.createContext(defaultValue);

2. Provider로 컴포넌트들 감싸기

  • value prop을 모든 하위 컴포넌트에게 전달합니다.

  • Provider 하위에 또 Provider를 배치 할 수 있으며, 이 때 하위 Provider의 value가 우선된다.

  • Provider 하위의 모든 컴포넌트들은 value prop이 바뀔 때, 리렌더링 됩니다.

  • context 값의 바뀌었는지 여부는 Object.is와 동일한 알고리즘을 사용해 이전 값과 새로운 값을 비교해 측정됩니다.

<MyContext.Provider value={/* 어떤 값 */}>

Class.contextType

Context 객체를 클래스의 contextType 프로퍼티로 지정할 수 있습니다.

이 프로퍼티는 클래스 안에서 this.context로 읽을 수 있는데 그 값은 해당

Context의 가장 가까운 Provider를 찾아서 읽습니다. (= 가장 하위의 Provider)

this.context는 모든 생명주기 메서드에서 사용이 가능합니다.

class MyClass extends React.Component {
  //static contextType = MyContext; // 정적 프로퍼티로 지정할 수도 있다
  componentDidMount() {
    let value = this.context;
    /* MyContext의 값을 이용한 코드 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* ... */
  }
}
MyClass.contextType = MyContext;

Context.Consumer

  • Consumer로 함수 컴포넌트 안에서도 Context를 구독할 수 있다.

  • value 매개변수는 가장 하위의 Providervalue prop과 같다.

  • 만약, Provider가 없다면 value 매개변수는 createContext()에서 선언할 때 넣은 기본값과 같다

<MyContext.Consumer>
  /* 이 부분은 함수여야 한다 */
  {value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>

Context.displayName

displayName으로 React Developer Tools에서 식별되는 Context의 이름을 지정할 수 있다.

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

하위 컴포넌트에서 context 업데이트하기

하위 컴포넌트에서 context를 업데이트 하려면 createContext()에서 기본값의

프로퍼티로 업데이트 메소드를 설정해주자

export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {}, // 업데이트 메소드
});

하위 컴포넌트

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // state에 업데이트 메서드도 포함되어있으므로
    // 이 또한 context Provider를 통해 전달될것입니다.
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // Provider에 state 전체를 넘겨줍니다.
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);

여러 context 구독하기

각각의 Provider가 제공하는 value prop을

각각의 Consumer가 매개변수로 받는다.

이렇게 설계된 이유는, context 변화로 인해 다시 렌더링하는 과정을 빠르게 유지하기 위함이다.

// 기본값이 light인  ThemeContext
const ThemeContext = React.createContext('light');

// 로그인한 유저 정보를 담는 UserContext
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // context 초기값을 제공하는 App 컴포넌트
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// 여러 context의 값을 받는 컴포넌트
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

주의사항

Provider에서 내려주는 value prop이 참조형 타입일 경우, context의 변동유무를 확인하는 알고리즘에 의해서 value prop이 바뀔때마다 Provider의 하위 컴포넌트들이 리렌더링 되는 문제가 있습니다

따라서, 이를 해결하기 위해서 참조형 타입은 부모의 state로 선언하는 방법이 있습니다

// value가 바뀔때마다 Toolbar가 리렌더링 된다
class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
// value로 넘겨줄 값을 부모의 state로 빼주자 
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

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

0개의 댓글