開発者ドキュメント

Reactコンテキストでユーザー状態を管理する方法

序章

Reactでは、データが小道具(親コンポーネントから子コンポーネント)を介して上から下に流れるのが一般的ですが、これは常に理想的ではありません。 さまざまな深さの多くのネストされたコンポーネントで利用可能である必要があるデータがあり、小道具を渡すことは維持が難しく、エラーが発生しやすい状況があります。

この状況では、 Redux を使用して状態を管理することをお勧めしますが、多くの人がこれを最初のオプションにすべきではないと主張しています。

React Context は、すべてのレベルで小道具を手動で渡す必要なしに、コンポーネント間でデータを共有するための代替ソリューションです。

この記事では、Context APIを調べ、それを使用してユーザーの状態を管理する方法を学習します。

前提条件

この記事をフォローするには、次のものが必要です。

このチュートリアルは、ノードv15.3.0、npm v6.14.9、およびreactv17.0.1で検証されました。

問題を理解する

userおよびavatarSizeプロップを渡すPageコンポーネントの例を次に示します。

<Page user={user} avatarSize={avatarSize} />

PageLayoutコンポーネントをレンダリングします:

<PageLayout user={user} avatarSize={avatarSize} />

NavigationBarコンポーネントをレンダリングします:

<NavigationBar user={user} avatarSize={avatarSize} />

userおよびavatarSizeプロップを使用するLinkおよびAvatarをレンダリングします。

<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

上記の例では、Avatarコンポーネントのみが実際にuserプロップを使用しています。 ただし、その祖先コンポーネント(親、祖父母など)のそれぞれは、userを受け取り、それを渡します。 これは、Avatarコンポーネントが将来別の小道具を必要とする場合、その祖先コンポーネントのそれぞれがそれを受け取り、渡すことを確認する必要があることを意味します。

実際には、user状態は多くの異なるコンポーネント間で共有する必要があるため、小道具として渡すと、上記の例よりもさらに深くネストされます。

React.createContextを作成しています

React.createContextメソッドは、Contextオブジェクトを返します。 このContextオブジェクトには、データのサブスクライブを可能にする2つの重要なReactコンポーネントProviderConsumerが付属しています。

Reactは、このContextオブジェクトにサブスクライブするコンポーネントをレンダリングすると、ツリー内でその上にある最も一致するProviderコンポーネントから現在のコンテキスト値を読み取ります。

createContextメソッドは、オプションのdefaultValue引数を取ります。これは、一致するContext.Providerコンポーネントがツリーに見つからなかった場合にContext.Consumerに提供されます。

Contextオブジェクトを作成し、他のコンポーネントで使用できるようにエクスポートします。

src / userContext.js
import React from 'react';

const userContext = React.createContext({user: {}});

export { userContext };

上記の例では、userContextを初期化し、{user: {}}defaultValueを提供しました。

Contextオブジェクトができたので、それに値を指定して変更をサブスクライブできます。

Context.Providerを適用する

ユーザー状態を値としてcontext.Providerに渡し、context.Consumerで使用できるようにします。

src / App.js
import React from 'react';

import Main from './Main';

import {userContext} from './userContext';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: {}
    };
  }

  componentDidMount() {
    // get and set currently logged in user to state
  }

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

export default App;

これは最初から素晴らしいことです。MainコンポーネントをuserContext.Providerでラップすると、渡した値をMainの子孫コンポーネント内で確実に使用できるようになります。

Context.Consumerを適用する

userContext.Consumerは子として機能を取ります。 この関数は、現在のコンテキスト値(userContext.Providerに小道具として渡される値)を受け取り、Reactノードを返します。

src / Main.js
import Sidebar from './Sidebar';
import Content from './Content';
import Avatar from './Avatar';

import {userContext} from './userContext';

function Main(props) {
  return (
    <Sidebar/>
    <userContext.Consumer>
      {({value}) => {
        <Avatar value={value}/>
      }}
    </userContext.Consumer>
    <Content/>
  )
}

export default Main;

この場合、Appコンポーネントのstate.userを値として受け取り、Avatarコンポーネントをレンダリングします。

ネストされたコンポーネントからのコンテキストの更新

これまで、React Contextを使用して、小道具を手動で渡すことなく、データを必要とするコンポーネントにデータを渡してきました。 次に、ネストされた子コンポーネントからコンテキストを更新できる必要があります。

ユーザーをログアウトするためのボタンを考えてみましょう。

この場合、同じコンテキストを介して関数を渡し、コンシューマーがコンテキストを更新できるようにすることができます。

src / App.js
import React from 'react';

import Main from './Main';

import {userContext} from './userContext';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: {}
    };

    this.logout = this.logout.bind(this);
  }

  logout() {
    this.setState({user: {}});
  }

  componentDidMount() {
    // get and set currently logged in user to state
  }

  render() {
    const value = {
      user: this.state.user,
      logoutUser: this.logout
    }

    return (
      <userContext.Provider value={value}>
        <Main/>
      </userContext.Provider>
    );
  }
}

export default App;

コンテキストを更新するために渡される関数は、userContext.Providerコンポーネント内のネストされたコンポーネントで使用できます。

src / Main.js
import Sidebar from './Sidebar';
import Content from './Content';
import Avatar from './Avatar';
import LogoutButton from './LogoutButton';

import {userContext} from './userContext';

function Main(props) {
  return (
    <Sidebar/>
    <userContext.Consumer>
      {({user, logoutUser}) => {
        return (
          <Avatar user={user}/>
          <LogoutButton onClick={logoutUser}/>
        );
      }}
    </userContext.Consumer>
    <Content/>
  )
}

export default Main;

Mainコンポーネントにログアウトボタンが追加されました。

これで、ReactのコンテキストAPIを使用してユーザーの状態を管理する方法の例は終わりです。

結論

この記事では、React Contextを紹介し、Context.ProducerContext.Consumerを使用しました。

Reactの詳細については、 React.js シリーズのコーディング方法をご覧になるか、Reactトピックページで演習やプログラミングプロジェクトを確認してください。

モバイルバージョンを終了