Rimuovere una proprietà in un oggetto immutabilmente


145

Sto usando Redux. Nel mio riduttore sto cercando di rimuovere una proprietà da un oggetto come questo:

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

E voglio avere qualcosa del genere senza dover mutare lo stato originale:

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

Provai:

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

ma per alcuni motivi, elimina la proprietà da entrambi gli stati.

Potrebbe aiutarmi a farlo?


3
Si noti che Object.assigncrea solo una copia superficiale di statee quindi state.ce newState.cpunterà allo stesso oggetto condiviso. Si è tentato di eliminare la proprietà ydall'oggetto condiviso ce non dal nuovo oggetto newState.
Akseli Palén,

Risposte:


224

Che ne dici di usare la sintassi di assegnazione destrutturante ?

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
meraviglioso, senza libreria extra, usa es6 e abbastanza semplice.
sbk201,

La soluzione più elegante
long.luc

12
Bello! Ecco una funzione di aiuto es6 per andare con esso const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Uso: deleteProperty({a:1, b:2}, "a");{b:2}
mikebridge il

super intelligente, sono in ritardo con questo, ma uno dei miei nuovi preferiti
Gavin

1
Si noti che l'esempio di rimozione profonda si arresta in modo anomalo se deep['c']è vuoto, quindi in un caso generale, è possibile che si desideri aggiungere un controllo per la presenza della chiave.
Laurent S,

46

Trovo ES5 metodi di array come filter, mape reduceutili perché ritornano sempre nuovi array o oggetti. In questo caso Object.keysuserei per iterare l'oggetto eArray#reduce per trasformarlo in un oggetto.

return Object.assign({}, state, {
    c: Object.keys(state.c).reduce((result, key) => {
        if (key !== 'y') {
            result[key] = state.c[key];
        }
        return result;
    }, {})
});

intervenendo con la mia leggera modifica per rendere più chiaro l'IMO ... ti consente anche di omettere più proprietà. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) risultato [key] = state.c [key] restituisce risultato; ...
josh-sachs,

3
ES6 equivalente per ottenere una copia di myObjectcon la chiave myKeyrimossa:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
Transang

38

È possibile utilizzare _.omit(object, [paths])dalla libreria lodash

il percorso può essere nidificato ad esempio: _.omit(object, ['key1.key2.key3'])


3
Sfortunatamente, _.omitnon è possibile eliminare le proprietà profonde (cosa chiedeva OP). C'è un omit-deep-lodashmodulo per questo scopo.
AlexM,

2
Spegnendo quello che stava dicendo @AlexM. L'ho trovato utile, e potrebbe essere più appropriato per noi _.cloneDeep(obj)da lodash. Questo copia facilmente l'oggetto e quindi puoi semplicemente usare js delete obj.[key]per rimuovere la chiave.
Alex J

Accetto w / @AlexJ, cloneDeep funziona perfettamente e puoi usare anche la sintassi di diffusione con essa: ..._. CloneDeep (stato) per Redux
Sean Chase,

32

Basta usare la funzione di distruzione degli oggetti 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.cpotrebbe essere un po 'più chiaro che avere due csul lato sinistro.
dosentmatter,

10
attenzione: questo non funziona per un oggetto digitato da numeri interi
daviestar

3
Dalla risposta di seguito, se è necessario fare riferimento al nome della variabile da eliminare: const name = 'c'quindi è possibile fare const {[name]:deletedValue, ...newState} = statequindi tornare newStatenel proprio riduttore. Questo è per una cancellazione dei tasti di livello superiore
FFF

23

Questo perché stai copiando il valore di state.c dell'altro oggetto. E quel valore è un puntatore a un altro oggetto JavaScript. Quindi, entrambi questi puntatori puntano allo stesso oggetto.

Prova questo:

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;

Puoi anche fare una copia profonda dell'oggetto. Vedi questa domanda e troverai ciò che è meglio per te.


2
Questa è una risposta eccellente! state.cè un riferimento e il riferimento viene copiato correttamente. Redux vuole una forma di stato normalizzata, il che significa usare id invece di riferimenti durante lo stato di annidamento. Dai
Ziggy

17

Cosa ne pensi di questo:

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

Filtra la chiave da eliminare, quindi crea un nuovo oggetto dalle chiavi rimanenti e dall'oggetto iniziale. L'idea è stata rubata dal fantastico programma di reazione di Tyler McGinnes.

JSBin


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

Inoltre, se cerchi un toolkit di programmazione funzionale, guarda Ramda .


9

È possibile utilizzare l'helper di immutabilità per disinserire un attributo, nel tuo caso:

import update from 'immutability-helper';

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

Quindi come eliminare tutte le proprietà "y" di ogni oggetto oggetto dell'array?
5

@ 5ervant Penso che sia un'altra domanda, ma ti suggerirei di mappare l'array e applicare una delle soluzioni fornite qui
Javier P

8

A partire dal 2019, un'altra opzione è quella di utilizzare il Object.fromEntriesmetodo. Ha raggiunto la fase 4.

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

La cosa bella è che gestisce bene i tasti interi.



3

Il problema che stai riscontrando è che non stai clonando in profondità il tuo stato iniziale. Quindi hai una copia superficiale.

È possibile utilizzare l'operatore di diffusione

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

O seguendo il tuo stesso codice

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

1

Io di solito uso

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

Mi rendo conto che questo non sta effettivamente rimuovendo la proprietà ma per quasi tutti gli scopi 1 è funzionalmente equivalente. La sintassi per questo è molto più semplice delle alternative che ritengo un compromesso piuttosto buono.

1 Se si utilizza hasOwnProperty(), sarà necessario utilizzare la soluzione più complicata.


1

Uso questo modello

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

ma nel libro ho visto un altro modello

return Object.assign({}, state, { name: undefined } )

Il secondo, non rimuove la chiave. Lo imposta su indefinito.
Bman,

1

utilità ;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

tipo di azione

const MY_Y_REMOVE = 'MY_Y_REMOVE';

creatore di azioni

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

riduttore

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

0

Come già accennato in alcune delle risposte, è perché stai cercando di modificare uno stato nidificato, ad es. un livello più profondo. Una soluzione canonica sarebbe quella di aggiungere un riduttore a xlivello statale:

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

Riduttore di livello più profondo

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

Riduttore di livello originale

let newState = Object.assign({}, state, {c: newDeepState});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.