2015-11-27 17 views
12

Sto lavorando a un'applicazione React/Redux che consente di aggiungere "widget" a una pagina e manipolati nello spazio 2D. È necessario che più widget possano essere selezionati e manipolati contemporaneamente. Una versione semplificata del mio albero dello stato attuale sembra qualcosa di simile a quanto segue ...Come comporre riduttori di ridondanza con stato dipendente

{ 
    widgets: { 
    widget_1: { x: 100, y: 200 }, 
    widget_2: { x: 300, y: 400 }, 
    widget_3: { x: 500, y: 600 } 
    }, 
    selection: { 
    widgets: [ "widget_1", "widget_3" ] 
    } 
} 

Io attualmente ho questo albero di proprietà di 2 riduttori uno gestire widgets Stato e l'altro la gestione di selection statali. Il riduttore di stato di selezione può essere semplificata come (nota: sto usando Immutable.js troppo) ...

currentlySelectedWidgets(state = EMPTY_SELECTION, action) { 
    switch (action.type) { 
    case SET_SELECTION: 
     return state.set('widgets' , List(action.ids)); 
    default: 
     return state 
    } 
} 

Tutto questo sembra funzionare abbastanza bene, ma ora ho un requisito che Ho difficoltà a inserirsi in questo modello ...

Ai fini dell'interfaccia utente, è necessario che la selezione corrente abbia una proprietà x/y che riflette l'angolo in alto a sinistra delle coordinate x/y dei widget attualmente selezionati. Uno stato di esempio potrebbe sembrare ...

{ 
    widgets: { 
    widget_1: { x: 100, y: 200 }, 
    widget_2: { x: 300, y: 400 }, 
    widget_3: { x: 500, y: 600 } 
    }, 
    selection: { 
    x: 100, 
    y: 200, 
    widgets: [ "widget_1", "widget_3" ] 
    } 
} 

La logica qui è abbastanza semplice, ad es. trovare lo min() di tutti i valori selezionati x e ma non sono sicuro di come dovrei gestirlo in termini di composizione del riduttore poiché il riduttore currentlySelectedWidgets non ha accesso alla parte dello stato dell'albero widgets. Ho considerato ...

  • unione dei riduttori in un riduttore: questa non sembra una soluzione scalabile.
  • passando l'elenco corrente di widget in giro con l'azione: questo sembra particolarmente cattivo.
  • trovare un modo migliore per modellare l'albero di stato: ho pensato, ma le versioni alternative sembrano avere altri svantaggi.
  • Creazione di una personalizzata combineReducers() che può alimentare l'elenco di widget nel mio riduttore currentlySelectedWidgets() come argomento aggiuntivo: Penso che questa potrebbe essere la mia migliore scommessa.

Sono ansioso di ascoltare altri suggerimenti su come gli altri gestiscono situazioni simili, sembra che gestire una "selezione corrente" debba essere un problema di stato comune che altri devono aver dovuto risolvere.

risposta

8

Si tratta di un caso d'uso buono per Reselect:

creare un selettore per accedere al proprio stato e tornare derivati ​​dati.

Per fare un esempio:

import { createSelector } from 'reselect' 

const widgetsSelector = state => state.widgets; 
const selectedWidgetsSelector = state => state.selection.widgets; 

function minCoordinateSelector(widgets, selected) { 
    const x_list = selected.map((widget) => { 
    return widgets[widget].x; 
    }); 

    const y_list = selected.map((widget) => { 
    return widgets[widget].y; 
    }); 

    return { 
    x: Math.min(...x_list), 
    y: Math.min(...y_list) 
    }; 
} 

const coordinateSelector = createSelector(
    widgetsSelector, 
    selectedWidgetsSelector, 
    (widgets, selected) => { 
    return minCoordinateSelector(widgets, selected); 
    } 
); 

Il coordinateSelector ora fornisce l'accesso al vostro min x e y proprietà. Il selettore precedente verrà aggiornato solo quando lo stato widgetsSelector o selectedWidgetsSelector cambia, rendendo questo un modo molto performante per accedere alle proprietà che si stanno cercando ed evitare la duplicazione nella struttura ad albero.

+3

Dopo aver tentato di implementare l'approccio basato su thunk di Hummlas e questo approccio basato su reselect, mi sembra che questo approccio si adattasse meglio al mio modello e fornisse una soluzione più pulita e più manutenibile. Immagino che la regola generale che sto lentamente imparando non sia quella di memorizzare qualcosa nell'albero dello stato che può essere ricalcolato dall'albero esistente. – andykent

+1

Ho dimenticato di menzionare, c'è un piccolo errore in questa risposta di esempio, l'ultimo 'minSelector (widget, selezionato)' dovrebbe leggere 'minCoordinateSelector (widget, selezionato)' è improbabile che qualcuno possa copiare questo codice così com'è, ma nel caso loro fanno. – andykent

4

Se si utilizza il middleware thunk (https://github.com/gaearon/redux-thunk) è possibile effettuare l'azione SET_SELECTION un thunk, che consentirà di leggere l'intero stato prima di effettuare l'invio che verrà ricevuto dal riduttore.

// action creator 
function setSelection(selectedWidgetId) { 
    return (dispatch, getState) => { 
     const {widgets} = this.getState(); 
     const coordinates = getSelectionCoordinates(widgets, selectedWidgetIds); 

     dispatch({ 
      type: SET_SELECTION, 
      payload: { 
       widgets: selectedWidgets, 
       x: coordinates.x, 
       y: coordinates.y 
      } 
     }); 
} 

questo modo si ottiene tutte le informazioni necessarie nella selezione riduttore senza dover passare lungo l'elenco di tutti i Widget-oggetti alla vostra azione.

+0

Grazie per questo, sembra che risolverà il problema, ho Sono interessato a vedere quanto questo approccio si riduca man mano che la codebase cresce, ma vedremo. – andykent

+0

Oggi non posso più aprire domande, quindi chiedo in questo modo. E se @Ashley volesse eliminare un particolare widget dal riduttore 'stateSelectedWidgets (stato, azione)'? Come fare questo? – zatziky

0

Sto usando questo:

CommonReducer.js

export default function commonReducer(state = initialState.doesNotMatter, payload) { 
    switch (payload.type) { 
    case "JOINED_TYPE": { 
     let newState = Object.assign({}, state); 
     return newState; 
    } 
    default: 
     return state; 
    } 
} 

SomeOtherReducer.js

import commonReducer from './commonReducer'; 

export default function someReducer(state = initialState.somePart, payload) { 
     switch (payload.type) { 
     case "CUSTOM_TYPE": { 
      let newState = Object.assign({}, state); 
      return newState; 
     } 
     default:{ 
      return commonReducer(state, payload); 
     } 
     } 
} 
Problemi correlati