Видаліть властивість в об'єкті незмінно


145

Я використовую 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.
Акселі Пален

Відповіді:


224

Як щодо використання синтаксису призначення деструктування ?

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


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']він порожній, тому в загальному випадку ви можете додати чек на наявність ключа.
Лоран 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;
    }, {})
});

ввімкнути мою незначну зміну, щоб зробити її зрозумілішою IMO ... також дозволяє опустити кілька властивостей. const omit = ['prop1', 'prop2'] ... якщо (omit.indexOf (ключ) === -1) результат [ключ] = state.c [ключ] повернути результат; ...
josh-sachs

3
Еквівалент ES6, щоб отримати копію myObjectключа, myKeyвидаленого:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
трансанг

38

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

шлях може бути вкладений, наприклад: _.omit(object, ['key1.key2.key3'])


3
На жаль, _.omitне можна видалити глибокі властивості (про що запитував ОП). Для цього є omit-deep-lodashмодуль.
AlexM

2
Відключаючи те, що говорив @AlexM. Я вважав це корисним, і це, можливо, буде більш доцільним для нас _.cloneDeep(obj)із лодаша. Це легко копіює об'єкт, і тоді ви можете просто використовувати js delete obj.[key]для видалення ключа.
Alex J

Погодьтеся w / @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зліва на лівій стороні.
дозувальник

10
будьте обережні: це не працює для об’єкта,
набраного

3
З нижченаведеної відповіді, якщо вам потрібно посилатися на ім’я змінної, яку потрібно видалити: const name = 'c'тоді ви можете це зробити, const {[name]:deletedValue, ...newState} = stateповертаючись newStateу редуктор. Це для видалення ключових
класів

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;

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


2
Це відмінна відповідь! state.cє посиланням, а посилання копіюється просто чудово. Redux хоче нормалізовану форму стану, що означає використання ідентифікаторів замість посилань при вкладенні стану. Ознайомтесь із документами redux: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy

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 .


9

Ви можете скористатись помічником Immutability , щоб скасувати атрибут у вашому випадку:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    

Тоді як видалити все властивість "y" кожного елемента об'єкта масиву?
5ervant

@ 5ervant Я думаю, що це вже інше питання, але я б запропонував вам скласти карту масиву та застосувати будь-яке із заданих тут рішень
Javier P

8

Станом на 2019 рік ще одним варіантом є використання Object.fromEntriesметоду. Він досяг 4 стадії.

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

Приємно в тому, що він прекрасно обробляє цілі клавіші.



3

Проблема у вас полягає в тому, що ви не глибоко клонуєте свій початковий стан. Отже, у вас є неглибока копія.

Ви можете використовувати оператор розповсюдження

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

Другий, ключ не виймає. Це просто встановлює його невизначеним.
бман

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