レンダリング関数の外部でReactコンテキストにアクセスする


93

Reduxの代わりに新しいReactContext APIを使用して新しいアプリを開発しています。以前は、Reduxたとえばユーザーのリストを取得する必要があるときにcomponentDidMount、アクションを呼び出すだけでしたが、React Contextを使用すると、アクションは内部に存在します。レンダー関数内にあるコンシューマー。つまり、レンダー関数が呼び出されるたびに、アクションが呼び出されてユーザーリストが取得されますが、不必要なリクエストが多数行われるため、これは適切ではありません。

では、componentDidMountレンダーで呼び出す代わりに、アクションを1回だけ呼び出すにはどうすればよいですか?

例として、次のコードを見てください。

次のProvidersように、すべてを1つのコンポーネントにラップしていると仮定します。

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

次に、次のように、すべてのアプリをラップするこのプロバイダーコンポーネントを配置します。

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

たとえば、ユーザービューでは、次のようになります。

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

私が欲しいのはこれです:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

しかしもちろんgetUsers、私のユーザービューの小道具に住んでいないため、上記の例は機能しません。これが可能である場合、それを行う正しい方法は何ですか?

回答:


144

EDITは:で反応し、フックの導入によりv16.8.0、あなたが使用することにより、機能性成分のコンテキストを使用することができますuseContextフックを

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

編集:バージョン16.6.0以降。あなたは使用してライフサイクルメソッドでコンテキストを利用することができますthis.contextように

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values

バージョン16.6.0より前は、次の方法で実行できました。

ライフサイクルメソッドでContextを使用するには、次のようにコンポーネントを記述します。

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

通常、アプリで1つのコンテキストを維持し、それを再利用するために上記のログインをHOCにパッケージ化することは理にかなっています。あなたはそれを次のように書くことができます

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

そして、あなたはそれを次のように使うことができます

export default withUserContext(User);

それはそれを行う方法です!しかし、私は自分のコンポーネントを他のものでラップしているので、コードはこの方法でますます複雑になります。したがって、with-contextライブラリとそのデコレータを使用すると、コードがはるかに単純になります。しかし、答えてくれてありがとう!
グスタボメンドンサ

それは私には機能しません、私はwith-context github
PassionateDeveloper

1
Shubham Khatriは彼の答えが正しいです、これが実行されるのを妨げる小さなタイプミスがあります。中括弧は暗黙の戻りを防ぎます。それらを括弧に置き換えるだけです。
VDubs 2018

1
this.context同じコンポーネント内で複数のコンテキストでを使用する方法を説明できますか?UserContextとPostContextにアクセスする必要のあるコンポーネントがあるとしましょう。それをどのように処理するのですか?私が見ることができるのは、両方を混合したコンテキストを作成することですが、これが唯一の、またはより良い解決策ですか?
グスタボメンドンサ

1
@GustavoMendonçathis.contextAPI実装を使用して、単一のコンテキストにのみサブスクライブできます。複数を読む必要がある場合は、レンダリングプロップパターンに従う必要があります
ShubhamKhatri19年

3

わかりました、制限付きでこれを行う方法を見つけました。with-contextライブラリを使用して、すべてのコンシューマーデータをコンポーネントの小道具に挿入することができました。

ただし、同じコンポーネントに複数のコンシューマーを挿入するのは複雑であるため、このライブラリを使用して混合コンシューマーを作成する必要があります。これにより、コードが洗練されなくなり、生産性が低下します。

このライブラリへのリンク:https//github.com/SunHuawei/with-context

編集:実際には、with-context提供するマルチコンテキストAPIを使用する必要はありません。実際、単純なAPIを使用して、コンテキストごとにデコレータを作成できます。コンポーネントで複数のコンシューマを使用する場合は、クラスの上に必要な数のデコレータを宣言してください!


1

私としては.bind(this)、イベントに追加するだけで十分でした。これは私のコンポーネントがどのように見えるかです。

// Stores File
class RootStore {
   //...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;


// In Component
class MyComp extends Component {
  static contextType = myRootContext;

  doSomething() {
   console.log()
  }

  render() {
    return <button onClick={this.doSomething.bind(this)}></button>
  }
}

1
このコードは非常に単純に見えますが、実際には値にアクセスしたり、RootStoreインスタンスと対話したりすることはありません。2つのファイルに分割された例とリアクティブUIアップデートを表示できますか?
dcsan

-7

子の小道具としてアクセスするには、上位の親コンポーネントでコンテキストを渡す必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.