Czy to właściwy sposób na usunięcie pozycji za pomocą Redux?

117

Wiem, że nie powinienem modyfikować danych wejściowych i powinienem sklonować obiekt, aby go zmutować. Postępowałem zgodnie z konwencją stosowaną w projekcie startowym Redux, w której zastosowano:

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

za dodanie pozycji - uzyskuję użycie spreadu, aby dołączyć pozycję do tablicy.

do usunięcia użyłem:

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

ale to jest mutacja obiektu stanu wejściowego - czy jest to zabronione, mimo że zwracam nowy obiekt?

CWright
źródło
1
Szybkie pytanie. Splice zwraca elementy, które zostały usunięte. Czy to twój zamiar? Jeśli nie, powinieneś użyć plastra, który również jest zgodny z prawem braku mutacji.
m0meni
W tym przykładzie łączę dwie sekcje tablicy w nową tablicę - z pominięciem elementu, który chciałem usunąć. Slice również zwraca usunięty element, prawda? Tylko robi to bez mutowania oryginalnej tablicy, więc byłoby to lepsze podejście?
CWright,
@ AR7 zgodnie z twoją sugestią: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]użyj teraz plastra zamiast łączenia, aby nie modyfikować wejścia - czy to jest droga, czy jest bardziej zwięzła droga?
CWright,

Odpowiedzi:

208

Nie. Nigdy nie zmieniaj swojego stanu.

Nawet jeśli zwracasz nowy obiekt, nadal zanieczyszczasz stary obiekt, czego nigdy nie chcesz robić. To sprawia, że ​​porównywanie starego i nowego stanu jest problematyczne. Na przykład, shouldComponentUpdategdy reaktor-redux stosuje się pod maską. Uniemożliwia również podróże w czasie (tj. Cofanie i ponawianie).

Zamiast tego użyj niezmiennych metod. Zawsze używaj Array#slicei nigdy Array#splice.

Zakładam, że z twojego kodu action.payloadjest indeks usuwanego elementu. Lepszy sposób wyglądałby następująco:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],
David L. Walsh
źródło
to nie działa, jeśli mamy do czynienia z ostatnim elementem tablicy, również użycie ...w drugiej instrukcji podwoi zawartość twojego stanu
Thaenor
4
Udowodnij to na przykładzie jsfiddle / codepen. Fragment arr.slice(arr.length)powinien zawsze dawać pustą tablicę, bez względu na zawartość arr.
David L. Walsh
1
@ david-l-walsh przepraszam za zamieszanie, musiałem popełnić literówkę lub coś w tym rodzaju podczas testowania tego przykładu. Działa cuda. Moje jedyne pytanie brzmi: po co operator spreadu ...w drugiej części -...state.items.slice(action.payload + 1)
Thaenor
5
Array#slicezwraca tablicę. Aby połączyć dwa wycinki w jedną tablicę, użyłem operatora spreadu. Bez tego miałbyś tablicę tablic.
David L. Walsh
4
to ma sens. Dziękuję bardzo za wyjaśnienie (i przepraszam za zamieszanie na początku).
Thaenor
150

Możesz użyć metody filtru tablicy, aby usunąć określony element z tablicy bez modyfikowania oryginalnego stanu.

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

W kontekście twojego kodu wyglądałoby to mniej więcej tak:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})
Steph M.
źródło
1
Czy filtr tworzy nową tablicę?
chenop
6
@chenop Tak, metoda Array.filter zwraca nową tablicę. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M
4
Pamiętaj, że jeśli istnieją duplikaty, spowoduje to usunięcie WSZYSTKICH z nich. Aby użyć filtra do usunięcia określonego indeksu, możesz użyć np.arr.filter((val, i) => i !== action.payload )
erich2k8
22

Metoda ES6 Array.prototype.filterzwraca nową tablicę z elementami spełniającymi kryteria. Dlatego w kontekście pierwotnego pytania byłoby to:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})
JoeTidee
źródło
.filter()nie jest metodą ES2015, ale została dodana w poprzedniej wersji ES5.
morkro
7

Kolejna odmiana niezmiennego reduktora „DELETED” dla tablicy z obiektami:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;
rzymski
źródło
0

Złota zasada mówi, że nie przywracamy stanu zmutowanego, ale raczej nowy stan. W zależności od rodzaju akcji może być konieczne zaktualizowanie drzewa stanu w różnych formach, gdy trafi ono w reduktor.

W tym scenariuszu próbujemy usunąć element z właściwości stanu.

To prowadzi nas do koncepcji niezmiennych wzorców aktualizacji (lub modyfikacji danych) Redux. Niezmienność jest kluczowa, ponieważ nigdy nie chcemy bezpośrednio zmieniać wartości w drzewie stanów, ale raczej zawsze wykonujemy kopię i zwracamy nową wartość na podstawie starej wartości.

Oto przykład, jak usunąć zagnieżdżony obiekt:

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

Aby lepiej to zrozumieć, zapoznaj się z tym artykułem: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Kasra Khosravi
źródło