Це правильний спосіб видалення елемента за допомогою скорочення?


116

Я знаю, що я не повинен мутувати вхід і повинен клонувати об'єкт, щоб його мутувати. Я дотримувався конвенції, застосованої у проекті редукційного старту, який використовував:

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

для додавання елемента - я використовую спред для додавання елемента в масив.

для видалення я використовував:

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

але це мутація об'єкта вхідного стану - це заборонено, навіть якщо я повертаю новий об'єкт?


1
Швидке запитання. Сплайс повертає вилучені елементи. Це ваш намір? Якщо ні, то вам слід використовувати фрагмент, який також дотримується закону про мутації.
m0meni

Добре, що в цьому прикладі я сплачую два розділи масиву разом на новий масив - за допомогою елемента, який я хотів видалити, залишений. Фрагмент також повертає вилучений елемент правильно? Тільки це робить це, не мутуючи оригінальний масив, щоб це було кращим підходом?
CWright

@ AR7 за вашою пропозицією: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]тепер використовуйте фрагмент замість сплайсу, щоб не мутувати вхід - це шлях, чи є більш стислий шлях?
CWright

Відповіді:


207

Ні. Ніколи не мутуйте свій стан.

Незважаючи на те, що ви повертаєте новий об’єкт, ви все ще забруднюєте старий об’єкт, чого ніколи не хочете робити. Це робить проблематичним порівняння між старою та новою державою. Наприклад, shouldComponentUpdateколи react-redux використовує під кришкою. Це також робить неможливим подорож у часі (тобто скасовувати та повторювати).

Натомість використовуйте незмінні методи. Завжди використовуйте Array#sliceі ніколи Array#splice.

Я вважаю, що з вашого коду action.payloadє індекс вилученого елемента. Кращим способом було б таке:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],

це не спрацьовує, якщо ми маємо справу з останнім елементом у масиві, також використання ...другого висловлення подвоїть вміст вашого штату
Thaenor

4
Доведіть, будь ласка, на прикладі jsfiddle / codepen. Фрагмент arr.slice(arr.length)повинен завжди створювати порожній масив незалежно від вмісту arr.
Девід Л. Уолш

1
@ david-l-walsh вибачте за плутанину, я, мабуть, зробив друк або щось подібне під час тестування цього прикладу. Це творить чудеса. Єдине моє запитання: чому потреба оператора розкидання ...у другій частині -...state.items.slice(action.payload + 1)
Тенор

5
Array#sliceповертає масив. Щоб об'єднати два зрізи в один масив, я використовував оператор розповсюдження. Без нього у вас був би масив масивів.
Девід Л. Уолш

4
що має сенс. Дуже дякую за роз’яснення (і вибачте за першу чергу).
Thaenor

149

Ви можете використовувати метод фільтра масиву для видалення конкретного елемента з масиву, не змінюючи початковий стан.

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

У контексті вашого коду це виглядатиме приблизно так:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})

1
Чи виробляє фільтр новий масив?
чеснок

6
@chenop Так, метод Array.filter повертає новий масив. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M

4
Зауважте, що якщо є дублікати, це видалить ВСІ з них. Щоб використовувати фільтр для видалення певного індексу, ви можете використовувати, наприклад,arr.filter((val, i) => i !== action.payload )
erich2k8

21

Метод ES6 Array.prototype.filterповертає новий масив з елементами, які відповідають критеріям. Тому в контексті початкового питання це буде:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})

.filter()не є методом ES2015, але додано в попередній версії ES5.
morkro

7

Ще один варіант незмінного редуктора "ВІДКРИТИ" для масиву з об'єктами:

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

0

Золоте правило полягає в тому, що ми не повертаємо мутований стан, а новий стан. Залежно від типу вашої дії, вам може знадобитися оновити дерево свого стану у різних формах, коли воно потрапить на редуктор.

У цьому випадку ми намагаємось вилучити предмет із державної власності.

Це підводить нас до концепції моделей постійного оновлення (або модифікації даних) Redux. Незмінність є ключовою, оскільки ми ніколи не хочемо безпосередньо змінювати значення в дереві стану, а завжди робимо копію та повертаємо нове значення на основі старого значення.

Ось приклад того, як видалити вкладений об’єкт:

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

Щоб краще зрозуміти це, обов'язково ознайомтеся з цією статтею: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.