Entfernen Sie eine Eigenschaft in einem Objekt unveränderlich

145

Ich benutze Redux. In meinem Reduzierer versuche ich, eine Eigenschaft aus einem Objekt wie diesem zu entfernen:

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

Und ich möchte so etwas haben, ohne den ursprünglichen Zustand mutieren zu müssen:

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

Ich habe es versucht:

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

Aus bestimmten Gründen wird die Eigenschaft jedoch aus beiden Zuständen gelöscht.

Könnte mir dabei helfen?

Vincent Taing
quelle
3
Beachten Sie, dass Object.assignnur eine flache Kopie von und erstellt wird stateund daher auf dasselbe gemeinsam genutzte Objekt verweist. Sie haben versucht, die Eigenschaft aus dem freigegebenen Objekt und nicht aus dem neuen Objekt zu löschen . state.cnewState.cycnewState
Akseli Palén

Antworten:

224

Wie wäre es mit der Verwendung der Destrukturierungszuweisungssyntax ?

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
quelle
3
wunderbar, ohne zusätzliche Bibliothek, nutzen Sie es6 und einfach genug.
sbk201
Eleganteste Lösung
long.luc
12
Nett! Hier ist eine es6-Hilfsfunktion, die dazu passt const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Verwendung: deleteProperty({a:1, b:2}, "a");gibt{b:2}
Mikebridge
Super klug, ich bin zu spät dran, aber einer meiner neuen Favoriten
Gavin
1
Beachten Sie, dass das Beispiel für die Tiefenentfernung abstürzt, wenn deep['c']es leer ist. In einem allgemeinen Fall möchten Sie möglicherweise eine Überprüfung auf das Vorhandensein des Schlüssels hinzufügen.
Laurent S
46

Ich finde ES5 Array Methoden wie filter, mapund reducenützlich , weil sie immer neue Arrays oder Objekte zurück. In diesem Fall würde ich Object.keysdas Objekt durchlaufen und Array#reducees wieder in ein Objekt verwandeln.

return Object.assign({}, state, {
    c: Object.keys(state.c).reduce((result, key) => {
        if (key !== 'y') {
            result[key] = state.c[key];
        }
        return result;
    }, {})
});
David L. Walsh
quelle
Wenn Sie sich meiner geringfügigen Änderung anschließen, um es klarer zu machen, können Sie auch mehrere Eigenschaften weglassen. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) result [key] = state.c [key] return result; ...
Josh-Sachs
2
ES6-Äquivalent, um eine Kopie myObjectmit myKeyentferntem Schlüssel zu erhalten :Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
Transang
38

Sie können _.omit(object, [paths])aus der lodash Bibliothek verwenden

Pfad kann zum Beispiel verschachtelt sein: _.omit(object, ['key1.key2.key3'])

Dmitri
quelle
3
Leider _.omitkönnen keine tiefen Eigenschaften gelöscht werden (was OP gefragt hat). Es gibt ein omit-deep-lodashModul für diesen Zweck.
AlexM
2
Los geht's, was @AlexM gesagt hat. Ich fand es nützlich und vielleicht angemessener für uns _.cloneDeep(obj)von lodash. Das kopiert das Objekt einfach und dann können Sie einfach js verwenden delete obj.[key], um den Schlüssel zu entfernen.
Alex J
Stimmen Sie mit @AlexJ überein, cloneDeep funktioniert perfekt und Sie können auch die Spread-Syntax damit verwenden: ..._. CloneDeep (Status) für Redux
Sean Chase
32

Verwenden Sie einfach die ES6-Objektzerstörungsfunktion

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

Ramon Diogo
quelle
8
const {y, ...c} = state.ckönnte etwas klarer sein als zwei cauf der linken Seite.
Dosentmatter
10
Achtung: Dies funktioniert nicht für ein Objekt, das mit ganzen Zahlen
verschlüsselt ist
3
Aus der unten Antwort, wenn Sie die Variablennamen verweisen müssen gelöscht werden: const name = 'c'dann können Sie Sie const {[name]:deletedValue, ...newState} = statedann zurück newStatein Ihrem Minderer. Dies ist für eine Schlüssellöschung der obersten Ebene
FFF
23

Das liegt daran, dass Sie den Wert von kopieren state.c auf das andere Objekt . Und dieser Wert ist ein Zeiger auf ein anderes Javascript-Objekt. Beide Zeiger zeigen also auf dasselbe Objekt.

Versuche dies:

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;

Sie können das Objekt auch tief kopieren. Sehen Sie sich diese Frage an und finden Sie heraus, was für Sie am besten ist.

Aᴍɪʀ
quelle
2
Dies ist eine ausgezeichnete Antwort! state.cist eine Referenz, und die Referenz wird einwandfrei kopiert. Redux möchte eine normalisierte Statusform, dh beim Verschachteln des Status werden IDs anstelle von Referenzen verwendet. Schauen Sie sich die Redux-Dokumente an: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy
17

Wie wäre es damit:

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

Es filtert den Schlüssel, der gelöscht werden soll, und erstellt dann ein neues Objekt aus den verbleibenden Schlüsseln und dem ursprünglichen Objekt. Die Idee wurde Tyler McGinnes fantastischem Reaktionsprogramm gestohlen.

JSBin

SebK
quelle
11
function dissoc(key, obj) {
  let copy = Object.assign({}, obj)
  delete copy[key]
  return copy
}

Wenn Sie nach einem Toolkit für die funktionale Programmierung suchen, schauen Sie sich auch Ramda an .

Dominykas Mostauskis
quelle
9

Sie können den Unveränderlichkeitshelfer verwenden, um ein Attribut zu deaktivieren. In Ihrem Fall:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P.
quelle
Wie lösche ich dann alle Eigenschaften "y" jedes Array-Objektelements?
5ervant
@ 5ervant Ich denke, das ist eine andere Frage, aber ich würde vorschlagen, dass Sie das Array zuordnen und eine der angegebenen Lösungen hier anwenden
Javier P
8

Ab 2019 besteht eine weitere Option darin, die Object.fromEntries Methode zu verwenden. Es hat Stufe 4 erreicht.

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

Das Schöne daran ist, dass es ganzzahlige Schlüssel gut handhabt.

Jian
quelle
3

Das Problem, das Sie haben, ist, dass Sie Ihren Ausgangszustand nicht tief klonen. Sie haben also eine flache Kopie.

Sie können den Spread-Operator verwenden

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

Oder folgen Sie demselben Code

let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
Juan Carrey
quelle
1

Ich benutze normalerweise

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

Mir ist klar, dass dies nicht das Eigentum entfernt, sondern für fast alle Zwecke 1 funktional gleichwertig. Die Syntax dafür ist viel einfacher als die Alternativen, die ich für einen ziemlich guten Kompromiss halte.

1 Wenn Sie verwenden hasOwnProperty(), müssen Sie die kompliziertere Lösung verwenden.

Nicht geliebt
quelle
1

Ich benutze dieses Muster

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

aber im Buch sah ich ein anderes Muster

return Object.assign({}, state, { name: undefined } )
zloctb
quelle
Der zweite entfernt den Schlüssel nicht. Es setzt es einfach auf undefiniert.
Bman
1

Nützlichkeit ;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

Aktionstyp

const MY_Y_REMOVE = 'MY_Y_REMOVE';

Aktionsersteller

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

Reduzierstück

export default (state ={}, action) => {
  switch (action.type) {
    case myActions.MY_Y_REMOVE || :
      return { ...state, c: action.payload };
    default:
      return state;
  }
};
Musa
quelle
0

Wie bereits in einigen Antworten angedeutet, liegt dies daran, dass Sie versuchen, einen verschachtelten Status zu ändern, d. H. eine Ebene tiefer. Eine kanonische Lösung wäre, einen Reduzierer auf xstaatlicher Ebene hinzuzufügen :

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

Tieferer Reduzierer

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

Original Pegelreduzierer

let newState = Object.assign({}, state, {c: newDeepState});
Mieszko
quelle