Aby zmodyfikować głęboko zagnieżdżonych / zmiennych w React Państwowej, używane są zwykle trzy sposoby: wanilia JavaScript użytkownika Object.assign
, niezmienność pomocniczych i cloneDeep
od Lodash .
Istnieje również wiele innych mniej popularnych bibliotek stron trzecich, aby to osiągnąć, ale w tej odpowiedzi omówię tylko te trzy opcje. Istnieją również dodatkowe waniliowe metody JavaScript, takie jak rozkładanie tablic (patrz na przykład odpowiedź @ mpen), ale nie są one bardzo intuicyjne, łatwe w użyciu i zdolne do obsługi wszystkich sytuacji manipulacji stanem.
Jak wskazano niezliczoną ilość razy w najczęściej głosowanych komentarzach do odpowiedzi, których autorzy proponują bezpośrednią mutację stanu: po prostu nie rób tego . Jest to wszechobecny anty-wzór React, który nieuchronnie doprowadzi do niepożądanych konsekwencji. Naucz się właściwej drogi.
Porównajmy trzy powszechnie stosowane metody.
Biorąc pod uwagę ten stan struktury obiektu:
state = {
outer: {
inner: 'initial value'
}
}
Możesz użyć następujących metod, aby zaktualizować najbardziej wewnętrznie inner
pola bez wpływu na resztę stanu.
1. Object.assign Vanilla JavaScript
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
Należy pamiętać, że Object.assign nie wykona głębokiego klonowania , ponieważ kopiuje tylko wartości właściwości , i dlatego to, co robi, nazywa się płytkim kopiowaniem (patrz komentarze).
Aby to zadziałało, powinniśmy jedynie manipulować właściwościami typów pierwotnych ( outer.inner
), czyli ciągów, liczb, boolanów.
W tym przykładzie tworzymy nową stałą ( const newOuter...
), używając Object.assign
, która tworzy pusty obiekt ( {}
), kopiuje do niego outer
obiekt ( { inner: 'initial value' }
), a następnie kopiuje { inner: 'updated value' }
nad nim inny obiekt .
W ten sposób na koniec nowo utworzona newOuter
stała będzie miała wartość od { inner: 'updated value' }
momentu inner
przesłonięcia właściwości. Jest newOuter
to zupełnie nowy obiekt, który nie jest powiązany z obiektem w stanie, więc można go w razie potrzeby mutować, a stan pozostanie taki sam i nie zmieni się do momentu uruchomienia polecenia aktualizacji.
Ostatnią częścią jest użycie setOuter()
setera do zastąpienia oryginału outer
w stanie nowo utworzonym newOuter
obiektem (zmieni się tylko wartość, nazwa właściwości outer
się nie zmieni).
Teraz wyobraź sobie, że mamy głębszy stan state = { outer: { inner: { innerMost: 'initial value' } } }
. Możemy spróbować utworzyć newOuter
obiekt i zapełnić go outer
treścią ze stanu, ale Object.assign
nie będziemy mogli skopiować innerMost
wartości do tego nowo utworzonego newOuter
obiektu, ponieważinnerMost
jest on zbyt głęboko zagnieżdżony.
Możesz nadal kopiować inner
, jak w powyższym przykładzie, ale ponieważ jest to teraz obiekt, a nie prymityw, odwołanie z newOuter.inner
zostanie skopiowane do outer.inner
niego, co oznacza, że otrzymamy newOuter
obiekt lokalny bezpośrednio powiązany z obiektem w stanie .
Oznacza to, że w tym przypadku mutacje lokalnie utworzonego newOuter.inner
będą miały bezpośredni wpływ naouter.inner
obiekt (w stanie), ponieważ w rzeczywistości stały się tym samym (w pamięci komputera).
Object.assign
dlatego będzie działać tylko wtedy, gdy masz stosunkowo prostą jednopoziomową strukturę stanu głębokiego z najbardziej wewnętrznymi elementami przechowującymi wartości typu pierwotnego.
Jeśli masz głębsze obiekty (2. poziom lub więcej), które powinieneś zaktualizować, nie używaj Object.assign
. Ryzykujesz mutacją stanu bezpośrednio.
2. Klon Lodasha
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
Klon Lodasha jest znacznie prostszy w użyciu. Wykonuje głębokie klonowanie , więc jest solidną opcją, jeśli masz dość złożony stan z wielopoziomowymi obiektami lub tablicami. Tylko cloneDeep()
właściwość stanu najwyższego poziomu, mutuj sklonowaną część w dowolny sposób isetOuter()
wróć do stanu.
3. pomocnik niezmienności
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
const update = immutabilityHelper
console.log('Before the deep cloning and updating:', outer.inner) // initial value
const newOuter = update(outer, { inner: { $set: 'updated value' } })
console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>
<main id="react"></main>
immutability-helper
bierze go na zupełnie nowy poziom, a chłodny rzeczą jest to, że może on nie tylko $set
wartości do elementów państwowych, ale również $push
, $splice
, $merge
(itd.) im. Tutaj jest lista dostępnych poleceń .
Notatki dodatkowe
Ponownie pamiętaj, że setOuter
modyfikuje on tylko właściwości pierwszego poziomu obiektu stanu ( outer
w tych przykładach), a nie głęboko zagnieżdżone (outer.inner
). Gdyby zachowywał się inaczej, to pytanie by nie istniało.
Który jest odpowiedni dla twojego projektu?
Jeśli nie chcesz lub nie możesz korzystać z zewnętrznych zależności i masz prostą strukturę stanu , trzymaj się Object.assign
.
Jeśli manipulujesz ogromnym i / lub złożonym stanem , Lodash's cloneDeep
to mądry wybór.
Jeśli potrzebujesz zaawansowanych możliwości , tj. Jeśli twoja struktura stanu jest złożona i musisz wykonywać na niej wszelkiego rodzaju operacje, spróbuj immutability-helper
, jest to bardzo zaawansowane narzędzie, które można wykorzystać do manipulowania stanem.
... czy naprawdę naprawdę musisz to zrobić?
Jeśli przechowujesz złożone dane w stanie React, być może jest to dobry moment, aby pomyśleć o innych sposobach ich obsługi. Ustawienie złożonych obiektów stanu bezpośrednio w komponentach React nie jest prostą operacją i zdecydowanie sugeruję zastanowienie się nad różnymi podejściami.
Najprawdopodobniej lepiej nie trzymaj swoich złożonych danych w sklepie Redux, ustawiając je tam za pomocą reduktorów i / lub sag i uzyskując dostęp do nich za pomocą selektorów.