Это правильный способ удалить элемент с помощью redux?

118

Я знаю, что я не должен изменять ввод и должен клонировать объект, чтобы изменить его. Я следовал соглашению, используемому в стартовом проекте redux, который использовал:

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() 
})

но это изменяет объект состояния ввода - это запрещено, даже если я возвращаю новый объект?

CWright
источник
1
Быстрый вопрос. Splice возвращает элементы, которые вы удалили. Это ваше намерение? В противном случае вам следует использовать slice, который также подчиняется закону об отсутствии мутаций.
m0meni 03
В этом примере я объединяю две части массива в новый массив - с элементом, который я хотел удалить, оставленным. Slice также возвращает удаленный элемент, верно? Только он делает это без изменения исходного массива, так что это будет лучший подход?
CWright 03
@ AR7 в соответствии с вашим предложением: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]теперь использовать срез вместо сращивания, чтобы не изменять ввод - это путь или есть более краткий способ?
CWright 03

Ответы:

209

Нет. Никогда не изменяйте свое состояние.

Даже если вы возвращаете новый объект, вы все равно загрязняете старый объект, чего никогда не стоит делать. Это затрудняет сравнение старого и нового состояния. Например, в shouldComponentUpdateкотором react-redux используется под капотом. Это также делает невозможным путешествие во времени (то есть отмену и повтор).

Вместо этого используйте неизменяемые методы. Всегда используйте Array#sliceи никогдаArray#splice .

Я предполагаю из вашего кода, что action.payloadэто индекс удаляемого элемента. Лучше было бы следующее:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],
Дэвид Л. Уолш
источник
это не сработает, если мы имеем дело с последним элементом в массиве, а также использование ...во втором выражении удвоит содержимое вашего состояния
Тхэнор
4
Пожалуйста, докажите это на примере jsfiddle / codepen. Фрагмент arr.slice(arr.length)всегда должен создавать пустой массив, независимо от его содержимого arr.
Дэвид Л. Уолш
1
@ david-l-walsh извините за путаницу, должно быть, я сделал опечатку или что-то в этом роде при тестировании этого примера. Это творит чудеса. Мой единственный вопрос: зачем нужен оператор спреда ...во второй части...state.items.slice(action.payload + 1)
Таенор
5
Array#sliceвозвращает массив. Чтобы объединить два фрагмента в один массив, я использовал оператор распространения. Без него у вас был бы массив массивов.
Дэвид Л. Уолш
4
в этом есть смысл. Большое спасибо за разъяснения (и извините за путаницу вначале).
Thaenor
151

Вы можете использовать метод фильтрации массива, чтобы удалить определенный элемент из массива без изменения исходного состояния.

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

В контексте вашего кода это будет выглядеть примерно так:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})
Стеф М
источник
1
Создает ли фильтр новый массив?
chenop
6
@chenop Да, метод Array.filter возвращает новый массив. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M
4
Обратите внимание, что если есть дубликаты, это удалит их ВСЕ. Чтобы использовать фильтр для удаления определенного индекса, вы можете использовать, например,arr.filter((val, i) => i !== action.payload )
erich2k8
22

Метод ES6 Array.prototype.filterвозвращает новый массив с элементами, соответствующими критериям. Следовательно, в контексте исходного вопроса это будет:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})
JoeTidee
источник
.filter()не является методом ES2015, но был добавлен в предыдущей версии ES5.
morkro
7

Еще один вариант неизменного редуктора DELETED для массива с объектами:

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

Касра Хосрави
источник