Eliminar una propiedad en un objeto inmutable

145

Estoy usando Redux En mi reductor estoy tratando de eliminar una propiedad de un objeto como este:

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

Y quiero tener algo como esto sin tener que mutar el estado original:

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

Lo intenté:

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

pero por alguna razón, elimina la propiedad de ambos estados.

¿Podría ayudarme a hacer eso?

Vincent Taing
fuente
3
Tenga en cuenta que Object.assignsolo crea una copia superficial destate y por lo tanto state.cy newState.capuntará al mismo objeto compartido. Intentó eliminar la propiedad ydel objeto compartido cy no del nuevo objeto newState.
Akseli Palén

Respuestas:

224

¿Qué tal usar la sintaxis de asignación de desestructuración ?

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
fuente
3
maravilloso, sin biblioteca adicional, haga uso de es6 y lo suficientemente simple.
sbk201
La solución más elegante
long.luc
12
¡Agradable! Aquí hay una función auxiliar es6 para acompañarla const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Uso: deleteProperty({a:1, b:2}, "a");da{b:2}
mikebridge
super inteligente, llego tarde a este, pero uno de mis nuevos favoritos
Gavin
1
Tenga en cuenta que el ejemplo de eliminación profunda se bloqueará si deep['c']está vacío, por lo que, en un caso general, es posible que desee agregar una comprobación de la presencia de la clave.
Laurent S
46

Encuentro métodos de arreglos ES5 como filter, mapy reduceútiles porque siempre regreso nuevas matrices u objetos. En este caso, usaría Object.keyspara iterar sobre el objeto y Array#reducevolverlo a convertir en un 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
fuente
interviniendo con mi ligero cambio para hacerlo más claro OMI ... también le permite omitir múltiples propiedades. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) result [key] = state.c [key] return result; ...
josh-sachs
3
Equivalente a ES6 para obtener una copia myObjectcon la clave myKeyeliminada:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
transang
38

Puedes usar _.omit(object, [paths])desde la biblioteca lodash

La ruta se puede anidar, por ejemplo: _.omit(object, ['key1.key2.key3'])

Dmitri
fuente
3
Desafortunadamente, _.omitno se pueden eliminar propiedades profundas (lo que OP estaba pidiendo). Hay un omit-deep-lodashmódulo para este propósito.
AlexM
2
Dejando de lado lo que @AlexM estaba diciendo. Lo encontré útil, y podría ser más apropiado para nosotros _.cloneDeep(obj)desde lodash. Eso copia el objeto fácilmente y luego simplemente puede usar js delete obj.[key]para eliminar la clave.
Alex J
De acuerdo con @AlexJ, cloneDeep funciona perfectamente y también puede usar la sintaxis de propagación: ..._. CloneDeep (estado) para Redux
Sean Chase
32

Solo use la función de desestructuración de objetos 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
fuente
8
const {y, ...c} = state.cpodría ser un poco más claro que tener dos cen el lado izquierdo.
dosentmatter
10
cuidado: esto no funciona para un objeto
tecleado
3
De la respuesta a continuación, si necesita hacer referencia al nombre de la variable que se va a eliminar: const name = 'c'puede hacerlo y const {[name]:deletedValue, ...newState} = stateluego regresar newStateen su reductor. Esto es para una eliminación de clave de nivel superior
FFF
23

Esto se debe a que está copiando el valor de state.cal otro objeto. Y ese valor es un puntero a otro objeto javascript. Entonces, ambos punteros apuntan al mismo objeto.

Prueba esto:

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;

También puede hacer una copia profunda del objeto. Vea esta pregunta y encontrará lo que es mejor para usted.

Aᴍɪʀ
fuente
2
Esta es una excelente respuesta! state.ces una referencia, y la referencia se está copiando muy bien. Redux quiere una forma de estado normalizado, lo que significa usar identificadores en lugar de referencias al anidar el estado. Echa un vistazo a los documentos de redux: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy
17

Qué tal esto:

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

Filtra la clave que debe eliminarse y luego crea un nuevo objeto a partir de las claves restantes y el objeto inicial. La idea fue robada del increíble programa de reacciones de Tyler McGinnes.

JSBin

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

Además, si busca un kit de herramientas de programación funcional, mire a Ramda .

Dominykas Mostauskis
fuente
9

Puede usar el ayudante de Inmutabilidad para desarmar un atributo, en su caso:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P
fuente
Entonces, ¿cómo eliminar toda la propiedad "y" de cada elemento del objeto de matriz?
5ervant
@ 5ervant Creo que esa es otra pregunta, pero le sugiero que asigne la matriz y aplique cualquiera de las soluciones dadas aquí
Javier P
8

A partir de 2019, otra opción es usar el Object.fromEntriesmétodo. Ha alcanzado la etapa 4.

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

Lo bueno de esto es que maneja bien las teclas enteras.

jian
fuente
3

El problema que tiene es que no está clonando profundamente su estado inicial. Entonces tienes una copia superficial.

Podrías usar el operador de propagación

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

O siguiendo tu mismo código

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

Normalmente uso

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

Soy consciente de que no se eliminan realmente la propiedad, pero para casi todos los fines 1 su funcionalmente equivalente. La sintaxis para esto es mucho más simple que las alternativas, que creo que es una muy buena compensación.

1 Si está usando hasOwnProperty(), necesitará usar la solución más complicada.

No amado
fuente
1

Yo uso este patron

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

pero en el libro vi otro patrón

return Object.assign({}, state, { name: undefined } )
zloctb
fuente
El segundo, no quita la llave. Simplemente lo establece en indefinido.
bman
1

utilidad;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

tipo de acción

const MY_Y_REMOVE = 'MY_Y_REMOVE';

creador de acción

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

reductor

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

Como ya se insinuó en algunas de las respuestas, es porque está intentando modificar un estado anidado, es decir. Un nivel más profundo. Una solución canónica sería agregar un reductor a xnivel estatal:

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

Reductor de nivel más profundo

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

Reductor de nivel original

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