Redux: ¿Cómo crear una lista genérica como reductor y potenciador de componentes?

Creado en 30 sept. 2015  ·  50Comentarios  ·  Fuente: reduxjs/redux

Hola. ¿Cuál sería una buena forma de extender el ejemplo del contador a una lista dinámica de contadores independientes?

Por dinámico quiero decir que en la interfaz de usuario al final de la lista de contadores habría botones + y - para agregar un nuevo contador a la lista o eliminar el último.

Idealmente, el contrarreductor y el componente permanecerían como están. ¿Cómo se crearía una tienda de lista generalizada + componente para recopilar cualquier tipo de entidades? ¿Sería posible generalizar aún más la lista store+component para tomar contadores y tareas pendientes del ejemplo todomvc ?

Sería genial tener algo como esto en los ejemplos.

examples

Comentario más útil

¿Cómo se crearía una tienda de lista generalizada + componente para recopilar cualquier tipo de entidades? ¿Sería posible generalizar aún más la lista store+component para tomar contadores y tareas pendientes del ejemplo de todomvc?

Sí, definitivamente posible.
Desearía crear un reductor de orden superior y un componente de orden superior.

Para un reductor de orden superior, vea el enfoque aquí:

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
});

(No lo he ejecutado, pero debería funcionar con cambios mínimos).

Todos 50 comentarios

Estoy de acuerdo en que es un buen ejemplo.
Redux es similar aquí a Elm Architecture , así que siéntase libre de inspirarse con los ejemplos allí.

¿Cómo se crearía una tienda de lista generalizada + componente para recopilar cualquier tipo de entidades? ¿Sería posible generalizar aún más la lista store+component para tomar contadores y tareas pendientes del ejemplo de todomvc?

Sí, definitivamente posible.
Desearía crear un reductor de orden superior y un componente de orden superior.

Para un reductor de orden superior, vea el enfoque aquí:

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
});

(No lo he ejecutado, pero debería funcionar con cambios mínimos).

Gracias, intentaré que este enfoque funcione.

Todavía me pregunto si puedo reutilizar la funcionalidad de la lista a través combineReducers para tener una lista de contadores y una lista de tareas pendientes. Y si eso tendría sentido. Pero definitivamente lo intentaré.

Todavía me pregunto si puedo reutilizar la funcionalidad de la lista a través de combineReducers para tener una lista de contadores y una lista de tareas pendientes. Y si eso tendría sentido. Pero definitivamente lo intentaré.

Si totalmente:

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

@gaearon , ¿cómo debería la acción de la lista obtener su index ?

Logramos crear un reductor de orden superior con sus instrucciones, pero estamos luchando con el componente de orden superior. Actualmente, nuestro componente no es lo suficientemente genérico para usarse con otros componentes que no sean Counter. Nuestro problema es cómo agregar el índice a las acciones de forma genérica.

Puede ver nuestra solución aquí: https://github.com/Zeikko/redux/commit/6a222885c8c93950dbdd0d4cf3532cd99a32206c

Agregué un comentario al compromiso para resaltar la parte problemática.

Sería genial tener un reductor de lista general + componente que pueda hacer una lista de listas de contadores.

Actualmente, nuestro componente no es lo suficientemente genérico para usarse con otros componentes que no sean Counter. Nuestro problema es cómo agregar el índice a las acciones de forma genérica.

¿Puede explicar qué quiere decir con "agregar el índice de forma genérica"? ¿Quiere decir que desea tener diferentes nombres para la tecla index en la acción?

Creo que entiendo lo que quieres decir ahora.

Lo siento, no puedo comentar mucho ahora. Volveré mañana.

Ahora entiendo el problema. Buscando dentro.

Rápidamente me topé con algunas limitaciones inherentes a cómo Redux se desvía de la arquitectura Elm.
¡Lástima que no los entendí antes!

  • En el momento en que se ajusta el componente, debe aceptar un accesorio dispatch y no devoluciones de llamada. Esto significa que debe evitar hacer que los creadores de acción sean sus accesorios y simplemente usar dispatch() en los componentes compuestos, o debemos introducir un bindActionCreators(Component, actionCreators) => Component que es como connect() pero en realidad no se conecta a la tienda, sino que simplemente reemplaza action-creator-as-props-wanting-component con this.props.dispatch -wanting-component.
  • No puede distribuir acciones que requieran middleware desde componentes empaquetados. ¡Esto es un fastidio! Si envuelvo el contador con una lista, ya no puedo despachar incrementAsync() porque function (dispatch, getState) { ... } que acabo de despachar se convierten en { action: function (dispatch, getState) { ... } } por la lista, ¡y bam! el middleware thunk ya no lo reconoce.

Puede haber soluciones no incómodas para esto, pero aún no las veo.
Por ahora, vea este compromiso como ejemplo (con las limitaciones descritas anteriormente).

Aquí está el código:

componentes/Contador.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;

componentes/lista.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>
        )
      }
    }
  };
}

acciones/contra.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);
  };
}

acciones/lista.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
  };
}

reductores/contra.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;
  }
}

reductores/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;
    }
  }
}

reductores/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;

contenedores/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 : aquí hay un ejemplo del diseño actual de middleware + React Redux que se descompone un poco.
Podemos declarar esto como no solucionado, pero tal vez le gustaría echar un vistazo si hay alguna forma en que podamos esquivar esto.

He estado experimentando con el uso de un contenedor de servicio (IoC) para React y ayer creé este repositorio de prueba: https://github.com/magnusjt/react-ioc

Creo que potencialmente podría resolver parte del problema, ya que puede pasar un creador de acciones a Counter sin que CounterList lo sepa. Esto es posible porque el creador de la acción va en el constructor de Counter, no en los accesorios.

Por cada nuevo componente Counter que cree, puede pasar un creador de acción diferente (quizás vinculando un valor de índice al creador de la acción). Por supuesto, todavía tiene el problema de llevar los datos al mostrador. Todavía no estoy seguro de si eso es algo que podría resolverse con un contenedor de servicios.

@gaearon , tu ejemplo me parece correcto. Tienes que pasar los creadores de acción y despachar todo el camino hacia abajo. De esta forma, puedes enaltecer acciones con funciones de orden superior.

Sin embargo, no estoy tan seguro de que su segundo punto sea necesario. Extrañará el middleware debido al nuevo formato de mensaje, pero un problema mayor con performInList es que ha limitado la abstracción a una sola lista. @pe3 mencionó una lista de listas de contadores. Para una abstracción arbitraria como esa, creo que necesitarás anidar acciones de alguna manera.

En este ticket, se me ocurre una forma de anidar los tipos:
https://github.com/rackt/redux/issues/897

Pero creo que, más fundamentalmente, querrás anidar las acciones por completo...

Ok, acabo de intentarlo.

Simplifiqué bastante las cosas. Aquí está la aplicación de contador antes de hacer estas cosas elegantes:

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

Y aquí está después:

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

No estoy seguro de que "elevar" sea el término adecuado; sé que significa algo en la programación funcional, pero me pareció bien.

Básicamente, al levantar una acción, anida una acción dentro de otra.

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

Y la acción anidada desaparece al levantar el reductor. El reductor de elevación básicamente aplica el subreductor (que se aplica parcialmente con la acción apropiada) a algún subestado.

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

Entonces, para el reductor de la lista de componentes, tengo una acción que especifica a qué componente en qué índice se aplica la acción secundaria.

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

Y tengo un reductor de "orden superior" (otro término elegante que se sintió bien, jaja;) que aplica el sub-reductor al sub-estado apropiado.

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;
  }
}

Y todo lo que queda es "levantar" el reductor de conteo al reductor de lista.

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

Y ahora para la lista de contadores, solo necesitamos levantar las acciones a medida que las pasamos a los contadores.

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>
    );
  }
}

Creo que esto podría formalizarse más con la jerga adecuada. Creo que las lentes también podrían usarse aquí para los reductores de orden alto, pero nunca las he usado con éxito, jaja.

Y retiro lo que dije en el último comentario: @gaearon tiene razón. Al anidar la acción de esta manera, perderá el middleware y tendrá que pasar el envío hasta el final para poder manipular a los creadores de la acción. Quizás para respaldar esto, Redux tendrá que aplicar todas las subacciones a través del middleware. Además, otro problema es inicializar el estado dentro de la lista...

Lo que estás describiendo se conoce como Elm Architecture. Consulte aquí: https://github.com/gaearon/react-elmish-example

Amigo, ¡siempre estás un paso por delante! Báñame con enlaces a cosas geniales :+1:

No puede distribuir acciones que requieran middleware desde componentes empaquetados. ¡Esto es un fastidio! Si envuelvo el contador con una lista, ya no puedo enviar incrementAsync() porque la función (dispatch, getState) { ... } que acabo de enviar se convierte en { action: function (dispatch, getState) { ... } } by la lista—y ¡bam! el middleware thunk ya no lo reconoce.

@gaearon ¿qué tal esta solución? en lugar del reductor genérico que se llama a sí mismo el reductor infantil como este

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

proporcione a la tienda algún método especial dispatchTo(reducer, state, action, callback) que actúe como dispatch excepto que se envíe directamente a un reductor secundario (a través de todos los middelwares configurados) y notifique la devolución de llamada en cada modificación de estado secundario

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;
    }
  }
}

No sé si esto es factible en Redux. Una idea es implementar dispatchTo usando algún método interno store.derive(reducer, state) que devolvería una tienda secundaria para esa parte del árbol de estado configurada con algunos middlewares como la tienda raíz. por ejemplo

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

Esto es solo una idea, como dije, no estoy al tanto de las partes internas de Redux, así que tal vez me perdí algo.

EDITAR
_probablemente esto va a ser incómodo ya que se supone que los métodos de reducción son sincrónicos. hacer que un método sea asíncrono implica que todo el encadenamiento también debe ser asíncrono.
Tal vez la mejor solución es exponer directamente el método store.derive(reducer) y construir reductores genéricos usando algún tipo de composición Store_

Eso es demasiada complicación y no vale la pena, en mi opinión. Si desea hacerlo de esta manera, simplemente no use el middleware (o use alguna implementación alternativa de applyMiddleware ) y ya está todo listo.

Además, estoy cerrando porque no planeamos actuar sobre esto.

@gaearon por el bien de la discusión:
el ejemplo de código que adjuntó en este compromiso: https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4

En caso de que tenga 2 listas de contadores (o incluso 'modelos' separados), enviar addToList agregaría elementos a ambas listas, porque los tipos de acción son los mismos.

// 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;

Entonces, ¿cómo ayuda el reducers/list aquí? ¿No necesitas prefijar tipos de acción o algo así?

En caso de que tenga 2 listas de contadores (o incluso 'modelos' separados), enviar addToList agregaría elementos a ambas listas, porque los tipos de acción son los mismos.

Eche un vistazo de cerca a a83002aed8e36f901ebb5f139dd14ce9c2e4cab4. Anida acciones. dispatch que se transmite desde el componente contenedor envuelve acciones en performInList acción. Entonces la acción interior se recupera en el reductor. Así es como funciona Elm Architecture.

@gaearon tal vez me estoy perdiendo algo, pero junto con el reductor adicional counterList2 mencionado anteriormente, esta interfaz de usuario aún actualiza ambas listas en cada acción (lo cual se espera de acuerdo con la forma en que se construye, pero ¿cuál es la solución?):

// 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 deberá envolverlo en una lista nuevamente para que las acciones no coincidan en las dos listas, de la misma manera que lo hicimos con la lista de contadores

@ccorcos

para envolverlo en una lista de nuevo

envolver qué exactamente?

@ccorcos
Subí el ejemplo aquí: http://elado.s3-website-us-west-1.amazonaws.com/redux-counter/
Tiene mapas fuente.

Todavía no estoy completamente seguro de lo que quisiste decir. Como dije, el comportamiento actual es el esperado porque los nombres de las acciones son los mismos y no hay ninguna indicación en el creador de la acción en qué lista realizarla, por lo que ejecuta la acción en todos los reductores, lo que finalmente afecta a ambas listas.

Así que no conozco muy bien las funciones reales de Redux, pero estoy muy familiarizado con elm.

En este nuevo ejemplo, no vinculamos a los creadores de acciones en el nivel superior. En su lugar, pasamos la función de despacho a los componentes inferiores y esos componentes inferiores pueden pasar una acción a la función de despacho.

Para que la abstracción funcione bien y no tengamos colisiones de acciones, cuando el componente "listOf" pasa el envío a sus hijos, en realidad pasa una función que envuelve la acción en un formato que el componente de la lista puede entender.

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

Así que ahora puede componer listOf(counter) o listOf(listOf(counter)) y si desea crear un componente llamado pairOf , debe asegurarse de ajustar las acciones cuando las transmita. En este momento, el componente App simplemente los representa uno al lado del otro sin envolver las funciones de envío, por lo que tiene colisiones de acción.

@ccorcos

Gracias. Entonces, para concluir, obviamente no está sucediendo "magia", las acciones necesitan toda la información que necesitan para decirle al reductor en qué instancia realizar la acción. Si es un listOf(listOf(counter)) la acción necesitará ambos índices de la matriz 2d. listOf(listOf(counter)) puede funcionar, pero tener todos los contadores en una sola lista plana indexada por ID único, que es lo único que se pasa en una acción, parece más flexible.

Parece que la única forma de crear una aplicación Redux flexible y compleja es tener todas las entidades en el sistema indexadas por ID en la tienda. Cualquier otro enfoque más simple, que a veces se da en ejemplos, alcanzará sus límites rápidamente. Este índice es casi un espejo de una base de datos relacional.

la acción necesitará ambos índices de la matriz 2d

suena como si estuvieras pensando y la acción se parece a:

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

pero realmente debería verse como:

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

¡De esa manera puedes hacer un listOf(listOf(listOf(...listOf(counter)...))) para siempre!

Todo esto viene de Elm, por cierto. Consulte el tutorial de arquitectura de Elm

Soy un poco lento para la fiesta, pero no veo dónde está esto de "no funciona con middleware". Si está ofreciendo anidamiento infinito como @ccorcos , ¿no puede simplemente conectar el middleware para manejar el anidamiento? ¿O estamos hablando exclusivamente de redux-thunk donde las acciones anidadas significarían rarezas?

¿Cómo sabría el middleware si interpretar la acción tal cual o buscar acciones anidadas?

Ajá.

@gaearon

Hola.

No estoy seguro de haber entendido la resolución sobre el problema y realmente agradecería una respuesta simple.

¿Es _no usar middleware (y básicamente la mayor parte del ecosistema Redux), si tiene varias copias del componente en la misma página_? Tampoco vi si alguien respondió a la sugerencia de multireductor .

Cualquier aclaración ayudaría.

No, esta no es la resolución. El hilo no se trata de tener múltiples identidades de un componente en la página. Para implementar esto, solo puede pasar una ID en la acción. El hilo trataba sobre _escribir una función genérica para hacer eso_. Lo que desafortunadamente choca con el concepto de middleware.

Gracias.

Hola a todos,
Tal vez resolví este problema, sin embargo, prefiero usar deku en lugar react ^^

Me encantaría que alguien me diera su opinión sobre este experimento, especialmente sobre la idea taskMiddleware . @gaearon , ¿tienes tiempo para comprobarlo? :lengua:

Lista de Contadores

¡Gracias!

Solo quiero aclarar que ninguna de estas soluciones tiene como objetivo manejar un estado inicial.
¿Se podría lograr esto de alguna manera @gaearon ? ¿Permitir que el reductor de lista anterior tenga una lista inicial de contadores?

¿Por qué sería eso problemático? Me imagino que podría llamar a los reductores secundarios con el estado undefined y usar esos valores.

Cuando la tienda se inicializa con el primer envío, necesito (para mi lógica interna) tener un estado inicial para esa lista, y debe ser una lista predefinida de contadores (el tipo de elementos de la lista)

¿Algo como esto?

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;
   // ...
  }
}

También puede convertir esto en un argumento para list si lo desea

Esto parece realmente complicado solo para poder tener múltiples del mismo componente en una página.

Todavía estoy pensando en Redux, pero crear varias tiendas parece mucho más simple, incluso si no es un patrón de uso recomendado de Redux.

@deevus : no dejes que algunas de estas discusiones te asusten. Hay varias personas en la comunidad de Redux que están muy orientadas hacia la programación funcional, y aunque algunos de los conceptos discutidos en este hilo y otros similares tienen valor, también tienden a ser un intento de buscar lo "perfecto". , en lugar de lo meramente "bueno".

Puede tener totalmente múltiples instancias de un componente en una página, en general. El objetivo de esta discusión es la composición arbitraria de componentes anidados, lo cual es interesante, pero tampoco es algo que la mayoría de las aplicaciones deban hacer.

Si tiene inquietudes específicas más allá de eso, Stack Overflow suele ser un buen lugar para hacer preguntas. Además, la comunidad de Reactiflux en Discord tiene un montón de canales de chat dedicados a discutir sobre React y tecnologías relacionadas, y siempre hay gente dispuesta a hablar y ayudar.

@markerikson Gracias. Intentaré obtener ayuda de Reactiflux en Discord.

Siguiendo con esto:
Encontré una forma escalable de reutilizar reductores en mi proyecto, incluso reutilizar reductores dentro de reductores.

El paradigma del espacio de nombres

Es el concepto de que las acciones que actúan sobre uno de los muchos reductores "parciales" tienen una propiedad de "espacio de nombres" que determina qué reductor manejará la acción contrariamente a _todos_ los reductores que la manejan porque escuchan la misma acción type (ejemplo en https://github.com/reactjs/redux/issues/822#issuecomment-172958967)

Sin embargo, las acciones que no contienen un espacio de nombres aún se propagarán a todos los reductores parciales dentro de otros reductores.

Digamos que tiene el reductor parcial A y con un estado inicial de A(undefined, {}) === Sa y un Reductor B con un estado inicial de B(undefined, {}) === { a1: Sa, a2: Sa } donde las claves a1 y a2 son instancias de A .

Una acción con un espacio de nombres de ['a1'] (* los espacios de nombres siempre son matrices ordenadas de cadenas que se asemejan a la clave del estado para el reductor parcial) aplicada a B producirá el siguiente resultado

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

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

Y el contraejemplo de una acción sin espacio de nombres

const action = {
  type: UNIQUE_ID
};


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

Advertencias

  • Si A no maneja la acción dada (agota el reductor) debería devolver el mismo estado, esto significa que para una acción p que no se puede manejar para el reductor A el resultado de B(undefined, p) debería ser { a1: Sa, a2: Sa } que, por cierto, es el mismo que el estado inicial de B
  • La acción que se transmite a los reductores parciales (indicada como action* arriba) debe eliminarse del espacio de nombres utilizado para reducir el alcance. Entonces, si la acción pasada a B fue { type: UNIQUE_ID, namespace: ['a1'] } entonces la acción pasada a A es { type: UNIQUE_ID, namespace: [] }
  • Para lograr esta estructura, todos los reductores en la tienda deben manejar acciones de espacio de nombres, si se cumple este punto, no hay límite en cuantos espacios de nombres está usando y cuántos reductores parciales puede anidar.

Para lograr esto, se me ocurrió un pseudocódigo para manejar espacios de nombres en su reductor. Para que esto funcione debemos saber de antemano si un reductor puede manejar una acción y la cantidad de reductores parciales que existen en el reductor.

(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);
}

Depende de usted cómo decidir si el reductor puede o no manejar la acción de antemano, yo uso un mapa de objetos en el que los tipos de acción son las teclas y las funciones del controlador son los valores.

Puede que llegue un poco tarde al juego, pero escribí algunos reductores de propósito genérico que podrían ayudar:
https://gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1

Creo que mutilreducer es una gran implementación.

@jeffhtli multireducer no es una buena solución porque no permite una cantidad indefinida de reductores, sino que le pide de manera preventiva que cree una lista de reductores estáticos
En su lugar, creé un pequeño proyecto para resolver este problema usando UUID para cada instancia de componentes y un estado único para cada UUID.
https://github.com/eloytoro/react-redux-uuid

¿Fue útil esta página
0 / 5 - 0 calificaciones