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 cloneDeepod 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 outerobiekt ( { inner: 'initial value' }), a następnie kopiuje { inner: 'updated value' } nad nim inny obiekt .
W ten sposób na koniec nowo utworzona newOuterstała będzie miała wartość od { inner: 'updated value' }momentu innerprzesłonięcia właściwości. Jest newOuterto 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 outerw stanie nowo utworzonym newOuterobiektem (zmieni się tylko wartość, nazwa właściwości outersię nie zmieni).
Teraz wyobraź sobie, że mamy głębszy stan state = { outer: { inner: { innerMost: 'initial value' } } }. Możemy spróbować utworzyć newOuterobiekt i zapełnić go outertreścią ze stanu, ale Object.assignnie będziemy mogli skopiować innerMostwartości do tego nowo utworzonego newOuterobiektu, 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.innerzostanie skopiowane do outer.innerniego, co oznacza, że otrzymamy newOuterobiekt lokalny bezpośrednio powiązany z obiektem w stanie .
Oznacza to, że w tym przypadku mutacje lokalnie utworzonego newOuter.innerbę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-helperbierze go na zupełnie nowy poziom, a chłodny rzeczą jest to, że może on nie tylko $setwartoś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 setOutermodyfikuje on tylko właściwości pierwszego poziomu obiektu stanu ( outerw 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 cloneDeepto 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.