Remover uma propriedade em um objeto imutável

145

Estou usando o Redux. No meu redutor, estou tentando remover uma propriedade de um objeto como este:

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

E eu quero ter algo assim sem ter que alterar o estado original:

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

Eu tentei:

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

mas por alguns motivos, ele exclui a propriedade dos dois estados.

Poderia me ajudar a fazer isso?

Vincent Taing
fonte
3
Note que Object.assigncria apenas uma cópia superficial de statee, portanto, state.ce newState.cirá apontar para o mesmo objeto compartilhado. Você tentou excluir a propriedade ydo objeto compartilhado ce não do novo objeto newState.
Akseli Palén

Respostas:

224

Que tal usar a sintaxe de atribuição de desestruturação ?

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
fonte
3
maravilhoso, sem biblioteca extra, use es6 e bastante simples.
sbk201
Solução mais elegante
long.luc
12
Agradável! Aqui está uma função auxiliar do es6 para acompanhá-la const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Uso: deleteProperty({a:1, b:2}, "a");gives{b:2}
mikebridge 18/01/18
super-inteligente, eu estou atrasado para um presente, mas um dos meus novos favoritos
Gavin
1
Observe que o exemplo de remoção profunda falhará se deep['c']estiver vazio; portanto, em um caso geral, convém adicionar uma verificação da presença da chave.
Laurent S
46

Acho ES5 métodos de array como filter, mape reduceútil, porque eles sempre retornam novas matrizes ou objetos. Nesse caso, eu usaria Object.keyspara iterar o objeto e Array#reducetransformá-lo novamente em um objeto.

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
fonte
concordar com minha pequena alteração para tornar mais claro o IMO ... também permite que você omita várias propriedades. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (chave) === -1) resultado [chave] = estado.c [chave] retorna resultado; ...
josh-sachs
3
Equivalente ao ES6 para obter uma cópia da myObjectchave myKeyremovida:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
transang 27/02
38

Você pode usar _.omit(object, [paths])da biblioteca lodash

O caminho pode ser aninhado, por exemplo: _.omit(object, ['key1.key2.key3'])

Dmitri
fonte
3
Infelizmente, _.omitnão é possível excluir propriedades profundas (o que o OP estava pedindo). Há omit-deep-lodashmódulo para esse fim.
AlexM
2
Saindo do que @AlexM estava dizendo. Achei útil, e pode ser mais apropriado, para nós _.cloneDeep(obj)do lodash. Isso copia o objeto facilmente e você pode simplesmente usar js delete obj.[key]para remover a chave.
Alex J
Concordo w / @AlexJ, cloneDeep funciona perfeitamente e você pode usar a sintaxe propagação com ele também: ..._ cloneDeep (estado) para Redux.
Sean perseguição
32

Basta usar o recurso de desestruturação do objeto 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

Ramon Diogo
fonte
8
const {y, ...c} = state.cpode ser um pouco mais claro do que ter dois cno lado esquerdo.
dosentmatter
10
cuidado: isso não funciona para um objeto digitado por inteiros
daviestar
3
A partir da resposta abaixo, se você precisar fazer referência ao nome da variável a ser excluída: const name = 'c'é possível const {[name]:deletedValue, ...newState} = stateretornar newStateno seu redutor. Isto é para uma exclusão de chave de nível superior
FFF 16/04
23

Isso ocorre porque você está copiando o valor state.cpara o outro objeto. E esse valor é um ponteiro para outro objeto javascript. Portanto, os dois ponteiros estão apontando para o mesmo objeto.

Tente o seguinte:

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;

Você também pode fazer uma cópia em profundidade do objeto. Veja esta pergunta e você encontrará o que é melhor para você.

Aᴍɪʀ
fonte
2
Esta é uma excelente resposta! state.cé uma referência e a referência está sendo copiada muito bem. O Redux deseja uma forma de estado normalizada, o que significa usar IDs em vez de referências ao aninhar o estado. Verifique os documentos do redux: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy
17

Que tal agora:

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

Ele filtra a chave que deve ser excluída e cria um novo objeto a partir das chaves restantes e do objeto inicial. A ideia é roubada do incrível programa reactjs de Tyler McGinnes.

JSBin

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

Além disso, se estiver procurando por um kit de ferramentas de programação funcional, consulte o Ramda .

Dominykas Mostauskis
fonte
9

Você pode usar o Auxiliar de imutabilidade para desabilitar um atributo, no seu caso:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P
fonte
Então, como excluir toda a propriedade "y" de cada item de objeto de matriz?
13/07
@ 5ervant Eu acho que é uma outra questão, mas eu sugiro que você para mapear a matriz e aplicar qualquer uma das soluções dadas aqui
Javier P
8

A partir de 2019, outra opção é usar o Object.fromEntriesmétodo Atingiu o estágio 4.

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

O bom disso é que ele lida bem com chaves inteiras.

jian
fonte
3

O problema que você está enfrentando é que você não está clonando profundamente seu estado inicial. Então você tem uma cópia superficial.

Você poderia usar o operador spread

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

Ou seguindo o mesmo código

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

Eu normalmente uso

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

Sei que isso não é realmente remover a propriedade, mas para quase todos os fins 1 é funcionalmente equivalente. A sintaxe para isso é muito mais simples do que as alternativas que considero uma troca muito boa.

1 Se você estiver usando hasOwnProperty(), precisará usar a solução mais complicada.

Não amado
fonte
1

Eu uso esse padrão

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

mas no livro eu vi outro padrão

return Object.assign({}, state, { name: undefined } )
zloctb
fonte
O segundo, não remove a chave. Apenas o define como indefinido.
bman
1

Utilitário ;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

Tipo de acão

const MY_Y_REMOVE = 'MY_Y_REMOVE';

criador de ação

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

redutor

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

Como sugerido em algumas das respostas já, é porque você está tentando modificar um estado aninhado, ou seja. um nível mais profundo. Uma solução canônica seria adicionar um redutor no xnível do estado:

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

Redutor de nível mais profundo

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

Redutor de nível original

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