Remove a property in an object immutably

2019-03-07 21:16发布

问题:

I am using Redux. In my reducer I'm trying to remove a property from an object like this:

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

And I want to have something like this without having to mutate the original state:

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

I tried:

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

but for some reasons, it deletes the property from both states.

Could help me to do that?

回答1:

How about using destructuring assignment syntax?

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 } }



回答2:

I find ES5 array methods like filter, map and reduce useful because they always return new arrays or objects. In this case I'd use Object.keys to iterate over the object, and Array#reduce to turn it back into an object.

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


回答3:

You can use _.omit(object, [paths]) from lodash library

path can be nested for example: _.omit(object, ['key1.key2.key3'])



回答4:

That's because you are copying the value of state.c to the other object. And that value is a pointer to another javascript object. So, both of those pointers are pointing to the same object.

Try this:

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;

You can also do a deep-copy of the object. See this question and you'll find what's best for you.



回答5:

Just use ES6 object destructuring feature

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



回答6:

How about this:

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

It filters the key that should be deleted then builds a new object from the remaining keys and the initial object. The idea is stolen from Tyler McGinnes awesome reactjs program.

JSBin



回答7:

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

Also, if looking for a functional programming toolkit, look at Ramda.



回答8:

You may use Immutability helper in order to unset an attribute, in your case:

import update from 'immutability-helper';

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


回答9:

It's easy with Immutable.js:

const newState = state.deleteIn(['c', 'y']);

description of deleteIn()



回答10:

The issue you are having is that you are not deep cloning your initial state. So you have a shallow copy.

You could use spread operator

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

Or following your same code

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


回答11:

I normally use

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

I realise this isn't actually removing the property but for almost all purposes 1 its functionally equivalent. The syntax for this is much simpler than the alternatives which I feel is a pretty good tradeoff.

1 If you are using hasOwnProperty(), you will need to use the more complicated solution.



回答12:

I use this pattern

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

but in book i saw another pattern

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


回答13:

As hinted in some of the answers already, it's because you are trying to modify a nested state ie. one level deeper. A canonical solution would be to add a reducer on the x state level:

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

Deeper level reducer

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

Original level reducer

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


回答14:

utility ;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

action type

const MY_Y_REMOVE = 'MY_Y_REMOVE';

action creator

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

reducer

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