Come si aggiunge / rimuove a un negozio di ridex generato con normalizr?

Guardando gli esempi dal README :

Data la struttura "ctriggers":

[{ id: 1, title: 'Some Article', author: { id: 1, name: 'Dan' } }, { id: 2, title: 'Other Article', author: { id: 1, name: 'Dan' } }] 

È estremamente facile aggiungere un nuovo object. Tutto quello che devo fare è qualcosa di simile

 return { ...state, myNewObject } 

Nel riduttore.

Ora data la struttura del "buon" tree, non ho idea di come dovrei avvicinarmi.

 { result: [1, 2], entities: { articles: { 1: { id: 1, title: 'Some Article', author: 1 }, 2: { id: 2, title: 'Other Article', author: 1 } }, users: { 1: { id: 1, name: 'Dan' } } } } 

Ogni approccio che ho pensato richiede una manipolazione complessa di oggetti, che mi fa sentire come non sono sulla buona strada perché normalizr dovrebbe fare la mia vita più facile.

Non riesco a trovare esempi in linea di qualcuno che lavora con l'tree normalizr in questo modo. L'esempio ufficiale non aggiunge e rimuove quindi non è stato un aiuto.

Potrebbe qualcuno far sapere come aggiungere / rimuovere da un tree normalizr il modo giusto?

Di seguito è riportto direttamente da un post del creatore redux / normalizr qui :

Così il tuo stato sarebbe simile:

 { entities: { plans: { 1: {title: 'A', exercises: [1, 2, 3]}, 2: {title: 'B', exercises: [5, 1, 2]} }, exercises: { 1: {title: 'exe1'}, 2: {title: 'exe2'}, 3: {title: 'exe3'} } }, currentPlans: [1, 2] } 

I tuoi riduttori potrebbero sembrare simili

 import merge from 'lodash/object/merge'; const exercises = (state = {}, action) => { switch (action.type) { case 'CREATE_EXERCISE': return { ...state, [action.id]: { ...action.exercise } }; case 'UPDATE_EXERCISE': return { ...state, [action.id]: { ...state[action.id], ...action.exercise } }; default: if (action.entities && action.entities.exercises) { return merge({}, state, action.entities.exercises); } return state; } } const plans = (state = {}, action) => { switch (action.type) { case 'CREATE_PLAN': return { ...state, [action.id]: { ...action.plan } }; case 'UPDATE_PLAN': return { ...state, [action.id]: { ...state[action.id], ...action.plan } }; default: if (action.entities && action.entities.plans) { return merge({}, state, action.entities.plans); } return state; } } const entities = combineReducers({ plans, exercises }); const currentPlans = (state = [], action) { switch (action.type) { case 'CREATE_PLAN': return [...state, action.id]; default: return state; } } const reducer = combineReducers({ entities, currentPlans }); 

Allora cosa sta succedendo qui? Innanzitutto, si noti che lo stato è normalizzato. Non abbiamo mai entity framework; all'interno di altre entity framework;. Invece, si riferiscono tra di loro per ID. Quindi, each volta che un object cambia, c'è solo un posto where deve essere aggiornato.

In secondo luogo, nota come reagiamo a CREATE_PLAN aggiungendo un'entity framework; appropriata nel riduttore di piani e aggiungendo l'ID al riduttore corrente. Questo è importnte. Nelle applicazioni più complesse, puoi avere rapporti, ad esempio il riduttore di piani può gestire ADD_EXERCISE_TO_PLAN nello stesso modo aggiungendo un nuovo ID all'arrays all'interno del piano. Ma se l'esercizio stesso è aggiornato, non c'è bisogno di ridurre i piani per sapere che, come ID non è cambiato.

In terzo luogo, notare che i riduttori entity framework; (piani e esercizi) hanno clausole speciali che guardano fuori per action.entities. Ciò è nel caso in cui abbiamo una risposta del server con "verità conosciuta" che vogliamo aggiornare tutte le nostre entity framework; per riflettere. Per preparare i tuoi dati in questo modo prima di submit un'azione, puoi utilizzare normalizr. Potete vederlo usato nell'esempio "reale" in Redux repo.

Infine, notate come i riduttori delle entity framework; sono simili. Potresti voler scrivere una function per generarle. È fuori portta della mia risposta – a volte desideri una maggiore flessibilità e a volte ti desideri un po 'less. È ansible verificare il codice di pagine in riduttori di esempio "mondo reale" per un esempio di generazione di riduttori simili.

Oh, e ho usato la syntax {… a, … b}. È abilitato in Babel fase 2 come proposta ES7. Si chiama "operatore di diffusione degli oggetti" ed equivalente alla scrittura di Object.assign ({}, a, b).

Per quanto riguarda le librerie, è ansible utilizzare Lodash (attenzione a non mutare per esempio, ad esempio merge ({}, a, b} è corretto, ma unire (a, b) non è), updeep, react-addons-update o qualcos'altro. Tuttavia, se ti ritrovi a fare aggiornamenti profondi, significa che l'tree di stato non è abbastanza piatto e che non utilizzi abbastanza la composizione funzionale. Anche il tuo primo esempio:

 case 'UPDATE_PLAN': return { ...state, plans: [ ...state.plans.slice(0, action.idx), Object.assign({}, state.plans[action.idx], action.plan), ...state.plans.slice(action.idx + 1) ] }; 

può essere scritto come

 const plan = (state = {}, action) => { switch (action.type) { case 'UPDATE_PLAN': return Object.assign({}, state, action.plan); default: return state; } } const plans = (state = [], action) => { if (typeof action.idx === 'undefined') { return state; } return [ ...state.slice(0, action.idx), plan(state[action.idx], action), ...state.slice(action.idx + 1) ]; }; // somewhere case 'UPDATE_PLAN': return { ...state, plans: plans(state.plans, action) }; 

La maggior parte del tempo utilizzo normalizr per i dati che ricevi da un'API, perché non ho alcun controllo sulle strutture di dati (di solito) profondamente nidificate. Distinguiamo le Entità e il Risultato e il loro utilizzo.

Entità

Tutti i dati puro sono nell'object entity framework; dopo che è stato normalizzato (nel tuo caso articles e users ). Vorrei raccomandare di utilizzare un riduttore per tutte le entity framework; o un riduttore per each tipo di entity framework;. Il riduttore entity framework; dovrebbe essere responsabile per mantenere sincronizzati i dati (server) e per avere una sola fonte di verità.

 const initialState = { articleEntities: {}, userEntities: {}, }; 

Risultato

I risultati sono solo riferimenti alle tue entity framework;. Immagina il seguente scenario: (1) Ricevi da articles raccomandati da API con ids: ['1', '2'] . Salva le entity framework; nel riduttore dell'entity framework; dell'articolo . (2) Adesso ricevi tutti gli articoli scritti da un autore specifico con id: 'X' . Ancora una volta sincronizza gli articoli nel riduttore dell'entity framework; dell'articolo . Il riduttore dell'entity framework; dell'articolo è la sola fonte di verità per tutti i dati dell'articolo, vale a dire. Ora vuoi avere un altro posto per differenziare gli articoli (1) articoli consigliati e (2) articoli per autore X). Puoi facilmente tenere questi in un altro caso riduttore specifico. Lo stato di quel riduttore potrebbe sembrare così:

 const state = { recommended: ['1', '2' ], articlesByAuthor: { X: ['2'], }, }; 

Ora si può facilmente vedere che l'articolo dell'autore X è anche un articolo consigliato. Ma mantenete una sola fonte di verità nel tuo riduttore di entity framework; dell'articolo.

Nel tuo componente puoi semplicemente mappare le entity framework; + consigliati / articlesByAuthor per presentare l'entity framework;.

Disclaimer: Posso raccomandare un post sul blog che ho scritto, che mostra come un'applicazione di mondo reale utilizza normalizr per prevenire problemi nella gestione dello stato: Redux Normalizr: Migliora la gestione dello stato

Ho implementato una piccola deviazione di un riduttore generico che può essere trovato su Internet. È in grado di eliminare gli elementi dalla cache. Tutto quello che devi fare è accertarsi che each cancellata invii un'azione con il field eliminato:

 export default (state = entities, action) => { if (action.response && action.response.entities) state = merge(state, action.response.entities) if (action.deleted) { state = {...state} Object.keys(action.deleted).forEach(entity => { let deleted = action.deleted[entity] state[entity] = Object.keys(state[entity]).filter(key => !deleted.includes(key)) .reduce((p, id) => ({...p, [id]: state[entity][id]}), {}) }) } return state } 

esempio di utilizzo nel codice di azione:

 await AlarmApi.remove(alarmId) dispatch({ type: 'ALARM_DELETED', alarmId, deleted: {alarms: [alarmId]}, }) 

Nel tuo riduttore, conserva una copia dei dati non normalizzati. In questo modo, puoi fare qualcosa di simile (quando aggiungi un nuovo object a una matrix in stato):

 case ACTION: return { unNormalizedData: [...state.unNormalizedData, action.data], normalizedData: normalize([...state.unNormalizedData, action.data], normalizrSchema), } 

Se non desideri mantenere dati non normalizzati nel tuo negozio, puoi anche utilizzare denormalizzare