Supprimer une propriété dans un objet immuablement

145

J'utilise Redux. Dans mon réducteur, j'essaye de supprimer une propriété d'un objet comme celui-ci:

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

Et je veux avoir quelque chose comme ça sans avoir à muter l'état d'origine:

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

J'ai essayé:

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

mais pour certaines raisons, il supprime la propriété des deux états.

Pourrait m'aider à faire ça?

Vincent Taing
la source
3
Notez que Object.assigncrée uniquement une copie superficielle de stateet donc state.cet newState.cpointera vers le même objet partagé. Vous avez essayé de supprimer la propriété yde l'objet partagé cet non du nouvel objet newState.
Akseli Palén

Réponses:

224

Que diriez-vous d'utiliser la syntaxe d' affectation de déstructuration ?

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
la source
3
merveilleux, sans bibliothèque supplémentaire, utilisez es6 et assez simple.
sbk201
Solution la plus élégante
long.luc
12
Agréable! Voici une fonction d'assistance es6 pour l'accompagner const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Utilisation: deleteProperty({a:1, b:2}, "a");donne{b:2}
mikebridge
super intelligent, je suis en retard pour celui-ci, mais l'un de mes nouveaux favoris
Gavin
1
Notez que l'exemple de suppression profonde plantera s'il deep['c']est vide, donc dans un cas général, vous voudrez peut-être ajouter une vérification de la présence de la clé.
Laurent S
46

Je trouve les méthodes de tableau ES5 comme filter, mapet reduceutiles car elles renvoient toujours de nouveaux tableaux ou objets. Dans ce cas, j'utiliserais Object.keyspour parcourir l'objet et Array#reducele transformer en objet.

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
la source
sonner avec mon léger changement pour le rendre plus clair IMO ... vous permet également d'omettre plusieurs propriétés. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) result [key] = state.c [key] return result; ...
josh-sachs
3
ES6 équivalent pour obtenir une copie de myObjectavec la clé myKeyretirée:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
transang
38

Vous pouvez utiliser à _.omit(object, [paths])partir de la bibliothèque lodash

le chemin peut être imbriqué par exemple: _.omit(object, ['key1.key2.key3'])

Dmitri
la source
3
Malheureusement, _.omitimpossible de supprimer les propriétés profondes (ce qu'OP demandait). Il y a un omit-deep-lodashmodule à cet effet.
AlexM
2
En partant de ce que disait @AlexM. Je l'ai trouvé utile, et peut-être plus approprié, pour nous _.cloneDeep(obj)de lodash. Cela copie facilement l'objet et vous pouvez simplement utiliser js delete obj.[key]pour supprimer la clé.
Alex J
D'accord avec @AlexJ, cloneDeep fonctionne parfaitement et vous pouvez également utiliser la syntaxe de propagation avec: ..._. CloneDeep (état) pour Redux
Sean Chase
32

Utilisez simplement la fonction de déstructuration d'objets 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
la source
8
const {y, ...c} = state.cpeut être un peu plus clair que d 'en avoir deux csur le côté gauche.
dosentmatter
10
attention: cela ne fonctionne pas pour un objet saisi par des entiers
daviestar
3
À partir de la réponse ci-dessous, si vous avez besoin de référencer le nom de la variable à supprimer: const name = 'c'alors vous pouvez le faire const {[name]:deletedValue, ...newState} = statepuis retourner newStatedans votre réducteur. Ceci est pour une suppression de clé de niveau supérieur
FFF
23

C'est parce que vous copiez la valeur de state.cdans l'autre objet. Et cette valeur est un pointeur vers un autre objet javascript. Ainsi, ces deux pointeurs pointent vers le même objet.

Essaye ça:

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;

Vous pouvez également faire une copie complète de l'objet. Consultez cette question et vous trouverez ce qui vous convient le mieux.

Aᴍɪʀ
la source
2
C'est une excellente réponse! state.cest une référence, et la référence est copiée très bien. Redux veut une forme d'état normalisée, ce qui signifie utiliser des identifiants au lieu de références lors de l'imbrication de l'état. Consultez les documents redux: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy
17

Que dis-tu de ça:

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

Il filtre la clé à supprimer puis crée un nouvel objet à partir des clés restantes et de l'objet initial. L'idée est volée au génial programme reactjs de Tyler McGinnes.

JSBin

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

Aussi, si vous recherchez une boîte à outils de programmation fonctionnelle, regardez Ramda .

Dominykas Mostauskis
la source
9

Vous pouvez utiliser l' assistant d'immuabilité pour annuler la définition d'un attribut, dans votre cas:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P
la source
Alors comment supprimer toute la propriété "y" de chaque élément d'objet de tableau?
5ervant
@ 5ervant Je pense que c'est une autre question, mais je vous suggère de mapper le tableau et d'appliquer l'une des solutions données ici
Javier P
8

À partir de 2019, une autre option consiste à utiliser la Object.fromEntriesméthode. Il a atteint le stade 4.

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

Ce qui est bien, c'est qu'il gère bien les clés entières.

jian
la source
3

Le problème que vous rencontrez est que vous ne clonez pas en profondeur votre état initial. Vous avez donc une copie superficielle.

Vous pouvez utiliser l'opérateur de diffusion

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

Ou en suivant votre même code

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

J'utilise normalement

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

Je me rends compte que cela ne supprime pas réellement la propriété, mais à presque toutes les fins 1 son équivalent fonctionnellement. La syntaxe pour cela est beaucoup plus simple que les alternatives qui, à mon avis, sont un très bon compromis.

1 Si vous utilisez hasOwnProperty(), vous devrez utiliser la solution la plus compliquée.

Pas aimé
la source
1

J'utilise ce modèle

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

mais dans le livre j'ai vu un autre modèle

return Object.assign({}, state, { name: undefined } )
zloctb
la source
Le second, ne retire pas la clé. Il le définit simplement sur indéfini.
bman
1

utilité;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

type d'action

const MY_Y_REMOVE = 'MY_Y_REMOVE';

créateur d'action

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

réducteur

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

Comme indiqué dans certaines des réponses déjà, c'est parce que vous essayez de modifier un état imbriqué ie. un niveau plus profond. Une solution canonique serait d'ajouter un réducteur au xniveau de l' état:

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

Réducteur de niveau plus profond

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

Réducteur de niveau d'origine

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