React: 反応はネストされたコンテキストをどのように解決しますか?

作成日 2019年01月18日  ·  38コメント  ·  ソース: facebook/react

私はたくさんの文脈を持っていて、このように書かなければならないので、醜いです! それは今私の仕事を妨げています。 そのようなデザインはそれをほとんど使用不可能にします。

<context1.Provider value={value1}>
  <context2.Provider value={value2}>
    <context3.Provider value={value3}>
      <context4.Provider value={value4}>
        <context5.Provider value={value5}>

        </context5.Provider>
      </context4.Provider>
    </context3.Provider>
  </context2.Provider>
</context1.Provider>
<context1.Consumer>
  {value1 => <context2.Consumer>
    {value2 => <context3.Consumer>
      {value3 => <context4.Consumer>
        {value4 => <context5.Consumer>
          {value5 => (
            null
          )}
        </context5.Consumer>}
      </context4.Consumer>}
    </context3.Consumer>}
  </context2.Consumer>}
</context1.Consumer>
Question

最も参考になるコメント

@ 0xorialは、そのためのコンポーネントを実際に持つ必要はありません。結局のところ、<>>はReact.createElementの関数呼び出しにすぎません。 したがって、次のような作成関数に簡略化できます。

const compose = (contexts, children) =>
  contexts.reduce((acc, [Context, value]) => {
    return <Context.Provider value={value}>{acc}</Context.Provider>;
  }, children);

そしてそれを次のように使用します:

import Context1 from './context1';
import Context2 from './context2';
import Context3 from './context3';
...
import Context15 from './context15';

const MyComponent = (props) => {
  // const value1..15 = ... get the values from somewhere ;

  return compose(
    [
      [Context1, value1],
      [Context2, value2],
      [Context3, value3],
      ...
      [Context15, value15],
    ],
    <SomeSubComponent/>
  );
}

全てのコメント38件

今後のフックAPIは、コンテキストを消費するための別の方法を提供します。

https://reactjs.org/docs/hooks-reference.html#usecontext

ありがとうございました。 しかし、プロバイダーはどうですか?

正直に言います。 この種の実装に遭遇した場合、アーキテクチャ設計は貧弱に見えるので、おそらくReactコンテキストを使用すべきではありません。

いいえ、新しいストアコンテナを設計しています。 それはreactでコンテキストと連携する必要があります。
https://github.com/rabbitooops/rako

そのライブラリは、プロバイダー/コンシューマーの5層の深さと何の関係がありますか?

Reduxのような単一ストアソリューションの代わりに、反応する複数のストアの注入をサポートしているためです。

その場合、コンテキスト処理はライブラリのユーザーに行われ、ライブラリには行われません。 機能をどのように利用するかは彼ら次第であり、すべてのプロバイダーを1か所に配置したい場合(複数のストアを持つという目的に反する場合)、それが彼らの選択です。 理想的には、マルチストアソリューションはアプリケーションのさまざまな分割で実装されるため、このようなネストされたコンテキストははるかにまれです。

少なくとも私の2セント。

したがって、コンテキストAPIは、reduxのような単一ストアソリューションにのみ適しています。

どういたしまして。 しかし、より現実的な例がなければ、問題について議論することも困難です。 作成してください?

ユーザーとカウンターの3つのストアテーマがあると想像してください。

function theme(getState) {
  return {
    color: 'white',
    setColor(color) {
      this.setState({color})
    }
  }
}

function user(getState) {
  return {
    name: '',
    setName(name) {
      this.setState({name})
    }
  }
}

function counter(getState) {
  return {
    value: 0,
    increment() {
      const {value} = getState()
      this.setState({value: value + 1})
    }
  }
}

const [themeStore, userStore, counterStore] = createStores(theme, user, counter)
const [themeContext, userContext, counterContext] = createContexts(themeStore, userStore, counterStore)


class App extends React.Component {
  render() {
    return (
      <themeContext.StoreProvider>
        <userContext.StoreProvider>
          <counterContext.StoreProvider>

            <Child />

          </counterContext.StoreProvider>
        </userContext.StoreProvider>
      </themeContext.StoreProvider>
    )
  }
}

class Child extends React.Component {
  static contextType = [themeContext, userContext]
  render() {
    const [theme, user] = this.context

    /* ... */
  }
}

あなたの理想的な構文は何ですか?

context.writeを使用して、ネストせずに複数のコンテキストを消費することをサポートします。

ネストせずに複数のコンテキストを使用することは、すでにサポートされています。 (フック付き)

Context.writeにはRFCがあります。 非常に複雑な問題が発生するため、問題が解決するかどうかはわかりません。 しかし、RFCが開いている間は、この問題で何が実行可能かわかりません。 RFCの動機にすでにあるものを超えて追加するものはありますか?

質問したいのですが。 クラスで複数のコンテキストを消費することをサポートしないのはなぜですか? フックAPIには解決すべき問題がたくさんあり、現在非常に不安定なようです。😨🧐😥🤕

static contextType = [themeContext, userContext]
const [theme, user] = this.context

複数のコンテキストを自動的にネストできるStoreProvidersを実装します。

const StoreProviders = constructStoreProviders(...storeContexts)

<StoreProviders>
  <Child />
</StoreProviders>

ダンの助けをありがとう! :)

@rabbitooopsフックに関して正確に問題があるのはどれですか? 私は本番環境でフックを使用していますが、フックは私のチームにとってうまく機能します。

そして、これはどうですか? 今はフックを使っても大丈夫です。 @gaearon

// A library
function useStoreProviders(Component, ...contexts) {
  contexts.forEach(context => Component.useProvider(context, someValue))
}

// User code
function App(props) {
  const [theme] = useState('white')

  // Safe to use `component hook`.
  App.useProvider(themeContext, theme)
  App.useShouldComponentUpdate(() => {})

  // Meanwhile, library can also use `component hook`.
  useStoreProviders(App, storeContext1, storeContext2, storeContext3)

  // Normal hook can't use `component hook`.
  customHook()

  /* ... */
}

<App />

decorateBeforeRender(App)

@rabbitooops単一のストアとシンボルをキーとして使用して、ストアのマルチレイヤーを模倣するのはどうですか?

data Store = Leaf Object | C Store Store

またはjavascriptで不完全な方法で:

const LEFT = Symbol('LEFT')
const RIGHT = Symbol('RIGHT')
function createLeafStore = return new Store({});
function createStore(leftChild :: Store, rightChild :: Store) {
  return new Store({[LEFT]: leftChild, [Right]: rightChild})
}

@TrySoundの違い: App.useProvider

@zhujinxuan申し訳ありませんが、私はあなたを取得できません。

あなたが使用することができます@zhujinxuan暗黙の、例:

<Subscribe to={[AppContainer, CounterContainer, ...]}>
  {(app, counter, ...) => (
    <Child />
  )}
</Subscribe>

フックAPIには解決すべき問題がたくさんあり、現在非常に不安定なようです

1、2週間以内にリリースの準備をしていますが、なぜそれを推測したのかわかりません。 安全を確保したい場合は、安定したリリースまでお待ちください。

そして、これはどうですか?

forEach )ループでフックを呼び出すことは、通常は許可されていません。 この方法で問題を引き起こすのは簡単です。

useStoreProviders

useProvideruseShouldComponentUpdateはどちらもフックとして問題があります(これがReactにフックがない理由です)。 https://github.com/facebook/react/issues/14534#issuecomment-455411307で私の応答を参照して


全体として、私はこの問題の意図を理解するのに苦労しています。

複数のコンテキストの消費は、 useContextフックによって解決されます。 配列を使用して「自動化」することお勧めしuseContext APIが提供するものです。 必要に応じて、特定のコンテキストを明示的に使用するuseMyContexts()フックを作成できます。 配列の長さが変わると壊れてしまう可能性があるため、あなたのように動的にすることはお勧めしません。

複数のプロバイダーを配置することは「ボイラープレート」と見なすことができ、最終的にはこれに対する解決策が得られる可能性があります。 しかし、なぜそれが大きな問題だと思われるのかもわかりません。 このスレッドの例は、私に問題を説明するのに十分現実的ではありません。 アプリケーションの上部のどこかにJSXのいくつかのレイヤーをネストしても、何も悪いことはありません。 ほとんどのコンポーネントにdivネストがはるかに深く、それほど害がないことを確認してください。

私はすでにこれらの点に答えたと思うので、これを閉じます、そして議論は輪になって行きます。 足りないものがあれば教えてください。

OT: @gaearonuseRenderようなものを追加したり、レンダリングをより細かく制御したりする計画はありますか? 例えば:

useRender(() => <div />, [...props])

2番目の引数はuseEffectフックと同じ役割を果たします。

useMemoはあなたの友達です。

https://reactjs.org/docs/hooks-faq.html#how-to-memoize-calculationsの2番目のスニペットを参照して

私はそのようなコードになってしまいました:

function provider<T>(theProvider: React.Provider<T>, value: T) {
   return {
      provider: theProvider,
      value
   };
}

function MultiProvider(props: {providers: Array<{provider: any; value: any}>; children: React.ReactElement}) {
   let previous = props.children;
   for (let i = props.providers.length - 1; i >= 0; i--) {
      previous = React.createElement(props.providers[i].provider, {value: props.providers[i].value}, previous);
   }
   return previous;
}

次に、私のトップレベルの提供コンポーネントで:

public render() {
      return (
         <MultiProvider
            providers={[
               provider(Context1.Provider, this.context1),
               provider(Context2.Provider, this.context2),
               provider(Context3.Provider, this.context3),
               provider(Context4.Provider, this.context4),
               provider(Context5.Provider, this.context5),
            ]}
         ><AppComponents />
      </MultiProvider>
}

@gaearon

アプリケーションの上部のどこかにJSXのいくつかのレイヤーをネストしても、何も悪いことはありません。

その方法で注入できるようにしたい依存関係が約15あり、15レベルのインデントがあると私にはきれいに見えません:)

@ 0xorialは、そのためのコンポーネントを実際に持つ必要はありません。結局のところ、<>>はReact.createElementの関数呼び出しにすぎません。 したがって、次のような作成関数に簡略化できます。

const compose = (contexts, children) =>
  contexts.reduce((acc, [Context, value]) => {
    return <Context.Provider value={value}>{acc}</Context.Provider>;
  }, children);

そしてそれを次のように使用します:

import Context1 from './context1';
import Context2 from './context2';
import Context3 from './context3';
...
import Context15 from './context15';

const MyComponent = (props) => {
  // const value1..15 = ... get the values from somewhere ;

  return compose(
    [
      [Context1, value1],
      [Context2, value2],
      [Context3, value3],
      ...
      [Context15, value15],
    ],
    <SomeSubComponent/>
  );
}

私は過去にこのケースを処理するライブラリを作成しました: https

react11

これは間違いなく、アプリケーションで常に発生することです。 useContextは、コンポーネント内のコンテキストデータを_消費_するのに最適ですが、複数のプロバイダーを備えたアプリでコンテキストを_提供_する必要がある場合はそれほど優れていません。

@alesmenzelsocialbakersソリューションのクロージャの代替案は

const composeProviders = (...Providers) => (Child) => (props) => (
  Providers.reduce((acc, Provider) => (
    <Provider>
      {acc}
    </Provider>
  ), <Child {...props} />)
)

const WrappedApp = composeProviders(
  ProgressProvider,
  IntentsProvider,
  EntitiesProvider,
  MessagesProvider
)(App)

ReactDOM.render(<WrappedApp />, document.getElementById('root'));

欠点は、特定のプロバイダーコンポーネントをそれぞれ作成する必要があることです。
例:

export const ProgressProvider = ({ children }) => {
  const [progress, setProgress] = useState(0)

  return (
    <ProgressContext.Provider value={{ progress, setProgress }}>
      {children}
    </ProgressContext.Provider>
  )
}

サービス構成に優れた状態管理ライブラリを作成しました。 これはプロバイダー地獄を回避するデモです。 気軽に試してみるか、ソース(100行のコード)を読んでください!

コンテキストプロバイダーを収集するための「スコープ」オブジェクトを導入し、次のようにします。

  • サービスは、同じスコープ内にあるかどうかに応じて、分離または構成できます。

    • サービスは、同じコンポーネント内にあるにもかかわらず、同じスコープ内の以前のサービスを利用できます。

  • スコープによって収集されたすべてのプロバイダーは、プロバイダーの地獄を回避して、一度に提供できます。

私もこれにひびを入れました。 これはうまくいくようです:

const composeWrappers = (
  wrappers: React.FunctionComponent[]
): React.FunctionComponent => {
  return wrappers.reduce((Acc, Current): React.FunctionComponent => {
    return props => <Current><Acc {...props} /></Current>
  });
}

使用法は次のとおりです。

const SuperProvider = composeWrappers([
    props => <IntlProvider locale={locale} messages={messages} children={props.children} />,
    props => <ApolloProvider client={client}>{props.children}</ApolloProvider>,
    props => <FooContext.Provider value={foo}>{props.children}</FooContext.Provider>,
    props => <BarContext.Provider value={bar}>{props.children}</BarContext.Provider>,
    props => <BazContext.Provider value={baz}>{props.children}</BazContext.Provider>,
  ]);
  return (
    <SuperProvider>
      <MainComponent />
    </SuperProvider>
  );

このヘルパーをnpmライブラリとして公開しましたreact-compose-wrappers

以下は、認証されたユーザーをそれを必要とするコンポーネントに渡す方法を示しています。

アプリケーション用に1つの状態を作成することにしました。 State.jsファイルで、初期状態、コンテキスト、レデューサー、プロバイダー、およびフックを設定しました。

import React, { createContext, useContext, useReducer } from 'react';

const INITIAL_STATE = {}

const Context = createContext();

const reducer = (state, action) => 
  action 
    ? ({ ...state, [action.type]: action[action.type] }) 
    : state;

export const Provider = ({ children }) => (
  <Context.Provider value={ useReducer(reducer, INITIAL_STATE) }>
    { children }
  </Context.Provider>
);

const State = () => useContext(Context);

export default State;

次に、index.jsファイルで、アプリをプロバイダーにラップしました。

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './State';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <Provider>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root'),
);

コンポーネントの状態を消費するには、フックを使用できます。 ディスパッチを使用して状態を更新することもできます。 たとえば、ユーザーを取得または設定したい場合です。

import React, {useEffect} from 'react';
import State from './State'

const ExampleComponent = () => {
  const [{ user }, dispatch] = State(); 

  useEffect(() => {
    const getUser = async () => {
      const data = await fetch('http://example.com/user.json');  // However you get your data
      dispatch({ type: 'user', user: data });
    }
    getUser();
  }, [dispatch]);

  // Don't render anything until user is retrieved
  // The user is undefined since I passed an empty object as my initial state
  if(user === undefined) return null; 

  return(
    <p>{user.name}</p>
  );
}

export default ExampleComponent;

この方法により、大量のコンテキストを追加することなく、必要な状態を構築する自由が得られ、プロバイダーの深いネストを回避するのに役立つと思います。

今後のフックAPIは、コンテキストを消費するための別の方法を提供します。

https://reactjs.org/docs/hooks-reference.html#usecontext

これをクラスコンポーネントで使用するにはどうすればよいですか?

今後のフックAPIは、コンテキストを消費するための別の方法を提供します。
https://reactjs.org/docs/hooks-reference.html#usecontext

これをクラスコンポーネントで使用するにはどうすればよいですか?

フックは、クラスを作成せずにさまざまなReact機能を利用するために使用されていませんか?
つまり、さまざまなフックが行うことはすべて、クラスにすでに存在しています。 便利な構文と使用法のAPIについて話している場合、reactはクラスから機能コンポーネントに移行するため、関数とフックへようこそ)

vue3で同様のAPIを提供することにより、問題を解決するためのパッケージを作成しました

https://github.com/TotooriaHyperion/react-multi-provide

  • 反応コンテキストが余分なビューツリーを必要とする問題を解決します。
  • 注入されたものの反応性も維持します
  • プロバイダー間のフラクタル
  • WeakMapを使用して依存関係を保存する
  • オブジェクトラップシンボルを使用して、より優れたタイプスクリプトサポート、依存性注入、およびデバッグエクスペリエンスを実現します

通知:

  • データ自体よりもデータへのアクセスを提供コンテキストの反応性@gaearonがあまりにも多くのコンテキストにサブスクライブしないことについて言ったことは正しかった。 しかし、彼は、コンテキストの反応性に依存してデータサブスクリプションを解決するのは間違ったパターンである複数のコンテキストを使用するので、1つのコンテキストで依存関係を提供することです。 その間、コンテキストを可能な限り安定させてください。
  • したがって、より優れたAPIが必要な場合は、自分で処理する必要があり、APIをフラクタルにすることを忘れないでください。 それが私のパッケージの目的です。

Outer.tsx

import React, { useMemo } from "react";
import { Providers, useCreateContexts, useProvide } from "../..";
import { createService, ServiceA } from "./service";

export const Outer: React.FC = ({ children }) => {
  const contexts = useCreateContexts();
  const service = useMemo(createService, []);
  useProvide(contexts, ServiceA.id, service);
  return <Providers contexts={contexts}>{children}</Providers>;
};

Inner2.tsx

import React from "react";
import { useContexts, useReplaySubject } from "../..";
import { ServiceA } from "./service";

export const Inner2: React.FC = () => {
  const [
    {
      state$,
      actions: { inc, dec },
    },
  ] = useContexts([ServiceA.id]);
  const count = useReplaySubject(state$);
  return (
    <>
      <p>{count}</p>
      <div>
        <button onClick={inc}>Increment</button>
        <button onClick={dec}>Decrement</button>
      </div>
    </>
  );
};

これが私がそれをする方法です:

interface Composable {
    (node: React.ReactNode): React.ReactElement
}

const composable1: Composable = (node)=>{
      return <someContext.Provider>{node}</someContext.Provider>
}

function Comp({children}:{children?:React.ReactNode}){
       return pipe(
             composabl1, composabl2, composable3
       )(children)
}

pipe関数は、rxjsなどの多くの一般的なライブラリにあります。また、このパイプラインのような操作のための言語レベルの提案もいくつかあります。 別のライブラリを使用して「解決」する必要はありません。

このページは役に立ちましたか?
0 / 5 - 0 評価