Est-ce la bonne façon de supprimer un élément à l'aide de redux?

116

Je sais que je ne suis pas censé muter l'entrée et que je devrais cloner l'objet pour le muter. Je suivais la convention utilisée sur un projet de démarrage redux qui utilisait:

ADD_ITEM: (state, action) => ({
  ...state,
  items: [...state.items, action.payload.value],
  lastUpdated: action.payload.date
})

pour ajouter un élément - je reçois l'utilisation de spread pour ajouter l'élément dans le tableau.

pour supprimer j'ai utilisé:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
  lastUpdated: Date.now() 
})

mais cela mute l'objet d'état d'entrée - est-ce interdit même si je retourne un nouvel objet?

CWright
la source
1
Question rapide. Splice renvoie les éléments que vous avez supprimés. Est-ce votre intention? Sinon, vous devriez utiliser slice, qui respecte également la loi sans mutations.
m0meni
Eh bien, dans cet exemple, je joint les deux sections du tableau ensemble dans un nouveau tableau - avec l'élément que je voulais supprimer laissé de côté. Slice renvoie également l'élément supprimé, n'est-ce pas? Seulement, il le fait sans muter le tableau d'origine, ce serait donc la meilleure approche?
CWright
@ AR7 selon votre suggestion: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]utiliser slice maintenant au lieu de splice afin de ne pas muter l'entrée - est-ce la voie à suivre ou y a-t-il une manière plus concise?
CWright

Réponses:

207

Non, ne changez jamais votre état.

Même si vous renvoyez un nouvel objet, vous polluez toujours l'ancien, ce que vous ne voulez jamais faire. Cela rend problématique lors des comparaisons entre l'ancien et le nouvel état. Par exemple dans shouldComponentUpdatelequel react-redux utilise sous le capot. Cela rend également impossible le voyage dans le temps (c'est-à-dire annuler et refaire).

Utilisez plutôt des méthodes immuables. Utilisez toujours Array#sliceet jamais Array#splice.

Je suppose que de votre code action.payloadest l'index de l'élément en cours de suppression. Une meilleure façon serait la suivante:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],
David L. Walsh
la source
cela ne fonctionne pas si nous avons affaire au dernier élément du tableau, l'utilisation de ...dans la deuxième instruction doublera également le contenu de votre état
Thaenor
4
Veuillez le prouver avec un exemple jsfiddle / codepen. L'extrait de code arr.slice(arr.length)doit toujours produire un tableau vide quel que soit le contenu de arr.
David L. Walsh
1
@ david-l-walsh désolé pour la confusion, j'ai dû faire une faute de frappe ou quelque chose comme ça en testant cet exemple. Cela fonctionne à merveille. Ma seule question est: pourquoi le besoin de l'opérateur de propagation ...dans la deuxième partie -...state.items.slice(action.payload + 1)
Thaenor
5
Array#slicerenvoie un tableau. Pour combiner les deux tranches en un seul tableau, j'ai utilisé l'opérateur de propagation. Sans cela, vous auriez un tableau de tableaux.
David L. Walsh
4
ça a du sens. Merci beaucoup pour la clarification (et désolé pour la confusion au début).
Thaenor
149

Vous pouvez utiliser la méthode de filtre de tableau pour supprimer un élément spécifique d'un tableau sans modifier l'état d'origine.

return state.filter(element => element !== action.payload);

Dans le contexte de votre code, cela ressemblerait à ceci:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})
Steph M
la source
1
Le filtre produit-il un nouveau tableau?
chenop
6
@chenop Oui, la méthode Array.filter renvoie un nouveau tableau. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M
4
Notez que s'il y a des doublons, cela les supprimera TOUS. Pour utiliser le filtre pour supprimer un index spécifique, vous pouvez utiliser par exemplearr.filter((val, i) => i !== action.payload )
erich2k8
21

La Array.prototype.filterméthode ES6 renvoie un nouveau tableau avec les éléments qui correspondent aux critères. Par conséquent, dans le contexte de la question initiale, ce serait:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})
JoeTidee
la source
.filter()n'est pas une méthode ES2015, mais a été ajoutée dans la version précédente ES5.
morkro
7

Une autre variante du réducteur immuable "DELETED" pour le tableau avec des objets:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;
romain
la source
0

La règle d'or est que nous ne retournons pas un état muté, mais plutôt un nouvel état. Selon le type de votre action, vous devrez peut-être mettre à jour votre arborescence d'états sous diverses formes lorsqu'elle atteint le réducteur.

Dans ce scénario, nous essayons de supprimer un élément d'une propriété d'état.

Cela nous amène au concept des modèles de mise à jour immuable (ou de modification de données) de Redux. L'immuabilité est essentielle car nous ne voulons jamais changer directement une valeur dans l'arborescence d'état, mais plutôt toujours faire une copie et renvoyer une nouvelle valeur basée sur l'ancienne valeur.

Voici un exemple de suppression d'un objet imbriqué:

// ducks/outfits (Parent)

// types
export const NAME = `@outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;

// initialization
const initialState = {
  isInitiallyLoaded: false,
  outfits: ['Outfit.1', 'Outfit.2'],
  filters: {
    brand: [],
    colour: [],
  },
  error: '',
};

// action creators
export function removeFilter({ field, index }) {
  return {
    type: REMOVE_FILTER,
    field,
    index,
  };
}

export default function reducer(state = initialState, action = {}) {
  sswitch (action.type) {  
  case REMOVE_FILTER:
  return {
    ...state,
    filters: {
    ...state.filters,
       [action.field]: [...state.filters[action.field]]
       .filter((x, index) => index !== action.index)
    },
  };
  default:
     return state;
  }
}

Pour mieux comprendre cela, assurez-vous de consulter cet article: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Kasra Khosravi
la source