Redux: connectの掚奚される䜿甚法

䜜成日 2015幎08月07日  Â·  34コメント  Â·  ゜ヌス: reduxjs/redux

こんにちは@ gaearon-次のステヌトメントは、ドキュメントを読んでいるずきに私を

次に、react-reduxのconnect関数を䜿甚しお、Reduxに接続するコンポヌネントをラップしたす。 これは、最䞊䜍のコンポヌネントたたはルヌトハンドラヌに察しおのみ実行しおください。 技術的には、アプリ内の任意のコンポヌネントをReduxストアにconnectできたすが、デヌタフロヌの远跡が困難になるため、これを深くしすぎないようにしおください。

深いプロップチェヌンは、私がFluxを䜿甚するようになったReactの問題の1぀でした。 デヌタフロヌのトレヌスを容易にするためのトレヌドオフは、プロップフロヌをセットアップ/維持/トレヌスする必芁があるこずです。さらに重芁なこずに、芪は子のデヌタ芁件に぀いお知る必芁がありたす。 私の経隓から、このアプロヌチが優れおいるずは確信しおいたせんが、あなたの考えを聞いおみたいですsmile

最も参考になるコメント

倚くのコンポヌネントをconnectするずきにデヌタフロヌをトレヌスする以倖に問題はありたすか たずえば、パフォヌマンスの䜎䞋はありたすか

いいえ、たったく逆です。トップダりンフロヌを攟棄するこずで、パフォヌマンスが向䞊したす。

党おのコメント34件

私はブレントに匷く同意したす。 これは、Relayのようなものの背埌にある基本的な考え方の倚くであり、より高い結束に぀ながりたす。

たぶん、「ここでは人によっお奜みが違う」ずだけ蚀うべきでしょう。

@ gaearon-おそらく、デヌタフロヌのトレヌスを耇雑にする方法のデモンストレヌションを含むトレヌドオフに関するセクションが圹立぀でしょう。 私はその奜みの私の偎に売り蟌むこずができたした。

いいね。 最初のドキュメントが萜ち着いたら、開いたたたにしお、もう䞀床確認したしょう。

@gaearonいいですね :)再蚪したいずきに私にpingしおください

私はReduxを初めお䜿甚したすが、これは昚幎の私にずっおかなり倧きな問題であり、デヌタ構造を倧幅に倉曎したアプリを構築したした。 だから私はこれに぀いお@brentvatneに本圓に同意しおいるず蚀わなければなりたせん。

倚くのコンポヌネントをconnectするずきにデヌタフロヌをトレヌスする以倖に問題はありたすか たずえば、パフォヌマンスの䜎䞋はありたすか

倚くのコンポヌネントをconnectするずきにデヌタフロヌをトレヌスする以倖に問題はありたすか たずえば、パフォヌマンスの䜎䞋はありたすか

いいえ、たったく逆です。トップダりンフロヌを攟棄するこずで、パフォヌマンスが向䞊したす。

䞭間レベルのコンポヌネントの䞍芁な再レンダリングを回避できるためですか

はい。

議論に远加するには私は䞻にルヌトコンポヌネントぞの接続の䜿甚を制限しおいたす。 远加のスマヌトコンポヌネントフォヌムモヌダルなどを䜿甚できるペヌゞがある堎合、回避策は芁玠たたはノヌドを枡し、ダムコンポヌネントにレンダリングさせるこずでした。 これは、ボむラヌプレヌトがもう少しあるこずを意味したすが、ダムコンポヌネントのテストは簡単です。 私はただこれを実隓しおいたすが、これは簡単なテスト容易性をあきらめるこずなくスマヌトコンポヌネントを䜜成するための最良の方法かもしれないず思いたす。

倧たかな䟋を挙げるず

Foo.jsx

export class Foo extends Component {
  render () {
    return (
      <div className='foo'>
        {/* foo stuff going on in here */}
        {this.props.Bar}
      </div>
    )
  }
}

FooContainer.jsx

@connect(getState, getActions)
export class FooContainer extends Component {
  render () {
    return (
      <Foo
        Bar={<BarContainer/>}
        {...this.props}
       />
    )
  }
}

@brentvatne䜕かを曞きたいず思ったら、それを珟圚のドキュメント構造のどこに収めるかを考えお、自由に先に進んでください :-)

2015/10/19曎新このコメントを改善し、 react-redux-provideずしおリリヌスしたした。 以䞋のhttps://github.com/rackt/redux/issues/419#issuecomment-149325401を参照しお

475から続けお、私が思い぀いたものに぀いお説明したすが、この議論はおそらく珟圚react-redux属しおいたす。 ;

暪向きの割り圓おを䜿甚するモゞュラヌプロバむダヌ

簡単に蚀うず、私が採甚したアプロヌチは、任意のコンポヌネントに割り圓おるこずができるモゞュラヌプロバむダヌを䞭心に展開しおいたす。 これにより、_真の_「ダム」コンポヌネントが可胜になり、関心の分離が最倧限に行われ、互換性のある任意の数のプロバむダヌを非垞に簡単か぀迅速に䜿甚および共有できるようになりたす。 たた、コンポヌネントを曎新するためのより効率的な方法を適甚したす。

だから...私はactions 、 constants 、 containers 、 reducers 、およびstoresディレクトリ䟋内で䞀般的を取り陀きたしたそしおそれらを単䞀のprovidersディレクトリに眮き換えたした。 あるいは、 providersディレクトリは必芁ないかもしれたせん。このアプロヌチでは、スタンドアロンプ​​ロバむダヌをパッケヌゞ化しお配垃できるからです。 この特定のアプロヌチが採甚されれば、いく぀かの本圓にクヌルなものが珟れるず思いたす!! 泚もちろん、これらのディレクトリを1぀に統合する必芁はありたせんが、1読みやすく、理解しやすく、2定型文を枛らし、3個々のプロバむダヌが十分に小さいので意味があるず思いたす。 

「ダム」コンポヌネントの䟋

// components/Branch.js

import React, { Component } from 'react';
import provide from '../utilities/provide.js';
import { branchName, tree, toggle, open, theme } from '../common/propTypes.js';
import Limbs from './Limbs.js';

<strong i="22">@provide</strong>  // maybe require specifying expected props? e.g., @provide('theme')
export default class Branch extends Component {
  static propTypes = { branchName, tree, toggle, open, theme };

  onClick(event) {
    const { branchName, toggle } = this.props;  // toggle is from a provider

    event.stopPropagation();
    toggle(branchName);
  }

  render() {
    const props = this.props;
    const { branchName, tree, open, theme } = props;  // latter 3 from providers
    const classes = theme.sheet.classes || {};
    const imgSrc = open ? 'folder-open.png' : 'folder-closed.png';

    return (
      <div
        onClick={::this.onClick}
        className={classes.branch}
      >
        <h4 className={classes.branchName}>
          <img
            className={classes.branchIcon}
            src={theme.imagesDir+imgSrc}
          />

          <span>{branchName}</span>
        </h4>

        <Limbs
          tree={tree}
          open={open}
        />
      </div>
    );
  }
}

プロバむダヌの䟋

// providers/toggle.js

import createProvider from '../utilities/createProvider.js';

export const TOGGLE = 'TOGGLE';

export const actions = {
  toggle(fullPath) {
    return { type: TOGGLE, fullPath };
  }
};

export const reducers = {
  open(state = {}, action) {
    switch (action.type) {
      case TOGGLE:
        const { fullPath } = action;
        return { ...state, [fullPath]: !state[fullPath] };

      default:
        return state;
    }
  }
};

function merge (stateProps, dispatchProps, parentProps) {
  return Object.assign({}, parentProps, {
    open: !!stateProps.open[parentProps.fullPath]
  });
}

export const provider = createProvider(actions, reducers, merge);
export default provider;

暪向きの割り圓お

前述のように、アむデアは、任意のプロバむダヌを「ダム」コンポヌネントに簡単に割り圓おるこずができるようにするこずです。 したがっお、アプリをマりントするずきは、次のように行うこずができたす。

import React from 'react';
import { Provider } from 'react-redux';

import assignProviders from './utilities/assignProviders.js';
import createStoreFromProviders from './utilities/createStoreFromProviders.js';

import dark from './themes/dark.js';
import github from './sources/github.js';
import * as providers from './providers/index.js';
import * as components from './components/index.js';

const { packageList, sources, toggle, theme } = providers;
const { Branches, Branch, Limbs, Limb } = components;

const initialState = {
  packageList: [
    'github:gaearon/react-redux<strong i="10">@master</strong>',
    'github:loggur/branches<strong i="11">@master</strong>',
    'github:rackt/redux<strong i="12">@master</strong>'
  ],
  sources: {
    github: github({
      token: 'abc123',
      auth: 'oauth'
    })
  },
  open: {
    'github': true,
    'github:gaearon': true,
    'github:gaearon/react-redux<strong i="13">@master</strong>': true,
    'github:rackt': true,
    'github:rackt/redux<strong i="14">@master</strong>': true
  },
  theme: dark
};

const store = createStoreFromProviders(providers, initialState);

assignProviders({ theme }, components);
assignProviders({ packageList }, { Branches });
assignProviders({ sources }, { Branches, Branch });
assignProviders({ toggle }, { Branch, Limb });

React.render(
  <Provider store={store}>
    {() => <Branches/>}
  </Provider>,
  document.getElementById('root')
);

うたくいけば、これはすべお非垞に簡単ですが、必芁に応じお、より詳现に説明し、説明のためにコメントを远加できれば幞いです。

远加コヌド

これらの䟋では、いく぀かのナヌティリティ関数がむンポヌトされおいるこずに気付くでしょう。 これらは小さなモゞュヌルであり4぀のうち3぀は数行の長さです、基本的には既存のredux react-reduxメ゜ッドず

// utilities/createProvider.js

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

/**
 * Creates an object to be used as a provider from a set of actions and 
 * reducers.
 *
 * <strong i="10">@param</strong> {Object} actions
 * <strong i="11">@param</strong> {Object} reducers
 * <strong i="12">@param</strong> {Function} merge Optional
 * <strong i="13">@return</strong> {Object}
 * <strong i="14">@api</strong> public
 */
export default function createProvider (actions, reducers, merge) {
  return {
    mapState(state) {
      const props = {};

      for (let key in reducers) {
        props[key] = state[key];
      }

      return props;
    },

    mapDispatch(dispatch) {
      return bindActionCreators(actions, dispatch);
    },

    merge
  };
}
// utilities/createStoreFromProviders.js

import { createStore, combineReducers } from 'redux';

import { createStore, applyMiddleware, combineReducers } from 'redux';

/**
 * Creates a store from a set of providers.
 *
 * <strong i="17">@param</strong> {Object} providers
 * <strong i="18">@param</strong> {Object} initialState Optional
 * <strong i="19">@return</strong> {Object}
 * <strong i="20">@api</strong> public
 */
export default function createStoreFromProviders (providers, initialState) {
  const reducers = {};
  const middleware = [];
  let create = createStore;

  for (let key in providers) {
    let provider = providers[key];

    Object.assign(reducers, provider.reducers);

    if (provider.middleware) {
      if (Array.isArray(provider.middleware)) {
        for (let mid of provider.middleware) {
          if (middleware.indexOf(mid) < 0) {
            middleware.push(mid);
          }
        }
      } else if (middleware.indexOf(provider.middleware) < 0) {
        middleware.push(provider.middleware);
      }
    }
  }

  if (middleware.length) {
    create = applyMiddleware.apply(null, middleware)(createStore);
  }

  return create(combineReducers(reducers), initialState);
}
// utilities/assignProviders.js

/**
 * Assigns each provider to each component.  Expects each component to be
 * decorated with `@provide` such that it has an `addProvider` static method.
 *
 * <strong i="5">@param</strong> {Object} providers
 * <strong i="6">@param</strong> {Object} components
 * <strong i="7">@api</strong> public
 */
export default function assignProviders (providers, components) {
  for (let providerName in providers) {
    let provider = providers[providerName];

    if (provider.default) {
      provider = provider.default;
    } else if (provider.provider) {
      provider = provider.provider;
    }

    for (let componentName in components) {
      let addProvider = components[componentName].addProvider;
      if (typeof addProvider === 'function') {
        addProvider(providerName, provider);
      }
    }
  }
}

最埌になりたしたが、 provideデコレヌタがありたす。 これは、暪向きの割り圓おを可胜にするように蚭蚈されたconnect修正バヌゞョンです。

// utilities/provide.js

import React, { Component, PropTypes } from 'react';
import createStoreShape from 'react-redux/lib/utils/createStoreShape';
import shallowEqual from 'react-redux/lib/utils/shallowEqual';
import isPlainObject from 'react-redux/lib/utils/isPlainObject';
import wrapActionCreators from 'react-redux/lib/utils/wrapActionCreators';
import invariant from 'invariant';

const storeShape = createStoreShape(PropTypes);
const defaultMapState = () => ({});
const defaultMapDispatch = dispatch => ({ dispatch });
const defaultMerge = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
});

// Helps track hot reloading.
let nextVersion = 0;

export default function provide (WrappedComponent) {
  const version = nextVersion++;
  const providers = [];
  let shouldSubscribe = false;

  function getDisplayName () {
    return ''
      +'Provide'
      +(WrappedComponent.displayName || WrappedComponent.name || 'Component')
      +'('+providers.map(provider => provider.name).join(',')+')';
  }

  function addProvider (name, { mapState, mapDispatch, merge }) {
    if (Boolean(mapState)) {
      shouldSubscribe = true; 
    }

    providers.push({
      name,
      mapState: mapState || defaultMapState,
      mapDispatch: isPlainObject(mapDispatch)
        ? wrapActionCreators(mapDispatch)
        : mapDispatch || defaultMapDispatch,
      merge: merge || defaultMerge
    });

    Provide.displayName = getDisplayName();
  }

  function computeStateProps (store) {
    const state = store.getState();
    const stateProps = {};

    for (let provider of providers) {
      let providerStateProps = provider.mapState(state);

      invariant(
        isPlainObject(providerStateProps),
        '`mapState` must return an object. Instead received %s.',
        providerStateProps
      );

      Object.assign(stateProps, providerStateProps);
    }

    return stateProps;
  }

  function computeDispatchProps (store) {
    const { dispatch } = store;
    const dispatchProps = {};

    for (let provider of providers) {
      let providerDispatchProps = provider.mapDispatch(dispatch);

      invariant(
        isPlainObject(providerDispatchProps),
        '`mapDispatch` must return an object. Instead received %s.',
        providerDispatchProps
      );

      Object.assign(dispatchProps, providerDispatchProps);
    }

    return dispatchProps;
  }

  function computeNextState (stateProps, dispatchProps, parentProps) {
    const mergedProps = {};

    for (let provider of providers) {
      let providerMergedProps = provider.merge(
        stateProps, dispatchProps, parentProps
      );

      invariant(
        isPlainObject(providerMergedProps),
        '`merge` must return an object. Instead received %s.',
        providerMergedProps
      );

      Object.assign(mergedProps, providerMergedProps);
    }

    return mergedProps;
  }

  const Provide = class extends Component {
    static displayName = getDisplayName();
    static contextTypes = { store: storeShape };
    static propTypes = { store: storeShape };
    static WrappedComponent = WrappedComponent;
    static addProvider = addProvider;

    shouldComponentUpdate(nextProps, nextState) {
      return !shallowEqual(this.state.props, nextState.props);
    }

    constructor(props, context) {
      super(props, context);
      this.version = version;
      this.store = props.store || context.store;

      invariant(this.store,
        `Could not find "store" in either the context or ` +
        `props of "${this.constructor.displayName}". ` +
        `Either wrap the root component in a <Provider>, ` +
        `or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
      );

      this.stateProps = computeStateProps(this.store);
      this.dispatchProps = computeDispatchProps(this.store);
      this.state = { props: this.computeNextState() };
    }

    recomputeStateProps() {
      const nextStateProps = computeStateProps(this.store);
      if (shallowEqual(nextStateProps, this.stateProps)) {
        return false;
      }

      this.stateProps = nextStateProps;
      return true;
    }

    recomputeDispatchProps() {
      const nextDispatchProps = computeDispatchProps(this.store);
      if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
        return false;
      }

      this.dispatchProps = nextDispatchProps;
      return true;
    }

    computeNextState(props = this.props) {
      return computeNextState(
        this.stateProps,
        this.dispatchProps,
        props
      );
    }

    recomputeState(props = this.props) {
      const nextState = this.computeNextState(props);
      if (!shallowEqual(nextState, this.state.props)) {
        this.setState({ props: nextState });
      }
    }

    isSubscribed() {
      return typeof this.unsubscribe === 'function';
    }

    trySubscribe() {
      if (shouldSubscribe && !this.unsubscribe) {
        this.unsubscribe = this.store.subscribe(::this.handleChange);
        this.handleChange();
      }
    }

    tryUnsubscribe() {
      if (this.unsubscribe) {
        this.unsubscribe();
        this.unsubscribe = null;
      }
    }

    componentDidMount() {
      this.trySubscribe();
    }

    componentWillReceiveProps(nextProps) {
      if (!shallowEqual(nextProps, this.props)) {
        this.recomputeState(nextProps);
      }
    }

    componentWillUnmount() {
      this.tryUnsubscribe();
    }

    handleChange() {
      if (this.recomputeStateProps()) {
        this.recomputeState();
      }
    }

    getWrappedInstance() {
      return this.refs.wrappedInstance;
    }

    render() {
      return (
        <WrappedComponent ref='wrappedInstance' {...this.state.props} />
      );
    }
  }

  if ((
    // Node-like CommonJS environments (Browserify, Webpack)
    typeof process !== 'undefined' &&
    typeof process.env !== 'undefined' &&
    process.env.NODE_ENV !== 'production'
   ) ||
    // React Native
    typeof __DEV__ !== 'undefined' &&
    __DEV__ //eslint-disable-line no-undef
  ) {
    Provide.prototype.componentWillUpdate = function componentWillUpdate () {
      if (this.version === version) {
        return;
      }

      // We are hot reloading!
      this.version = version;

      // Update the state and bindings.
      this.trySubscribe();
      this.recomputeStateProps();
      this.recomputeDispatchProps();
      this.recomputeState();
    };
  }

  return Provide;
}

provideデコレヌタに぀いお蚀及する䟡倀のあるこずの1぀は、 react-devtoolsを䜿甚しおすべおを芋るず、次のようなものが衚瀺されるこずです BranchesはProvideBranches(theme,packageList,sources)ラップされたす
2015-08-15-010648_1366x768_scrot

これの代わりに元のconnectで衚瀺され、 BranchesはConnect(Branches)ラップされたす
2015-08-14-220140_1366x768_scrot

制限事項

私は2日前にreduxに぀いお孊び始めたばかりなので、このアプロヌチの結果ずしおどのような制限が存圚するのかもしあれば正盎にわかりたせん。 頭から離れお、私は本圓に䜕も考えるこずができず、すべおが私のナヌスケヌスで完党に機胜しおいるように芋えたすが、おそらくより経隓豊富な誰かがチャむムを鳎らすこずができたす。

この特定のアプロヌチを思い぀いたのは、任意のプロバむダヌを割り圓おるこずができる_真の_「ダム」コンポヌネントを持぀ずいうアむデアが本圓に奜きだからです。 関心の分離を匷制し、モゞュヌルずしお簡単に亀換できるプロバむダヌをいく぀でも䜿甚できるようにしたす。

さらに、 @ gaearonがこのアプロヌチを気に入っおおり、 react-reduxの範囲内にあるず思われる堎合は、远加のPRを提出しおいただければreact-redux-providers䜜成しお公開し、最終的にはサンプルプロバむダヌたずえば、 react-redux-provide-toggle ずいく぀かの実際の䟋を公開したす。

䞭間レベルのコンポヌネントの䞍芁な再レンダリングを回避できるためですか

はい

ここでの方向性は、状態App状態ずUI状態の䞡方。コンポヌネントにUI状態を保持にImmutable.jsを䜿甚し、すべおのコンポヌネントにPureRenderMixinを䜿甚するこずです。 これにより、トップダりンフロヌを䜿甚するこずによるパフォヌマンスの䜎䞋が解消されたせんか

線集䞋䜍レベルのコンポヌネントの1぀を再レンダリングする必芁がある堎合、䞭間レベルのコンポヌネントのパフォヌマンスの䜎䞋を排陀できないこずに気づきたしたが、それでも倚くのオヌバヌヘッドを排陀する必芁がありたす。 それに぀いお䜕か経隓はありたすか

@ danmaz74私たちはそのようなシナリオでパフォヌマンスの問題にぶ぀かっおいたすreduxではなく、非垞によく䌌た自家補のlibを䜿甚しおいたす。 ただし、非垞に「高䟡な」コンポヌネントを含む、かなり耇雑なアプリがありたす。 たた、アプリが倧きくなるに぀れお、トップレベルだけでなく倚くの堎所にデヌタを挿入するこずで、コンポヌネント間に暗黙の䟝存関係が䜜成されるのを防ぎ、芪が子のデヌタ芁件に぀いおあたり知らないようにするこずができたす。

@eldh曎新しおくれおありがずう、それは理にかなっおいたす。 続行する間、それを芚えおおきたす:)

これに関する曎新はありたすか timburの䟋は私には完党に理にかなっおいたすが、シングルコネクトの緎習は私にはよくわかりたせん。 私は反察の議論、すなわち「人々はここで異なる奜みを持っおいる」に興味がありたす。

単䞀の接続を䜿甚するず、状態をたずえば10コンポヌネントの深い階局に倉換するために、巚倧なmapStateToProps関数が必芁になりたす...その背埌にある考えが䜕であるか、たたは䜕かを誀解しおいるかどうかは少し混乱しおいたす...

単䞀の接続を䜿甚するず、巚倧なmapStateToProps関数が必芁になりたす

誰も単䞀の接続を支持しおいたせん。

次に、react-reduxのconnect関数を䜿甚しお、Reduxに接続するコンポヌネントをラップしたす。 これは、最䞊䜍のコンポヌネントたたはルヌトハンドラヌに察しおのみ実行しおください。 技術的には、アプリ内の任意のコンポヌネントをReduxストアにconnectできたすが、デヌタフロヌの远跡が困難になるため、これを深くしすぎないようにしおください。

「シングル」ずは、䟋で䜜成したような小さなアプリのみを指したす。 これをより明確にするために、ドキュメントを自由に修正しおください。 私は珟圚他のプロゞェクトで忙しいので、誰かがPRをしない限り、この問題が動きを起こすこずを期埅しないでください。 あなたもそれを行うこずができたす。

ご説明ありがずうございたす。

぀いにreact-redux-provideリリヌスに取り掛かりたした。 こちらをご芧ください。 たた、数日/数週間以内に他のいく぀かのものをリリヌスする予定です。

ずおもよさそうです、ありがずう

私は新しいreduxプロゞェクトを開始したばかりで、「スマヌト」コンポヌネントにconnectを䜿甚しおいたす。これは私にずっおはるかに理にかなっおいたす。パフォヌマンス䞊のメリットがある堎合は、远加のメリットがありたす。 逆に、すべおの制埡をメむンのアプリたたはルヌタヌにたでバブルした堎合、SRPは完党に倱われたす-物事を分解し始める前に、アプリはどのくらいの倧きさにする必芁がありたすか

関連する芁玠をコンポヌネントフォルダに敎理するこずも考えおいたす。 䞻成分等の暪にレデュヌサヌを入れたす。

最終的に、redux / fluxは予枬可胜な状態にずっお倧きなメリットであるず思いたすが、暙準のmvからの粟神的な倉化-UIアプリの開発をシンプルにし、誰もがアクセスできるようにしたものは䜕でも、最終的にはfluxが抜象化され、移動したすmv *のように芋えるものに戻りたす。

これは1285で修正されおいたす。

曎新されたドキュメントでコンテナコンポヌネントを䜜成するこずは掚奚されなくなりたした。
http://redux.js.org/docs/basics/UsageWithReact.html

ねえ、私はここで圹立぀かもしれないいく぀かのものに぀いお曞いた。 :)

https://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1

https://github.com/reactjs/redux/issues/419#issuecomment -183769392も1353に圹立぀ず思い

@timbur玠晎らしい蚘事 この質問に぀いおのあなたの考えも共有しおいただけたせんか //github.com/reactjs/react-redux/issues/278

これがやみくもに明癜であるかどうかはわかりたせんが、このスレッドに来るreduxに䞍慣れな人にずっおは、蚀われおいるこずの倚くはおそらく少し抜象的なものだず思うので、明確にするためにここに远加する䟡倀があるず思いたす。

私が最初にreduxを䜿い始めたずき、アプリがそれほどデカップリングを必芁ずしないず思ったので、誀っお「コンテナ」接続されたコンポヌネントを単なる叀いダム「コンポヌネント」の䞭に点圚させたした。 私はどれほど間違っおいた。 これらのコンポヌネントの倚くを再利甚する必芁があるこずに気付いたずき、かなりのリファクタリングを行う必芁があり、倚くのスマヌトなものをツリヌの最䞊郚に移動したしたが、これはすぐに扱いにくくなりたした。 䞊郚にある「プロバむダヌ」は、コンテキストを提䟛したすが必芁に応じお、基本的にすべおのアプリを提䟛したすすべきではありたせん。

私がアプロヌチするのに最適な方法は、プロバむダヌを最䞊䜍にしお、ダムコンポヌネントを構成するコンテナヌの階局を䜜成するこずです。 コンテナは他のコンテナ内にのみ存圚する必芁がありたす。 アプリは、コンポヌネントを䜿甚しおデヌタを衚瀺するコンテナヌの階局である必芁がありたす。

倚くの堎合、リストを䜿甚しおこれを行う良い方法は、スマヌトコンポヌネントを介しおIDを枡すこずです。 IDの必芁性は、䜕かがアプリのドメむンに属しおいるこずを瀺しおいたす。 したがっお、可胜な堎合は、あるコンテナ内のIDのリストを取埗し、それらを別のコンテナに枡したす。別のコンテナは、IDを䜿甚しお必芁な情報を取埗できたす。 各コンテナ内でコンポヌネントを䜿甚しお、IDを必芁ずせずにその情報をレンダリングしたす。

以䞋に、コンポヌネントを䜿甚しおそれらを衚瀺するコンテナヌ階局を介しおアプリの接続された郚分を枡す方法の耇雑な䟋をモックアりトしたした。

// Provider component that renders some containers and some components and provides the store
class TodoAppProvider {
  constructor() {
    // setup store etc.
  }

  render() {
    return (
      <Provider store={this.store}> {/* Provider from 'react-redux' */}
        <AppLayoutComponent title="My Todos" footer={<TodoFooter />}>
          <TodoListsContainer />
        </AppLayoutComponent>
      </Provider>
    );
  }
);

// AppLayoutComponent
// Lots of nice css, other dumb components etc. no containers!
export default const AppLayoutComponent = ({ title, children, footer }) => (
  <header>
    {title}
  </header>
  <main>
    {children /* This variable can be a container or components but it's not hardcoded! */}
  </main>
  <footer>
    {footer}
  </footer>
);

// TodoFooter
// Another dumb component
export default const TodoFooter = () => (
  <footer>
    &copy; {Date.now() /* we are copyrighted to the millisecond */}
  </footer>
);

// TodoListsContainer
// Smart component that renders all the lists
class TodoListsContainer extends React.Component {
  render() {
    return () {
      <div>
        {todoLists.map(id => (
          {/* this container renders another container */ }
          <TodoListContainer key={id} todoListId={id} />
        ))}
      </div>
    }
  }
}

const mapStateToProps = state => ({
  todoLists: getTodoLists(state),
});

export default connect(mapStateToProps)(TodoListsContainer);

// TodoListContainer
// Gets the props and visibleTodo IDs for the list
class TodoListContainer {
  render() {
    const { id, title, visibleTodos } = this.props;
    return (
      <div>
        {/* Render a component but passes any connected data in as props / children */}
        <TodoListPanelComponent title={title}>
          {visibleTodos.map(id => (
            <TodoContainer todoId={id} />
          ))}
        </TodoListPanelComponent>
      </div>
    );
  }
}

const mapStateToProps = (state, { todoListId }) => ({
  ...getTodoList(state, todoListId), // A todo object (assume we need all the attributes)
  visibleTodos: getVisibleTodos(state, todoListId), // returns ids
});

export default connect(mapStateToProps)(TodoListContainer);


// TodoListPanelComponent
// render the panel to sit the todos in
// children should be todos
// No containers!
export default const TodoListPanelComponent = ({ title, children }) => (
  <div>
    <h3>{title}</h3>
    <div>
      {children}
    </div>
  </div>
);

// TodoContainer
// This just wraps the TodoComponent and passed the props
// No separate class or JSX required!
const mapStateToProps = (state, { todoId }) => ({
  ...getTodo(state, todoId),
});

const mapDispatchToProps = (dispatch, { todoListId }) => ({
  handleFilter: () => dispatch(hideTodo(id)), // Pass ALL smart stuff in
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoComponent); // Passing in the component to connect

// TodoComponent
// Render the component nicely; again, as all of its connected stuff passed in
// The FilterLinkContainer is an example of a smell that will come back to bite you!
export default const TodoComponent = ({ content, isComplete, handleFilter }) => (
  <div>
    <div>
      {content}
    </div>
    <div>
      {isComplete ? '✓' : '✗'}
    </div>
    <div>
      {/* Don't do this, can't re-use TodoComponent outside the app context! */}
      <FilterLinkContainer />

      {/* Instead do this (or similar), component can be reused! */}
      <Link onClick={handleFilter}>
        'Filter'
      </Link>
    </div>
  </div>
);

したがっお、ここでのコンテナ階局はTodoAppProvider > TodoListsContainer > TodoListContainer > TodoContainerです。 それらはそれぞれ互いにレンダリングされ、コンポヌネント内でレンダリングされるこずはなく、生のビュヌコヌドは含たれおいたせんReactのラッピングの理由で時折divを陀いお。

最終的に、私がそれを考えるのが奜きなのは、UIが気にする䟿利な方法にデヌタをマップする接続されたコンポヌネントのツリヌを最初に䜜成するかのようです。 ただし、UIはたったくなく、コンテナだけの階局を介しおマップされた状態のツリヌ

connect()耇数回䜿甚するず、パフォヌマンスが䜎䞋したすかたたは別のアヌキテクチャ䞊の理由 次の方法で接続゚ントリポむントを抜象化するこずにより、コンポヌネントに頻繁に䜿甚される小道具を提䟛しようずしおいたす。

// connectCommonProps.js (mergeProps not included for the sake of simplicity)

const _mapStateToProps = (state) => ({ [often used slices of state] });

const _mapDispatchToProps = (dispatch) => ({ [often used actions] });

const connectCommonProps = (mapStateToProps, mapDispatchToProps, component) => {
    // First connect
    const connectedComponent = connect(mapStateToProps, mapDispatchToProps)(component);

    // Second connect
    return connect(_mapStateToProps, _mapDispatchToProps)(connectedComponent);
};

export default connectMapAndFieldProps;
// Some component that needs the often used props
...
export default connectCommonProps(..., ..., Component);

私はここで怠惰で、宣蚀を単玔に保぀ため、2぀のバヌゞョンのmapStateToPropsず2぀のバヌゞョンのmapDispatchToProps組み合わせたせんでした。 しかし、 connectそれを任せるのは悪い考えかどうか疑問に思いたす。

@timotgl Danはアクティブなメンテナではなくなりたした。盎接pingしないでください。

耇数のコンポヌネントの接続に関するReduxFAQ゚ントリであなたの質問に答えおいるず蚀っおいたしたが、別のこずに぀いお質問しおいるようです-意図的に耇数の接続を単䞀のコンポヌネントにラップしおいたすか これたで誰かがそうしおいるのを芋たこずがないずは蚀えたせんし、Reduxコヌドを_たくさん_芋たこずがありたす。

個人的には、別のアプロヌチを詊すこずをお勧めしたす。 「共通の小道具」HOCか、ほずんどの堎合それらを子に枡すだけのものを甚意するか、セレクタヌを䜿甚しお特定のコンポヌネントのmapState関数で共通の小道具を取埗し、それらを必芁な特定の小道具ず組み合わせたす。

@markerikson申し蚳ありたせんが、それを知りたせんでした、at-mentionは削陀されたした。

したがっお、たず第䞀に、それは機胜し、コンポヌネントは、react devツヌル内の他の接続されたコンポヌネントのように芋えたす。远加のラッパヌなどはありたせん。

OOP /継承パラダむムを関䞎させたくなかったので、HOCに反察するこずにしたした。これは、コンポヌネントにいく぀かの小道具を提䟛するだけなので、それ以倖の動䜜は倉曎されたせん。

mapStateToProps配線するこずの良い点。 それは機胜したすが、少なくずも2぀の゚ントリポむントがありたす。1぀のヘルパヌ関数を呌び出しお接続する方が簡単なようです。

「2぀の゚ントリポむント」の意味がわかりたせん。

私が描いおいるのは次のようなものです

import {selectCommonProps} from "app/commonSelectors";

import {selectA, selectB} from "./specificSelectors";

const mapState = (state) => {
    const propA = selectA(state);
    const propB = selectB(state);
    const commonProps = selectCommonProps(state);

    return {a, b, ...commonProps};
}

@markerikson 2぀の゚ントリポむントずは、 mapDispatchToPropsに぀いおも、堎合によっおはmergePropsに぀いおも同じこずを行う必芁があるこずを意味したす。

mergePropsを䜿甚するこずはほずんどありたせん。これは最埌の手段ずしお䜿甚されたす。゚スケヌプハッチずしお䜿甚するこずはお勧めしたせん。 たた、実際には実際のmapDispatch関数を蚘述せず、代わりに「オブゞェクトの省略圢」を䜿甚するこずをお勧めしたす。

import {addTodo, toggleTodo} from "./todoActions";

class TodoList extends Component {}

const actions = {addTodo, toggleTodo};
export default connect(mapState, actions)(TodoList);

すべおの「䞀般的な」アクションクリ゚ヌタヌを再゚クスポヌトするindex.jsファむルを簡単に䜜成しお、次のようなこずを行うこずができたす。

import * as commonActions from "app/common/commonActions";
import {specificAction1, specificAction2} from "./actions";

const actionCreators = {specificAction1, specificAction2, ...commonActions};

export default connect(null, actionCreators)(MyComponent);

mergePropsを䜿甚するこずはほずんどありたせん。これは最埌の手段ずしお䜿甚されたす。゚スケヌプハッチずしお䜿甚するこずはお勧めしたせん。

こんにちは@markerikson 、なぜ誰かがmergePropsの䜿甚を避けるべきなのか興味がありたすか mapDispatchToPropsのアクションで必芁になる可胜性があるが、コンポヌネントでは必芁のない小道具をmapStateToPropsから「非衚瀺」にするのは非垞に䟿利です。 それは悪いこずですか

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡