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");dà{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.