Reactガイドを読んでいくその36

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

コンテクスト

昨日の続き

動的なコンテクスト

テーマに動的な値を使用したより複雑な例をあげている。

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';

// ThemedButtonを使用する中間のコンポーネント
function Toolbar(props) {
  return (
    <ThemedButton onClick={props.changeTheme}>
      Change Theme
    </ThemedButton>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };

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

  render() {
    // ThemeProvider 内の ThemedButton ボタンは
    // state からのテーマを使用し、外側では
    // デフォルトの dark テーマを使用します
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}

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

ネストしたコンポーネントからコンテクストを更新する

コンポーネントツリーのどこか深くネストされたコンポーネントからコンテクストを更新することはよく必要になるらしい。
ネストしたコンポーネントからコンテクストを更新するには、コンテクストを通して下の関数に渡すことで更新を可能とする方法がある。

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 には更新する関数も含まれているため、
    // コンテクストプロバイダにも渡されます。
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // state は全部プロバイダへ渡されます
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

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

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

複数のコンテクストを使用する

コンテクストの再レンダーを高速に保つためにReactは各コンテクストのコンシューマをツリー内の別々のノードにする必要がある。
二つ以上のコンテクストの値が一緒に使用されることが多い場合、両方を提供するような独自レンダープロップコンポーネントを作ってしまった方が、わかりやすいものになる可能性が高い。

// テーマのコンテクスト、デフォルトのテーマは light
const ThemeContext = React.createContext('light');

// サインイン済みのユーザのコンテクスト
const UserContext = React.createContext({
  name: 'Guest',
});

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

    // コンテクストの初期値を与える App コンポーネント
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

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

// コンポーネントは複数のコンテクストを使用する可能性があります
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

注意事項

コンテクストが再レンダーを決定するタイミングは参照の同一性を使用して決める。
プロバイダの親が再レンダーするときにコンシューマで意図しないレンダーを引き起こす可能性がある。
オブジェクトがvalueに対して常に作成されるため、プロバイダが再レンダーするたびに全てコンシューマを再レンダーしてしまう。

問題回避のために親のstateをリフトアップする。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

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

今日はここまで。