Redux: Как создать общий список в качестве редуктора и усилителя компонентов?

Созданный на 30 сент. 2015  ·  50Комментарии  ·  Источник: reduxjs/redux

Привет. Что было бы хорошим способом расширить пример счетчика до динамического списка независимых счетчиков?

Под динамическим я подразумеваю, что в UI в конце списка счетчиков будут кнопки + и - для добавления нового счетчика в список или удаления последнего.

В идеале редуктор счетчика и компонент должны оставаться такими, какие они есть. Как можно создать обобщенное хранилище списков + компонент для сбора любых сущностей? Можно ли еще больше обобщить хранилище списка + компонент, чтобы взять как счетчики, так и элементы задач из примера 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 , чтобы иметь как список счетчиков, так и список задач. И если бы в этом был смысл. Но я обязательно попробую.

Мне все еще интересно, могу ли я повторно использовать функциональность списка через combReducers, чтобы иметь как список счетчиков, так и список элементов todo. И если бы в этом был смысл. Но я обязательно попробую.

Да, полностью:

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) { ... } } — и бам! промежуточное ПО 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);

cc @acdlite — вот пример того, как текущее промежуточное ПО + дизайн React Redux несколько ломаются.
Мы можем объявить это как wontfix, но, может быть, вы захотите посмотреть, есть ли способ обойти это.

Было бы
https://github.com/erikras/multireducer/ помочь?

Я экспериментировал с использованием контейнера службы (IoC) для React и вчера создал этот тестовый репозиторий: https://github.com/magnusjt/react-ioc .

Я думаю, что это потенциально может решить часть проблемы, поскольку вы можете передать создателя действия счетчику без ведома CounterList. Это возможно, потому что создатель действия находится в конструкторе Counter, а не в свойствах.

Для каждого нового компонента счетчика, который вы создаете, вы можете передать другого создателя действия (возможно, привязав значение индекса к создателю действия). Конечно, у вас все еще есть проблема с передачей данных на счетчик. Я еще не уверен, что это можно решить с помощью сервисного контейнера.

@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(), потому что функция (dispatch, 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) и создавать общие редукторы, используя какую-то композицию Store_

Это слишком сложно и не стоит ИМО. Если вы хотите сделать это таким образом, просто не используйте промежуточное программное обеспечение (или используйте какую-либо альтернативную реализацию applyMiddleware ), и все готово.

Кроме того, я закрываю, потому что мы не планируем действовать по этому поводу.

@gaearon ради обсуждения:
пример кода, который вы приложили к этой фиксации: https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4

В случае, если у меня есть 2 списка счетчиков (или даже отдельные «модели»), отправка 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 ? Разве вам не нужно префикс типов действий или что-то в этом роде?

В случае, если у меня есть 2 списка счетчиков (или даже отдельные «модели»), диспетчеризация 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 вам нужно снова обернуть его в список, чтобы действия не конфликтовали для двух списков, точно так же, как мы сделали со списком счетчиков.

@ccorcos

чтобы снова обернуть его в список

завернуть что именно?

@ccorcos
Я загрузил пример здесь: 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 просто отображает их рядом, не оборачивая функции диспетчеризации, поэтому у вас есть коллизии действий.

@ccorcos

Спасибо. Итак, в заключение, очевидно, что никакого «волшебства» не происходит, действиям нужна вся информация, необходимая им, чтобы сообщить редюсеру, над каким экземпляром выполнять действие. Если это listOf(listOf(counter)) , для действия потребуются оба индекса массива 2d. listOf(listOf(counter)) может работать, но наличие всех счетчиков в одном плоском списке, проиндексированном уникальным идентификатором, который является единственным, что передается в действии, кажется более гибким.

Похоже, что единственный способ создать гибкое и сложное приложение Redux — это проиндексировать все объекты в системе по идентификатору в магазине. Любой другой более простой подход, который иногда приводится в примерах, быстро достигает своих пределов. Этот индекс является почти зеркалом реляционной БД.

для действия потребуются оба индекса массива 2d

это звучит так, как будто вы думаете, а действие выглядит так:

{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 , где вложенные действия означают странность?

Как промежуточное ПО узнает, следует ли интерпретировать действие как есть или искать вложенные действия?

Ага.

@gaearon

Привет.

Я не уверен, что понял решение по этому вопросу и был бы очень признателен за простой ответ.

Это _не использовать промежуточное ПО (и, в основном, большую часть экосистемы Redux), если у вас есть несколько копий компонента на одной странице_? Я также не видел, ответил ли кто-нибудь на предложение мультиредуктора .

Любое разъяснение поможет.

Нет, это не разрешение. Поток не связан с наличием нескольких идентификаторов компонента на странице. Чтобы реализовать это, вы можете просто передать идентификатор в действии. Тема была посвящена написанию универсальной функции для этого. Что, к сожалению, противоречит концепции промежуточного программного обеспечения.

Спасибо.

Всем привет,
Возможно, я решил эту проблему, однако я предпочитаю использовать deku вместо react ^^

Я был бы счастлив, если бы кто-нибудь рассказал мне об этом эксперименте, особенно об идее taskMiddleware . @gaearon , у тебя есть время, чтобы проверить это? :язык:

Список счетчиков

Спасибо!

Просто хочу уточнить, что ни одно из этих решений не предназначено для обработки начального состояния.
Можно ли как-то добиться этого @gaearon ? Разрешить приведенному выше редуктору списка иметь начальный список счетчиков?

Почему это будет проблематично? Я предполагаю, что вы могли бы вызвать дочерние редукторы с состоянием undefined и использовать эти значения.

Когда хранилище инициализируется с первой отправкой, мне нужно (для моей внутренней логики) иметь начальное состояние для этого списка, и это должен быть предопределенный список счетчиков (тип элементов списка)

Что-то вроде этого?

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 и связанных с ним технологий, и всегда есть люди, желающие поговорить и помочь.

@markerikson Спасибо. Я постараюсь получить помощь от Reactiflux в Discord.

Следуя этому:
Я нашел масштабируемый способ повторного использования редьюсеров в своем проекте, даже повторного использования редьюсеров внутри редюсеров.

Парадигма пространства имен

Его концепция заключается в том, что действия, которые действуют на один из многих «частичных» редукторов, содержат свойство «пространства имен», которое определяет, какой редуктор будет обрабатывать действие, в отличие от _всех_ редюсеров, обрабатывающих его, потому что они слушают одно и то же действие type (пример в https://github.com/reactjs/redux/issues/822#issuecomment-172958967)

Однако действия, которые не содержат пространства имен, по-прежнему будут распространяться на все частичные редьюсеры в других редьюсерах.

Скажем, у вас есть частичный редуктор A с начальным состоянием A(undefined, {}) === Sa и редьюсер 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 не является хорошим решением, потому что он не допускает неопределенного количества редюсеров, вместо этого он предварительно просит вас создать список статических редукторов.
Вместо этого я создал небольшой проект для решения этой проблемы, используя UUID для каждого экземпляра компонентов и уникальное состояние для каждого UUID.
https://github.com/eloytoro/реакт-редукс-uuid

Была ли эта страница полезной?
0 / 5 - 0 рейтинги