Niezmiennie usuwa właściwość z obiektu

145

Używam Redux. W moim reduktorze próbuję usunąć właściwość z obiektu takiego:

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

I chcę mieć coś takiego bez konieczności modyfikowania pierwotnego stanu:

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

Próbowałem:

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

ale z pewnych powodów usuwa właściwość z obu stanów.

Czy może mi w tym pomóc?

Vincent Taing
źródło
3
Zauważ, że Object.assigntworzy tylko płytkie kopię z statea zatem state.ci newState.cbędzie wskazywać na ten sam obiekt udostępniony. Próbowałeś usunąć właściwość yz udostępnionego obiektu, ca nie z nowego obiektu newState.
Akseli Palén

Odpowiedzi:

224

A co powiesz na użycie składni przydziału destrukturyzacji ?

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
źródło
3
wspaniałe, bez dodatkowej biblioteki, korzystaj z es6 i dość proste.
sbk201
Najbardziej eleganckie rozwiązanie
long.luc
12
Miły! Oto funkcja pomocnicza es6, która może się z tym pogodzić const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;. Sposób użycia: deleteProperty({a:1, b:2}, "a");daje{b:2}
mikebridge
super sprytny, spóźniłem się na ten, ale jeden z moich nowych ulubionych
Gavin
1
Zwróć uwagę, że przykład głębokiego usuwania ulegnie awarii, jeśli deep['c']jest pusty, więc w ogólnym przypadku możesz chcieć dodać sprawdzenie obecności klucza.
Laurent S,
46

Uważam ES5 metod tablicowych jak filter, mapi reduceużyteczny, ponieważ zawsze powrócić nowych tablic lub obiektów. W tym przypadku Object.keysużyłbym do iteracji po obiekcie i Array#reduceprzekształcenia go z powrotem w obiekt.

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
źródło
wbijanie się w moją niewielką zmianę, aby uczynić ją bardziej przejrzystą IMO ... pozwala również pominąć wiele właściwości. const omit = ['prop1', 'prop2'] ... if (omit.indexOf (key) === -1) result [key] = state.c [key] return result; ...
josh-sachs
3
Odpowiednik ES6, aby otrzymać kopię myObjectbez klucza myKey:Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})
transang
38

Możesz użyć _.omit(object, [paths])z biblioteki lodash

ścieżka może być zagnieżdżona na przykład: _.omit(object, ['key1.key2.key3'])

Dmitri
źródło
3
Niestety _.omitnie można usunąć głębokich właściwości (o co prosił OP). Do omit-deep-lodashtego celu służy moduł.
AlexM,
2
Wychodzę z tego, co mówił @AlexM. Uznałem, że jest to przydatne i może być bardziej odpowiednie dla nas _.cloneDeep(obj)z lodash. To łatwo kopiuje obiekt, a następnie możesz po prostu użyć js, delete obj.[key]aby usunąć klucz.
Alex J
Zgadzam się w / @AlexJ, cloneDeep działa doskonale i możesz z nim również korzystać ze składni spreadu: ..._. CloneDeep (stan) dla Redux
Sean Chase
32

Po prostu użyj funkcji destrukturyzacji obiektów 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
źródło
8
const {y, ...c} = state.cmoże być trochę jaśniejsze niż posiadanie dwóch cpo lewej stronie.
dosentmatter
10
uwaga: to nie działa dla obiektu z kluczem liczb całkowitych
daviestar,
3
Z poniższej odpowiedzi, jeśli chcesz odwołać się do nazwy zmiennej do usunięcia: const name = 'c'możesz to zrobić, const {[name]:deletedValue, ...newState} = statea następnie powrócić newStatedo swojego reduktora. To jest dla usunięcia klucza najwyższego poziomu
FFF
23

Dzieje się tak, ponieważ kopiujesz wartość state.cdo innego obiektu. Ta wartość jest wskaźnikiem do innego obiektu javascript. Zatem oba te wskaźniki wskazują ten sam obiekt.

Spróbuj tego:

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;

Możesz także wykonać głęboką kopię obiektu. Zobacz to pytanie, a znajdziesz to, co jest dla Ciebie najlepsze.

Aᴍɪʀ
źródło
2
To doskonała odpowiedź! state.cjest odniesieniem i jest kopiowany poprawnie. Redux chce znormalizowanego kształtu stanu, co oznacza używanie identyfikatorów zamiast odniesień podczas zagnieżdżania stanu. Sprawdź dokumenty redux: redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Ziggy
17

Co powiesz na to:

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

Filtruje klucz, który powinien zostać usunięty, a następnie tworzy nowy obiekt z pozostałych kluczy i obiektu początkowego. Pomysł został skradziony z programu niesamowitych reakcji Tylera McGinnesa.

JSBin

SebK
źródło
11
function dissoc(key, obj) {
  let copy = Object.assign({}, obj)
  delete copy[key]
  return copy
}

Ponadto, jeśli szukasz funkcjonalnego zestawu narzędzi do programowania, spójrz na Ramdę .

Dominykas Mostauskis
źródło
9

Możesz użyć pomocnika niezmienności , aby usunąć atrybut, w twoim przypadku:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P.
źródło
Jak więc usunąć całą właściwość „y” każdego elementu obiektu tablicy?
5ervant
@ 5ervant Myślę, że to kolejne pytanie, ale proponuję zmapować tablicę i zastosować tutaj dowolne z podanych rozwiązań
Javier P
8

Od 2019 roku inną opcją jest użycie Object.fromEntriesmetody. Osiągnął etap 4.

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

Zaletą jest to, że ładnie obsługuje klucze całkowite.

jian
źródło
3

Problem, który masz, polega na tym, że nie klonujesz głęboko swojego stanu początkowego. Więc masz płytką kopię.

Możesz użyć operatora spreadu

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

Lub postępując zgodnie z tym samym kodem

let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
Juan Carrey
źródło
1

Zwykle używam

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

Zdaję sobie sprawę, że w rzeczywistości nie jest to usuwanie własności, ale prawie do wszystkich celów 1 jest to równoważne funkcjonalnie. Składnia tego jest znacznie prostsza niż alternatywy, które uważam za całkiem niezły kompromis.

1 Jeśli używasz hasOwnProperty(), będziesz musiał użyć bardziej skomplikowanego rozwiązania.

Nie kochany
źródło
1

Używam tego wzoru

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

ale w książce widziałem inny wzór

return Object.assign({}, state, { name: undefined } )
zloctb
źródło
Drugi nie wyjmuje klucza. Po prostu ustawia go na niezdefiniowany.
bman
1

użyteczność;))

const removeObjectField = (obj, field) => {

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

    return rest;
}

Rodzaj działania

const MY_Y_REMOVE = 'MY_Y_REMOVE';

twórca akcji

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

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

reduktor

export default (state ={}, action) => {
  switch (action.type) {
    case myActions.MY_Y_REMOVE || :
      return { ...state, c: action.payload };
    default:
      return state;
  }
};
Musa
źródło
0

Jak już wskazano w niektórych odpowiedziach, dzieje się tak, ponieważ próbujesz zmodyfikować stan zagnieżdżony, tj. jeden poziom głębiej. Rozwiązaniem kanonicznym byłoby dodanie reduktora na xpoziomie stanu:

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

Głębszy reduktor poziomu

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

Oryginalny reduktor poziomu

let newState = Object.assign({}, state, {c: newDeepState});
Mieszko
źródło