Неизменно удалить свойство объекта

158

Я использую Redux. В моем редукторе я пытаюсь удалить свойство из такого объекта:

const state = {
    a: '1',
    b: '2',
    c: {
       x: '42',
       y: '43'
    },
}

И я хочу иметь что-то подобное, не изменяя исходное состояние:

const newState = {
    a: '1',
    b: '2',
    c: {
       x: '42',
    },
}

Я старался:

let newState = Object.assign({}, state);
delete newState.c.y

но по некоторым причинам он удаляет свойство из обоих состояний.

Могли бы мне помочь в этом?

Винсент Тэйнг
источник
3
Обратите внимание , что Object.assignсоздает лишь неполную копию из stateи , следовательно , state.cи newState.cбудет указывать на тот же объект разделяемого. Вы пытались удалить свойство yиз общего объекта, cа не из нового объекта newState.
Аксели Пален,

Ответы:

241

Как насчет использования синтаксиса присваивания деструктуризации ?

const original = {
  foo: 'bar',
  stack: 'overflow',
};

// If the name of the property to remove is constant
const { stack, ...withoutFirst } = original;
console.log(withoutFirst); // Will be { "foo": "bar" }

// If the name of the property to remove is from a variable
const key = 'stack'
const { [key]: value, ...withoutSecond } = original;
console.log(withoutSecond); // Will be { "foo": "bar" }

// To do a deep removal with property names from variables
const deep = {
  foo: 'bar',
  c: {
   x: 1,
   y: 2
  }
};

const parentKey = 'c';
const childKey = 'y';
// Remove the 'c' element from original
const { [parentKey]: parentValue, ...noChild } = deep;
// Remove the 'y' from the 'c' element
const { [childKey]: removedValue, ...childWithout } = parentValue;
// Merge back together
const withoutThird = { ...noChild, [parentKey]: childWithout };
console.log(withoutThird); // Will be { "foo": "bar", "c": { "x": 1 } }

madebydavid
источник
3
замечательно, без дополнительной библиотеки, использовать es6 и достаточно просто.
sbk201
Самое элегантное решение
long.luc
12
Ницца! Вот вспомогательная функция es6 const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Использование: deleteProperty({a:1, b:2}, "a");дает{b:2}
mikebridge
супер умный, я опаздываю на это, но один из моих новых фаворитов
Гэвин
1
Обратите внимание, что пример глубокого удаления приведет к сбою, если deep['c']он пуст, поэтому в общем случае вы можете добавить проверку наличия ключа.
Laurent S
46

Мне нравятся методы массива ES5 filter, mapи reduceони полезны, потому что они всегда возвращают новые массивы или объекты. В этом случае я бы Object.keysперебрал объект и Array#reduceснова превратил его в объект.

return Object.assign({}, state, {
    c: Object.keys(state.c).reduce((result, key) => {
        if (key !== 'y') {
            result[key] = state.c[key];
        }
        return result;
    }, {})
});
Дэвид Л. Уолш
источник
вмешиваясь с моим небольшим изменением, чтобы сделать его более ясным, ИМО ... также позволяет вам опускать несколько свойств. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) result [key] = state.c [key] return result; ...
josh-sachs
3
Эквивалент ES6 для получения копии myObjectс myKeyудаленным ключом :Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
transang
38

Вы можете использовать _.omit(object, [paths])из библиотеки lodash

путь может быть вложенным, например: _.omit(object, ['key1.key2.key3'])

Дмитрий
источник
3
К сожалению, _.omitневозможно удалить глубокие свойства (о чем спрашивал OP). Для этого есть omit-deep-lodashмодуль.
AlexM
2
Исходя из того, что говорил @AlexM. Я нашел его полезным и, возможно, более подходящим для нас _.cloneDeep(obj)из lodash. Это легко копирует объект, а затем вы можете просто использовать js delete obj.[key]для удаления ключа.
Alex J
Согласитесь, с @AlexJ, cloneDeep работает отлично, и вы также можете использовать с ним синтаксис распространения: ..._. CloneDeep (состояние) для Redux
Шон Чейз
32

Просто используйте функцию деструктуризации объекта ES6

const state = {
    c: {
       x: '42',
       y: '43'
    },
}

const { c: { y, ...c } } = state // generates a new 'c' without 'y'

console.log({...state, c }) // put the new c on a new state

Рамон Диого
источник
8
const {y, ...c} = state.cможет быть немного яснее, чем два cс левой стороны.
dosentmatter
10
будьте осторожны: это не работает для объекта с целыми числами
daviestar
3
Из приведенного ниже ответа, если вам нужно указать имя переменной, которую нужно удалить: const name = 'c'тогда вы можете сделать это, а const {[name]:deletedValue, ...newState} = stateзатем вернуться newStateв свой редуктор. Это для удаления ключа верхнего уровня
FFF
23

Это потому, что вы копируете значение state.cв другой объект. И это значение является указателем на другой объект javascript. Итак, оба этих указателя указывают на один и тот же объект.

Попробуй это:

let newState = Object.assign({}, state);
console.log(newState == state); // false
console.log(newState.c == state.c); // true
newState.c = Object.assign({}, state.c);
console.log(newState.c == state.c); // now it is false
delete newState.c.y;

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

Aᴍɪʀ
источник
2
Это отличный ответ! state.cявляется ссылкой, и ссылка копируется нормально. Redux хочет нормализованную форму состояния, что означает использование идентификаторов вместо ссылок при вложении состояния. Ознакомьтесь с документами redux
Зигги
17

Как насчет этого:

function removeByKey (myObj, deleteKey) {
  return Object.keys(myObj)
    .filter(key => key !== deleteKey)
    .reduce((result, current) => {
      result[current] = myObj[current];
      return result;
  }, {});
}

Он фильтрует ключ, который следует удалить, а затем создает новый объект из оставшихся ключей и исходного объекта. Идея позаимствована у Тайлера МакГиннеса из классной программы reactjs.

JSBin

СебК
источник
11
function dissoc(key, obj) {
  let copy = Object.assign({}, obj)
  delete copy[key]
  return copy
}

Кроме того, если вам нужен инструментарий функционального программирования, обратите внимание на Ramda .

Доминикас Мостаускис
источник
10

Вы можете использовать помощник неизменяемости , чтобы отключить атрибут, в вашем случае:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Хавьер П.
источник
Тогда как удалить все свойство «y» каждого элемента объекта массива?
5ervant
@ 5ervant Я думаю, что это еще один вопрос, но я бы посоветовал вам сопоставить массив и применить здесь любое из данных решений
Хавьер П.
9

С 2019 года есть еще один вариант - использовать этот Object.fromEntriesметод. Он достиг стадии 4.

const newC = Object.fromEntries(
    Object.entries(state.c).filter(([key]) => key != 'y')
)
const newState = {...state, c: newC}

Приятно то, что он прекрасно обрабатывает целочисленные ключи.

цзянь
источник
2

Проблема в том, что вы не глубоко клонируете свое исходное состояние. Итак, у вас есть неглубокая копия.

Вы можете использовать оператор распространения

  const newState = { ...state, c: { ...state.c } };
  delete newState.c.y

Или следуя тому же коду

let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
Хуан Керри
источник
1

Я обычно использую

Object.assign({}, existingState, {propToRemove: undefined})

Я понимаю, что на самом деле это не удаляет свойство, а почти для всех целей 1 его функциональный эквивалент. Синтаксис для этого намного проще, чем альтернативы, что, как мне кажется, является довольно хорошим компромиссом.

1 Если вы используете hasOwnProperty(), вам нужно будет использовать более сложное решение.

Не любил
источник
1

Я использую этот шаблон

const newState = Object.assign({}, state);
      delete newState.show;
      return newState;

но в книге я увидел другой образец

return Object.assign({}, state, { name: undefined } )
zloctb
источник
Второй ключ не вынимает. Он просто устанавливает значение undefined.
bman
1

полезность ;))

const removeObjectField = (obj, field) => {

    // delete filter[selectName]; -> this mutates.
    const { [field]: remove, ...rest } = obj;

    return rest;
}

тип действия

const MY_Y_REMOVE = 'MY_Y_REMOVE';

создатель действий

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

        return dispatch =>
            dispatch({
                type: MY_Y_REMOVE,
                payload: result
            })
    }

редуктор

export default (state ={}, action) => {
  switch (action.type) {
    case myActions.MY_Y_REMOVE || :
      return { ...state, c: action.payload };
    default:
      return state;
  }
};
Муса
источник
0

Как уже упоминалось в некоторых ответах, это потому, что вы пытаетесь изменить вложенное состояние, т.е. на один уровень глубже. Каноническим решением было бы добавить редуктор на xуровне состояния:

const state = {
    a: '1',
    b: '2',
    c: {
       x: '42',
       y: '43'
    },
}

Редуктор более глубокого уровня

let newDeepState = Object.assign({}, state.c);
delete newDeepState.y;

Оригинальный редуктор уровня

let newState = Object.assign({}, state, {c: newDeepState});
Мешко
источник