Redux: كيفية إنشاء قائمة عامة كمخفض ومُحسِّن للمكوِّن؟

تم إنشاؤها على ٣٠ سبتمبر ٢٠١٥  ·  50تعليقات  ·  مصدر: reduxjs/redux

مرحبا. ما هي الطريقة الجيدة لتوسيع المثال المضاد إلى قائمة ديناميكية من العدادات المستقلة؟

أعني بالديناميكية أنه في واجهة المستخدم في نهاية قائمة العدادات سيكون هناك أزرار + و - لإضافة عداد جديد إلى القائمة أو إزالة آخر واحد.

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

سيكون من الرائع وجود شيء مثل هذا في الأمثلة.

examples

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

كيف يمكن للمرء إنشاء مخزن قائمة معمم + مكون لجمع أي نوع من الكيانات؟ هل سيكون من الممكن تعميم مخزن القائمة + المكون بشكل أكبر لأخذ كل من العدادات وعناصر المهام من مثال todomvc؟

نعم ، ممكن بالتأكيد.
قد ترغب في إنشاء مخفض ترتيب أعلى ومكون ذو ترتيب أعلى.

للحصول على مخفض ترتيب أعلى ، انظر النهج هنا:

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(لم أقم بتشغيله ولكن يجب أن يعمل مع الحد الأدنى من التغييرات.)

ال 50 كومينتر

أوافق على أن هذا مثال جيد.
يشبه Redux هنا Elm Architecture ، لذا لا تتردد في الحصول على الإلهام من الأمثلة هناك.

كيف يمكن للمرء إنشاء مخزن قائمة معمم + مكون لجمع أي نوع من الكيانات؟ هل سيكون من الممكن تعميم مخزن القائمة + المكون بشكل أكبر لأخذ كل من العدادات وعناصر المهام من مثال todomvc؟

نعم ، ممكن بالتأكيد.
قد ترغب في إنشاء مخفض ترتيب أعلى ومكون ذو ترتيب أعلى.

للحصول على مخفض ترتيب أعلى ، انظر النهج هنا:

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(لم أقم بتشغيله ولكن يجب أن يعمل مع الحد الأدنى من التغييرات.)

شكرًا - سأحاول تفعيل هذا النهج.

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

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

نعم ، تمامًا:

const reducer = combineReducers({
  counterList: list(counter, {
    add: 'ADD_COUNTER',
    remove: 'REMOVE_COUNTER'
  }),
  todoList: list(counter, {
    add: 'ADD_TODO',
    remove: 'REMOVE_TODO'
  }),
});

gaearon كيف يجب أن يحصل إجراء القائمة على index ؟

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

يمكنك رؤية حلنا هنا: https://github.com/Zeikko/redux/commit/6a222885c8c93950dbdd0d4cf3532cd99a32206c

أضفت بعض التعليقات إلى الالتزام لتسليط الضوء على الجزء الإشكالي.

سيكون من الرائع أن يكون لديك مخفض قائمة عام + مكون يمكنه عمل قوائم بقوائم العدادات.

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

هل يمكنك شرح ما تقصده بعبارة "إضافة الفهرس بطريقة عامة"؟ هل تقصد أنك تريد أن يكون لديك أسماء مختلفة لمفتاح index في الإجراء؟

أعتقد أنني فهمت ما تعنيه الآن.

آسف لا أستطيع التعليق كثيرا الآن. سأعود غدا.

أنا أفهم المشكلة الآن. ننظر في الامر.

سرعان ما اصطدمت ببعض القيود الملازمة لكيفية انحراف Redux عن هندسة Elm.
إنه لأمر مؤسف أنني لم أفهمهم من قبل!

  • عندما يتم تغليف عنصر الوقت ، فإنه يحتاج إلى قبول خاصية dispatch وليس عمليات الاسترجاعات. هذا يعني أنك ستحتاج إما إلى تجنب جعل صانعي الإجراءات الدعائم الخاصة بك واستخدام dispatch() في المكونات المكونة ، أو نحتاج إلى تقديم bindActionCreators(Component, actionCreators) => Component مثل connect() ولكن لا يتصل بالفعل بالمتجر ، بدلاً من ذلك فقط استبدل مكون Action-creator-as-props-wanting-component بـ this.props.dispatch -wanting-component.
  • لا يمكنك إرسال إجراءات تتطلب البرامج الوسيطة من المكونات المغلفة. هذا هو المشكله! إذا قمت بلف العداد بقائمة ، فلن يعد بإمكاني إرسال incrementAsync() لأن function (dispatch, getState) { ... } الذي أرسلته للتو تحول إلى { action: function (dispatch, getState) { ... } } بواسطة القائمة - و bam! البرمجيات الوسيطة thunk لم تعد تتعرف عليها.

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

ها هو الكود:

المكونات / Counter.js

import React, { Component, PropTypes } from 'react';
import { increment, incrementIfOdd, incrementAsync, decrement } from '../actions/counter';

class Counter extends Component {
  render() {
    const { dispatch, counter } = this.props;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={() => dispatch(increment())}>+</button>
        {' '}
        <button onClick={() => dispatch(decrement())}>-</button>
        {' '}
        <button onClick={() => dispatch(incrementIfOdd())}>Increment if odd</button>
        {' '}
        <button onClick={() => dispatch(incrementAsync())}>Increment async</button>
      </p>
    );
  }
}

Counter.propTypes = {
  dispatch: PropTypes.func.isRequired,
  counter: PropTypes.number.isRequired
};

export default Counter;

المكونات / list.js

import React, { Component, PropTypes } from 'react';
import { addToList, removeFromList, performInList } from '../actions/list';

export default function list(mapItemStateToProps) {
  return function (Item) {
    return class List extends Component {
      static propTypes = {
        dispatch: PropTypes.func.isRequired,
        items: PropTypes.array.isRequired
      };

      render() {
        const { dispatch, items } = this.props;
        return (
          <div>
            <button onClick={() =>
              dispatch(addToList())
            }>Add counter</button>

            <br />
            {items.length > 0 &&
              <button onClick={() =>
                dispatch(removeFromList(items.length - 1))
              }>Remove counter</button>
            }
            <br />
            {this.props.items.map((item, index) =>
              <Item {...mapItemStateToProps(item)}
                    key={index}
                    dispatch={action =>
                      dispatch(performInList(index, action))
                    } />
            )}
          </div>
        )
      }
    }
  };
}

الإجراءات / counter.js

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

export function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

export function decrement() {
  return {
    type: DECREMENT_COUNTER
  };
}

export function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

export function incrementAsync(delay = 1000) {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, delay);
  };
}

الإجراءات / list.js

export const ADD_TO_LIST = 'ADD_TO_LIST';
export const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST';
export const PERFORM_IN_LIST = 'PERFORM_IN_LIST';

export function addToList() {
  return {
    type: ADD_TO_LIST
  };
}

export function removeFromList(index) {
  return {
    type: REMOVE_FROM_LIST,
    index
  };
}

export function performInList(index, action) {
  return {
    type: PERFORM_IN_LIST,
    index,
    action
  };
}

مخفضات / counter.js

import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';

export default function counter(state = 0, action) {
  switch (action.type) {
  case INCREMENT_COUNTER:
    return state + 1;
  case DECREMENT_COUNTER:
    return state - 1;
  default:
    return state;
  }
}

مخفضات / list.js

import { ADD_TO_LIST, REMOVE_FROM_LIST, PERFORM_IN_LIST } from '../actions/list';

export default function list(reducer) {
  return function (state = [], action) {
    const {
      index,
      action: innerAction
    } = action;

    switch (action.type) {
    case ADD_TO_LIST:
      return [
        ...state,
        reducer(undefined, action)
      ];
    case REMOVE_FROM_LIST:
      return [
        ...state.slice(0, index),
        ...state.slice(index + 1)
      ];
    case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];
    default:
      return state;
    }
  }
}

مخفضات / index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);

const rootReducer = combineReducers({
  counterList
});

export default rootReducer;

حاويات / App.js

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

import Counter from '../components/Counter';
import list from '../components/list';

const CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(Counter);

export default connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

ccacdlite - إليك مثال على البرامج الوسيطة الحالية + تصميم React Redux الذي تم تفكيكه إلى حد ما.
يمكننا إعلان هذا على أنه wontfix ولكن ربما ترغب في إلقاء نظرة إذا كان هناك أي طريقة يمكننا من خلالها تفادي ذلك.

سيكون
https://github.com/erikras/multireducer/ مساعدة؟

لقد كنت أجرب استخدام حاوية خدمة (IoC) لـ React ، وأنشأت هذا الاختبار التجريبي بالأمس: https://github.com/magnusjt/react-ioc

أعتقد أنه من المحتمل أن يحل جزءًا من المشكلة ، حيث يمكنك تمرير منشئ الإجراء إلى Counter دون معرفة CounterList به. هذا ممكن لأن منشئ الإجراء ينتقل إلى الباني للعداد ، وليس في الدعائم.

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

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

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

في هذه التذكرة ، توصلت إلى طريقة لتداخل الأنواع:
https://github.com/rackt/redux/issues/897

لكنني أعتقد بشكل أساسي أكثر ، أنك سترغب في تداخل الإجراءات بالكامل ...

حسنًا ، لقد جربتها للتو.

لقد قمت بتبسيط الأمور قليلاً. إليك تطبيق العداد قبل القيام بهذه الأشياء الفاخرة:

https://github.com/ccorcos/redux-lifted-reducers/blob/80295a09c4d04654e6b36ecc8bc1bfac4ae821c7/index.js#L49

وهنا بعد:

https://github.com/ccorcos/redux-lifted-reducers/blob/1fdafe8ed29303822018cde4973fda3305b43bb6/index.js#L57

لست متأكدًا من أن "الرفع" هو المصطلح المناسب - أعلم أنه يعني شيئًا ما في البرمجة الوظيفية ، لكنني أشعر أنني بخير.

في الأساس ، من خلال رفع إجراء ، فأنت تقوم بتداخل إجراء داخل آخر.

const liftActionCreator = liftingAction => actionCreator => action => Object.assign({}, liftingAction, { nextAction: actionCreator(action) })

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

const liftReducer = liftingReducer => reducer => (state, action) => liftingReducer(state, action)((subState) => reducer(subState, action.nextAction))

بالنسبة لقائمة مخفض المكونات ، لدي إجراء يحدد المكون الذي ينطبق عليه الإجراء الفرعي.

// list actions
const LIST_INDEX = 'LIST_INDEX'
function actOnIndex(i) {
  return {
    type: LIST_INDEX,
    index: i
  }
}

ولدي "رتبة عالية" (مصطلح آخر رائع شعرت أنه صحيح ، هاها ؛) مخفض يطبق المخفض الفرعي على الحالة الفرعية المناسبة.

const list = (state=[], action) => (reduce) => {
  switch (action.type) {
    case LIST_INDEX:
      let nextState = state.slice(0)
      nextState[action.index] = reduce(nextState[action.index])
      return nextState
    default:
      return state;
  }
}

وكل ما تبقى هو "رفع" مخفض العد إلى مخفض القائمة.

const reducer = combineReducers({
  counts: liftReducer(list)(count)
});

والآن بالنسبة لقائمة العدادات ، نحتاج فقط إلى رفع الإجراءات أثناء تمريرها إلى العدادات.

class App extends Component {
  render() {
    const counters = [0,1,2,3,4].map((i) => {
      return (
        <Counter count={this.props.state.counts[i]}
                 increment={liftActionCreator(actOnIndex(i))(increment)}
                 decrement={liftActionCreator(actOnIndex(i))(decrement)}
                 dispatch={this.props.dispatch}
                 key={i}/>
      )
    })
    return (
      <div>
        {counters}
      </div>
    );
  }
}

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

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

ما تصفه يعرف باسم Elm Architecture. يرجى الاطلاع هنا: https://github.com/gaearon/react-elmish-example

يا صاح ، أنت دائمًا متقدم بخطوة! دش لي روابط لأشياء رائعة: +1:

لا يمكنك إرسال إجراءات تتطلب البرامج الوسيطة من المكونات المغلفة. هذا هو المشكله! إذا قمت بلف العداد بقائمة ، لم يعد بإمكاني إرسال incrementAsync () لأن الوظيفة (إرسال ، getState) {...} التي أرسلتها للتو تحولت إلى {action: function (dispatch، getState) {...}} بواسطة القائمة - وبام! البرمجيات الوسيطة thunk لم تعد تتعرف عليها.

gaearon ماذا عن هذا الحل؟ بدلاً من المخفض العام الذي يطلق على نفسه المخفض الفرعي مثل هذا

case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];

تزويد المتجر ببعض الطرق الخاصة dispatchTo(reducer, state, action, callback) والتي تعمل مثل dispatch باستثناء أنها ترسل مباشرة إلى مخفض الطفل (من خلال جميع البرامج الوسيطة المكونة) وإخطار رد الاتصال عند كل تعديل للحالة الفرعية

export default function list(reducer, dispatchTo) {
  return function (state = [], action) {
    ...
    case PERFORM_IN_LIST:
      dispatchTo(reducer, state[index], innerAction, newState =>
         [
           ...state.slice(0, index),
           newState,
           ...state.slice(index + 1)
        ]);
       default:
          return state;
    }
  }
}

لا أعرف ما إذا كان هذا ممكنًا في Redux. تتمثل الفكرة في تنفيذ dispatchTo باستخدام طريقة store.derive(reducer, state) داخلية والتي ستعيد مخزنًا فرعيًا لهذا الجزء من شجرة الحالة التي تم تكوينها باستخدام بعض الأدوات الوسيطة كمخزن الجذر. فمثلا

function dispatchTo(reducer, state, action, callback) {
  const childStore = store.derive(reducer, state)
  childStore.subscribe(() => setRootState( callback(getState() ))
  childStore.dispatch(action)
}

هذه مجرد فكرة كما قلت إنني لست على دراية بالأجزاء الداخلية لـ Redux ، لذا ربما فاتني شيء ما

تعديل
_ من المحتمل أن يكون هذا أمرًا محرجًا نظرًا لأنه من المفترض أن تكون طرق الاختزال متزامنة. جعل طريقة غير متزامنة يعني أن كل السلسلة يجب أن تكون غير متزامنة أيضًا.
ربما يكون الحل الأفضل هو عرض طريقة store.derive(reducer) مباشرة وإنشاء مخفضات عامة باستخدام نوع من تكوين المتجر_

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

أيضًا ، أنا أغلق لأننا لا نخطط للعمل على هذا.

gaearon من أجل المناقشة:
مثال الكود الذي أرفقته في هذا الالتزام: https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4

في حالة وجود قائمتين من العدادات (أو حتى "نماذج" منفصلة) ، فإن إرسال addToList سيضيف عنصرًا إلى كلتا القائمتين ، لأن أنواع الإجراءات هي نفسها.

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;

فكيف يساعد reducers/list هنا؟ ألا تحتاج إلى بادئة أنواع الإجراءات أو شيء من هذا القبيل؟

في حالة وجود قائمتين من العدادات (أو حتى "نماذج" منفصلة) ، فإن إرسال addToList سيضيف عنصرًا إلى كلتا القائمتين ، لأن أنواع الإجراءات هي نفسها.

يرجى إلقاء نظرة فاحصة على a83002aed8e36f901ebb5f139dd14ce9c2e4cab4. أعشاش الإجراءات. dispatch الذي يتم تمريره لأسفل من إجراءات التفاف مكون الحاوية إلى إجراء performInList . ثم يتم استرداد الإجراء الداخلي في المخفض. هذا إلى حد كبير كيف يعمل Elm Architecture.

gaearon ربما أفتقد شيئًا ما ، ولكن مع المخفض الإضافي counterList2 المذكور أعلاه ، لا تزال واجهة المستخدم هذه تقوم بتحديث كلتا القائمتين في كل إجراء (وهو أمر متوقع وفقًا لكيفية بنائه ، ولكن ما هو الحل؟):

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;


// containers/App.js

import React from 'react';

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

import counter from '../components/Counter';
import list from '../components/list';

let CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList = connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

let CounterList2 = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList2 = connect(function mapStateToProps(state) {
  return {
    items: state.counterList2
  };
})(CounterList2);


export default class App extends React.Component {
  render() {
    return (
      <div>
        <CounterList />
        <CounterList2 />
      </div>
    )
  }
}

elado ، ستحتاج إلى لفها في قائمة مرة أخرى حتى لا تتعارض الإجراءات مع القائمتين ، تمامًا كما فعلنا مع قائمة العدادات

تضمين التغريدة

لفه في قائمة مرة أخرى

التفاف ماذا بالضبط؟

تضمين التغريدة
قمت بتحميل المثال هنا: http://elado.s3-website-us-west-1.amazonaws.com/redux-counter/
لديها خرائط المصدر

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

لذلك لا أعرف وظائف Redux الفعلية جيدًا ، لكنني على دراية كبيرة بـ Elm.

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

لجعل التجريد يعمل بشكل جيد حتى لا يكون لدينا تضاربات إجرائية ، عندما يمرر مكون "listOf" الإرسال إلى توابعه ، فإنه في الواقع يمرر وظيفة تغلف الإجراء بتنسيق يمكن لمكون القائمة فهمه.

children.map((child, i) => {
  childDispatch = (action) => dispatch({action, index: i})
  // ...

الآن يمكنك إنشاء listOf(counter) أو listOf(listOf(counter)) وإذا كنت تريد إنشاء مكون يسمى pairOf ، فأنت بحاجة إلى التأكد من التفاف الإجراءات عند تمريرها. في الوقت الحالي ، يقوم المكون App بعرضها جنبًا إلى جنب دون تغليف وظائف الإرسال ، وبالتالي يكون لديك تضارب في الإجراءات.

تضمين التغريدة

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

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

سيحتاج الإجراء كلا مؤشري المصفوفة ثنائية الأبعاد

يبدو أنك تفكر والعمل يبدو مثل:

{type: 'increment', index:[0 5]}

ولكن يجب أن يبدو بالفعل كما يلي:

{type:'child', index: 0, action: {type: 'child', index: 5, action: {type: 'increment'}}}

بهذه الطريقة يمكنك عمل listOf(listOf(listOf(...listOf(counter)...))) إلى الأبد!

كل هذا يأتي من Elm ، راجع للشغل. تحقق من دروس الهندسة المعمارية Elm

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

كيف ستعرف البرمجيات الوسيطة ما إذا كانت ستفسر الإجراء كما هو ، أو تبحث عن الإجراءات المتداخلة؟

أرى.

تضمين التغريدة

مرحبا.

لست متأكدًا من فهمي للقرار المتعلق بالمسألة وسأقدر حقًا إجابة بسيطة.

هل _ لا تستخدم البرامج الوسيطة (ومعظم نظام Redux البيئي بشكل أساسي) ، إذا كان لديك نسخ متعددة من المكون في نفس الصفحة _؟ كما أنني لم أرَ ما إذا كان أحدهم قد استجاب لاقتراح متعدد المستخدمين .

أي توضيح من شأنه أن يساعد.

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

شكرا لك.

أهلا بالجميع،
ربما قمت بحل هذه المشكلة ، لكنني أفضل استخدام deku على react ^^

سأكون سعيدًا إذا أعطاني أحدهم رؤيته حول هذه التجربة ، خاصةً حول فكرة taskMiddleware . gaearon هل لديك بعض الوقت للتحقق من ذلك؟ :لسان:

قائمة العدادات

شكرا!

أريد فقط توضيح أن أيًا من هذه الحلول لا يهدف إلى التعامل مع حالة أولية.
هل يمكن تحقيق ذلك بطريقة ما gaearon ؟ السماح لمخفض القائمة أعلاه بالحصول على قائمة أولية بالعدادات؟

لماذا قد يكون ذلك إشكالية؟ أتخيل أنه يمكنك استدعاء مخفضات الأطفال بـ undefined state واستخدام هذه القيم.

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

شيء من هذا القبيل؟

export default function list(reducer) {
  return function (state = [

    // e.g. 2 counters with default values
    reducer(undefined, {}),
    reducer(undefined, {}),

      ], action) {
    const {
      index,
      action: innerAction
    } = action;
   // ...
  }
}

يمكنك جعل هذه حجة لـ list أيضًا إذا كنت ترغب في ذلك

يبدو هذا معقدًا حقًا ، لذا يمكنني الحصول على العديد من نفس المكون على الصفحة.

ما زلت ألتف رأسي حول Redux ، لكن إنشاء متاجر متعددة يبدو أبسط بكثير ، حتى لو لم يكن نمط استخدام Redux موصى به.

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

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

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

تضمين التغريدة سأحاول الحصول على بعض المساعدة من Reactiflux على Discord.

متابعة هذا:
لقد وجدت طريقة قابلة للتطوير لإعادة استخدام المخفضات في مشروعي ، حتى إعادة استخدام المخفضات داخل المخفضات.

نموذج مساحة الاسم

إنه مفهوم أن الإجراءات التي تعمل على واحدة من العديد من المخفضات "الجزئية" لها خاصية "مساحة الاسم" التي تحدد أي مخفض سيتعامل مع الإجراء على عكس _جميع_ المخفضات التي تتعامل معها لأنهم يستمعون إلى نفس الإجراء type (مثال في https://github.com/reactjs/redux/issues/822#issuecomment-172958967)

ومع ذلك ، سيستمر نشر الإجراءات التي لا تحتوي على مساحة اسم لجميع المخفضات الجزئية داخل مخفضات أخرى

لنفترض أن لديك المخفض الجزئي A وبحالة أولية A(undefined, {}) === Sa و Reducer B بالحالة الأولية B(undefined, {}) === { a1: Sa, a2: Sa } حيث المفاتيح a1 و a2 يمثلان حالات من A .

إجراء مع مساحة اسم ['a1'] (* دائمًا ما يتم ترتيب مصفوفات من السلاسل التي تشبه مفتاح الحالة للمخفض الجزئي) عند B ستؤدي إلى النتيجة التالية

const action = {
  type: UNIQUE_ID,
  namespace: ['a1']
};

B(undefined, action) == { a1: A(undefined, action*), a2: Sa }

والمثال المضاد لعمل بدون مساحة اسم

const action = {
  type: UNIQUE_ID
};


B(undefined, action) == { a1: A(undefined, action), a2: A(undefined, action) }

تحفظات

  • إذا كان A لا يتعامل مع الإجراء المحدد (يستنفد المخفض) فإنه يجب أن يعيد نفس الحالة ، وهذا يعني أنه بالنسبة للإجراء p لا يمكن التعامل معه من أجل المخفض A يجب أن تكون نتيجة B(undefined, p) { a1: Sa, a2: Sa } وهي بالمناسبة نفس الحالة الأولية لـ B
  • يجب تجريد الإجراء الذي تم تمريره إلى المخفضات الجزئية (المشار إليها بـ action* أعلاه) من مساحة الاسم المستخدمة لتضييق النطاق. لذلك إذا تم تمرير الإجراء إلى B كان { type: UNIQUE_ID, namespace: ['a1'] } ، فإن الإجراء الذي تم تمريره إلى A هو { type: UNIQUE_ID, namespace: [] }
  • من أجل تحقيق هذا الهيكل ، يجب على جميع المخفضات في المتجر التعامل مع إجراءات تباعد الأسماء ، إذا تم استيفاء هذه النقطة ، فلا يوجد حد لعدد مساحات الأسماء التي تستخدمها وعدد المخفضات الجزئية التي يمكنك تداخلها

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

(state = initialState, { ...action, namespace = [] }) => {
    var partialAction = { ...action, namespace: namespace.slice(1) };
    var newState;
    if (reducerCanHandleAction(reducer, action) and namespaceExistsInState(namespace, state)) {
        // apply the action to the matching partial reducer
        newState = {
            ...state,
            [namespace]: partialReducers[namespace](state[namespace], partialAction)
        };
    } else if (reducerCantHandleAction(reducer, action) {
        // apply the action to all partial reducers
        newState = Object.assign(
            {},
            state,
            ...Object.keys(partialReducers).map(
                namespace => partialReducers[namespace](state[namespace], action)
            )
        );
    } else {
        // can't handle the action
        return state;
    }

    return reducer(newState, action);
}

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

قد أتأخر قليلاً عن اللعبة لكني كتبت بعض مخفضات الأغراض العامة التي قد تساعد:
https://gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1

أعتقد أن mutilreducer هو تطبيق رائع

jeffhtli multireducer ليس حلاً جيدًا لأنه لا يسمح بكمية غير محددة من المخفضات ، بدلاً من ذلك يطلب منك بشكل استباقي إنشاء قائمة مخفض ثابتة
بدلاً من ذلك ، قمت بإنشاء مشروع صغير لحل هذه المشكلة باستخدام UUIDs لكل مثيل من المكونات وحالة فريدة لكل UUID
https://github.com/eloytoro/react-redux-uid

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