Redux: الاستخدام الموصى به للاتصال ()

تم إنشاؤها على ٧ أغسطس ٢٠١٥  ·  34تعليقات  ·  مصدر: reduxjs/redux

مرحبًا gaearon -

بعد ذلك ، نلف المكونات التي نريد توصيلها بـ Redux بوظيفة connect () من رد الفعل-redux. حاول القيام بذلك فقط لمكون المستوى الأعلى ، أو معالجات المسار. بينما يمكنك تقنيًا توصيل () أي مكون في تطبيقك بمتجر Redux ، تجنب القيام بذلك بعمق شديد لأنه سيجعل تتبع تدفق البيانات أكثر صعوبة.

كانت سلاسل الدعامة العميقة إحدى مشكلات React التي قادتني إلى استخدام Flux ؛ تتمثل المقايضة لتسهيل تتبع تدفق البيانات في أنك بحاجة إلى إعداد / صيانة / تتبع تدفق العناصر ، والأهم من ذلك ، يحتاج الآباء إلى معرفة متطلبات البيانات الخاصة بأطفالهم. من تجربتي ، لست مقتنعًا بأن هذا النهج أفضل ، لكنني أشعر بالفضول لسماع رأيك: ابتسم:

docs

التعليق الأكثر فائدة

هل هناك مشاكل أخرى غير تتبع تدفق البيانات عند الاتصال () - إدخال الكثير من المكونات؟ هل سيكون هناك عقوبة الأداء على سبيل المثال؟

لا ، إنه عكس ذلك تمامًا: تحصل على أداء أفضل بالتخلي عن التدفق من أعلى إلى أسفل.

ال 34 كومينتر

أتفق بشدة مع برنت. كان هذا كثيرًا من التفكير الأساسي وراء شيء مثل Relay ويؤدي إلى تماسك أعلى.

ربما ينبغي أن نقول فقط "الناس لديهم تفضيلات مختلفة هنا".

gaearon - ربما يكون قسمًا عن المفاضلات يتضمن

رائع. دعنا نبقيه مفتوحًا ونعيد النظر بعد استقرار المستندات الأولية.

يبدو جيداgaearon! :) بينغ لي عندما ترغب في زيارة مرة أخرى

أنا جديد جدًا على Redux ، لكن هذه كانت نقطة ألم كبيرة بالنسبة لي خلال العام الماضي ، حيث أنشأت تطبيقًا حيث قمنا بتغيير بنية البيانات كثيرًا. لذا يجب أن أقول إنني أتفق حقًا مع brentvatne في هذا الأمر.

هل هناك مشاكل أخرى غير تتبع تدفق البيانات عند الاتصال () - إدخال الكثير من المكونات؟ هل سيكون هناك عقوبة الأداء على سبيل المثال؟

هل هناك مشاكل أخرى غير تتبع تدفق البيانات عند الاتصال () - إدخال الكثير من المكونات؟ هل سيكون هناك عقوبة الأداء على سبيل المثال؟

لا ، إنه عكس ذلك تمامًا: تحصل على أداء أفضل بالتخلي عن التدفق من أعلى إلى أسفل.

لأنه يمكنك تجنب عمليات إعادة تصيير غير ضرورية لمكونات المستوى المتوسط؟

نعم فعلا.

للإضافة إلى المناقشة: أقوم في الغالب بتحديد استخدامي للاتصال بمكونات المسار. عندما يكون لدي صفحة يمكن أن تستخدم مكونًا ذكيًا إضافيًا (مثل نموذج مشروط) ، كان الحل البديل الخاص بي هو تمرير العنصر أو العقدة ، وجعل المكون الغبي يعرضها. هذا يعني أن لديك المزيد من النماذج المعيارية ، لكن اختبار المكون الغبي لا يزال سهلاً. ما زلت أجرب هذا ، لكنني أعتقد أن هذا قد يكون أفضل طريقة لتكوين مكونات ذكية دون التخلي عن سهولة الاختبار.

لإعطاء مثال تقريبي:

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) يجعل الأمور أسهل في القراءة / الفهم و 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')
);

آمل أن يكون كل هذا واضحًا جدًا ، ولكن يسعدني الخوض في مزيد من التفاصيل وإضافة تعليقات للتوضيح إذا لزم الأمر.

كود إضافي

ستلاحظ على الأرجح عددًا قليلاً من وظائف الأداة المساعدة التي يتم استيرادها في هذه الأمثلة. هذه وحدات صغيرة (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 هو أنك إذا نظرت إلى كل شيء باستخدام react-devtools ، فسترى شيئًا كهذا ( Branches يتم تغليفه بـ ProvideBranches(theme,packageList,sources) ):
2015-08-15-010648_1366x768_scrot

بدلاً من هذا (الذي ستراه مع connect الأصلي ، حيث يتم تغليف Branches بـ Connect(Branches) ):
2015-08-14-220140_1366x768_scrot

محددات

لقد بدأت فقط في التعرف على redux منذ يومين ، لذلك ليس لدي أي فكرة بصراحة عن القيود (إن وجدت) الموجودة نتيجة لهذا النهج. بعيدًا عن رأسي ، لا يمكنني التفكير في أي شيء ، ويبدو أن كل شيء يعمل بشكل مثالي مع حالات الاستخدام الخاصة بي ، ولكن ربما يمكن لشخص أكثر خبرة أن يتناغم.

لقد توصلت إلى هذا النهج بالذات لأنني أحب حقًا فكرة وجود مكونات "غبية" يمكن تخصيص أي مزود لها. إنه يفرض فصلًا حقيقيًا للمخاوف ، ويسمح لأي عدد من مقدمي الخدمات القابلة للتبديل بسهولة كوحدات نمطية.

علاوة على ذلك ، إذا كان gaearon يحب هذا النهج ويعتقد أنه يقع ضمن نطاق react-redux ، فسأكون سعيدًا بتقديم PR مع الإضافات. إذا لم يكن الأمر كذلك ، فمن المحتمل أن أقوم بإنشاء ونشر react-redux-providers ، ثم في النهاية أنشر أمثلة لمقدمي الخدمة (على سبيل المثال ، أشياء مثل react-redux-provide-toggle ) جنبًا إلى جنب مع بعض الأمثلة من العالم الحقيقي.

لأنه يمكنك تجنب عمليات إعادة تصيير غير ضرورية لمكونات المستوى المتوسط؟

نعم

الاتجاه الذي اتخذناه الآن هو أننا سنستخدم Immutable.js لحالتنا (كل من حالة التطبيق وحالة واجهة المستخدم ؛ نحتفظ ببعض حالات واجهة المستخدم في المكونات) ، ثم نستخدم PureRenderMixin لجميع مكوناتنا. ألن يؤدي ذلك إلى القضاء على عقوبة الأداء لاستخدام التدفق من أعلى إلى أسفل؟

تحرير: لقد أدركت للتو أنه لن يلغي عقوبة الأداء هذه للمكونات ذات المستوى المتوسط ​​إذا كان أحد مكونات المستوى الأدنى لا يزال بحاجة إلى إعادة العرض ، ولكن لا يزال يتعين عليه التخلص من الكثير من النفقات العامة. أي خبرة في ذلك؟

@ danmaz74 لقد

eldh شكرا على التحديث ، هذا منطقي. سنضعها في الاعتبار أثناء الاستمرار :)

أي تحديثات على هذا؟ مثال timbur منطقي تمامًا بالنسبة لي أثناء ممارسة الاتصال الفردي التي لا أفهمها تمامًا. سأكون مهتمًا بالحجة المضادة ، أي "الناس لديهم تفضيلات مختلفة هنا".

قد يحتاج استخدام وصلة واحدة إلى وظيفة mapStateToProps ضخمة لتحويل الحالة على سبيل المثال إلى تسلسل هرمي عميق مكون من 10 مكونات ...

قد يحتاج استخدام وصلة واحدة إلى وظيفة mapStateToProps ضخمة

لا أحد يدعو إلى اتصال واحد.

بعد ذلك ، نلف المكونات التي نريد توصيلها بـ Redux بوظيفة connect () من رد الفعل-redux. حاول القيام بذلك فقط لمكون المستوى الأعلى ، أو معالجات المسار . بينما يمكنك تقنيًا توصيل () أي مكون في تطبيقك بمتجر Redux ، تجنب القيام بذلك بعمق شديد لأنه سيجعل تتبع تدفق البيانات أكثر صعوبة.

يشير "المنفرد" فقط إلى التطبيقات الصغيرة مثل التطبيق الذي أنشأناه في المثال. لا تتردد في تعديل المستندات لتوضيح ذلك بشكل أفضل. أنا الآن مشغول بمشاريع أخرى ، لذا من فضلك لا تتوقع أن تحصل هذه المشكلة على أي حركة إلا إذا قام شخص ما بعمل علاقات عامة. يمكنك أن تفعل ذلك أيضا.

شكرا على التوضيح.

أخيرًا توصلت إلى إصدار react-redux-provide . تحقق من ذلك هنا . سأقوم أيضًا بإصدار مجموعة من الأشياء الأخرى في غضون الأيام / الأسابيع القليلة القادمة.

هذا يبدو جيدا جدا ، شكرا!

لقد بدأت للتو مشروع إعادة إنشاء جديد ، وأنا أستخدم الاتصال بالمكونات "الذكية" - وهذا منطقي أكثر بالنسبة لي ، وإذا كانت هناك فائدة مثالية ، فهناك فوز إضافي. على العكس من ذلك ، إذا قمت بتحويل كل عناصر التحكم إلى التطبيق أو أجهزة التوجيه الرئيسية ، فهناك خسارة كاملة لـ SRP - ما هو حجم تطبيقك قبل البدء في تفكيك الأشياء؟

حتى أنني أفكر في تنظيم العناصر ذات الصلة في مجلد مكون - أي. ضع مخفضًا بجانب مكونه الرئيسي ، إلخ.

في النهاية ، أعتقد أن إعادة التدفق / التدفق هي فائدة كبيرة للحالة التي يمكن التنبؤ بها ، ولكن مثل هذا التحول العقلي من mv القياسي - أيًا كان ما جعل تطوير تطبيق UI بسيطًا ويمكن لأي شخص الوصول إليه ، فسيتم التخلص من التدفق في النهاية وسننتقل بالعودة إلى شيء يشبه mv *.

تم إصلاح هذا في # 1285.

لم نعد نشجع إنشاء مكونات حاوية في المستندات المحدثة.
http://redux.js.org/docs/basics/UsageWithReact.html

مرحبًا ، لقد كتبت عن بعض الأشياء التي قد تساعد هنا. :)

https://medium.com/@timbur/react-auto-redux-Provider-and-replicators-c4e35a39f1

أعتقد أن https://github.com/reactjs/redux/issues/419#issuecomment -183769392 يمكن أن يساعد في # 1353 أيضًا.

timbur مقالة رهيبة! هل يمكنك أيضًا مشاركة أفكارك حول هذا السؤال: https://github.com/reactjs/react-redux/issues/278

لست متأكدًا مما إذا كان هذا واضحًا بشكل صارخ ، لكنني أشعر أنه يستحق إضافته هنا من أجل الوضوح ، حيث أعتقد أن الكثير مما قيل ربما يكون مجردًا مجردة إلى حد ما بالنسبة لشخص جديد يعاود الظهور في هذا الموضوع.

عندما بدأت في استخدام redux لأول مرة ، قمت بنقط "حاويات" (مكونات متصلة) بشكل مضلل داخل "مكونات" (مجرد غبية قديمة بسيطة) لأنني اعتقدت أن تطبيقي لن يتطلب الكثير من الفصل. كم كنت مخطئا. عندما أدركت أنني بحاجة إلى إعادة استخدام العديد من هذه المكونات ، كان علي القيام بقدر لا بأس به من إعادة البناء ونقل الكثير من الأشياء الذكية إلى أعلى الشجرة ولكن سرعان ما أصبح هذا صعبًا ؛ "مزود" في الجزء العلوي يوفر السياق (كما ينبغي) ولكن أيضًا بشكل أساسي كل التطبيق (وهو ما لا ينبغي أن يفعله).

الطريقة التي وجدت أن أفضل طريقة للتعامل معها هي الحصول على تسلسل هرمي للحاويات التي تتكون من مكونات غبية ، مع وجود مزود في الأعلى. يجب أن تعيش الحاويات داخل الحاويات الأخرى فقط . يجب أن يكون تطبيقك عبارة عن تسلسل هرمي للحاويات التي تستخدم المكونات لتقديم بياناتها.

غالبًا ما تكون الطريقة الجيدة للقيام بذلك باستخدام القوائم هي تمرير المعرفات عبر المكونات الذكية. تعد الحاجة إلى معرف علامة على أن شيئًا ما ينتمي إلى مجال التطبيق. لذلك ، حيثما أمكن ، خذ قوائم المعرفات في حاوية واحدة وقم بتمريرها إلى حاوية أخرى يمكنها استخدام المعرفات للحصول على المعلومات التي تريدها. داخل كل حاوية ، استخدم مكونًا لتقديم تلك المعلومات دون الحاجة إلى المعرف.

أدناه ، سخرت من مثال (معقد) حول كيفية تمرير الأجزاء المتصلة من التطبيق من خلال تسلسل هرمي للحاويات يستخدم المكونات لعرضها.

// 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 . يتم عرض كل منها من قبل بعضها البعض ، ولا يتم عرضها أبدًا داخل مكون ، ولا تحتوي على كود عرض أولي (باستثناء div لأسباب التفاف React).

في النهاية ، الطريقة التي أحب أن أفكر بها هي كما لو كنت ستنشئ في البداية شجرة من المكونات المتصلة التي تحدد بياناتك بطريقة مفيدة ستهتم بها واجهة المستخدم الخاصة بك. ومع ذلك ، لا توجد واجهة مستخدم على الإطلاق ، مجرد شجرة حالة تم تعيينها من خلال تسلسل هرمي للحاويات فقط . بعد ذلك ، تذهب من خلال وترش المكونات التقديمية داخل تلك الحاويات لعرض البيانات فعليًا بأي طريقة تريدها (أي في DOM). من الواضح أنه ليس من المفيد كتابة تطبيقك بهذه الطريقة ذات المسارين ، لكنني وجدت أنه من المفيد وضع تصور للنموذج مثل هذا.

هل هناك غرامة على الأداء (أو سبب معماري آخر) لاستخدام 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);

أنا كسول هنا ولم أجمع بين نسختين من mapStateToProps ونسختين من mapDispatchToProps لأنه يبقي التصريح بسيطًا. لكني أتساءل ما إذا كانت فكرة سيئة أن تدع connect تقوم بهذا العمل من أجلي.

timotgl : لم يعد دان ترسله مباشرة.

كنت على وشك أن أقول إن سؤالك تمت الإجابة عليه في إدخال الأسئلة الشائعة حول Redux حول توصيل مكونات متعددة ، ولكن يبدو أنك تسأل عن شيء مختلف - هل تعمد التفاف اتصالات متعددة حول مكون واحد؟ لا أستطيع أن أقول إنني رأيت أي شخص يفعل ذلك من قبل ، وقد رأيت _lot_ من كود Redux.

أنا شخصياً أقترح تجربة نهج مختلف. إما أن يكون لديك "خاصيات مشتركة" HOC أو شيء يمررها في الغالب إلى العناصر الفرعية ، أو استخدم المحددات لاسترداد الخاصيات الشائعة في الوظيفة mapState للمكون المحدد ودمجها مع الدعائم المحددة المطلوبة.

markerikson آسف لم أكن أعرف ذلك ، تمت إزالة الإشارة.

لذا أولاً وقبل كل شيء ، فهو يعمل ، ويظهر المكون مثل أي مكون آخر متصل في أدوات تطوير التفاعل ، ولا يحتوي على غلاف إضافي أو أي شيء من هذا القبيل.

لقد اتخذت قرارًا ضد HOC لأنني لم أرغب في تضمين نموذج OOP / الميراث ، نظرًا لأنه يتعلق فقط بتوفير المزيد من الدعائم للمكون ، فإن سلوكه لم يمس.

نقطة جيدة حول إجراء الأسلاك في mapStateToProps . قد ينجح ذلك ولكن لديّ نقطتي دخول على الأقل - يبدو أن استدعاء وظيفة مساعد واحدة للاتصال يبدو أكثر مباشرة.

لست متأكدًا مما تقصده بعبارة "نقطتا دخول".

ما أتخيله هو شيء من هذا القبيل:

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 من خلال نقطتي إدخال ، كنت أعني أنه سيتعين عليك فعل الشيء نفسه مقابل 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؟ أجد أنه من الملائم جدًا "إخفاء" الدعائم من mapStateToProps التي قد أحتاجها في أفعالي في mapDispatchToProps ولكن ليس في المكون. هل هذا شيئ سيئ؟

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات