Vanilya JavaScript'in: tipik olarak üç yöntem kullanılır durumuna, tepki derin iç içe nesneler / değişkenleri değiştirmek için Object.assign, değişmezlik-yardımcı ve cloneDeepgelen Lodash .
Bunu başarmak için daha az popüler üçüncü taraf kütüphaneleri de var, ancak bu cevapta sadece bu üç seçeneği ele alacağım. Ayrıca, dizi yayma gibi bazı ek vanilya JavaScript yöntemleri de mevcuttur (örneğin @ mpen'in cevabına bakın), ancak çok sezgisel değil, kullanımı kolay ve tüm durum manipülasyon durumlarını idare edebilir.
As üst sivri sayısız kez yazarlar devletin doğrudan mutasyon teklif cevapları, açıklama olarak vardı: sadece bunu yapmaz . Bu, kaçınılmaz olarak istenmeyen sonuçlara yol açacak olan her yerde bulunan bir Tepki anti-paternidir. Doğru yolu öğrenin.
Yaygın olarak kullanılan üç yöntemi karşılaştıralım.
Bu durum nesne yapısı göz önüne alındığında:
state = {
outer: {
inner: 'initial value'
}
}
innerEyaletin geri kalanını etkilemeden en içteki alanın değerini güncellemek için aşağıdaki yöntemleri kullanabilirsiniz .
1. Vanilya JavaScript'in Object.assign
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>
O, unutmayın Object.assign derin klonlama gerçekleştirmez , çünkü sadece kopyalar gayrimenkul değerleri ve en o yüzden ona bir denir ne kopyalayarak sığ (yorumlar).
Bunun çalışması için sadece ilkel tiplerin ( outer.inner), yani dizelerin, sayıların, booleanların özelliklerini manipüle etmeliyiz .
Bu örnekte, yeni bir sabit (oluştururken const newOuter...kullanarak) Object.assign(boş bir nesne oluşturur ki, {}), kopyalar outer(nesne { inner: 'initial value' }içine) ve daha sonra kopya farklı bir nesne { inner: 'updated value' } üzerinde o.
Bu şekilde, yeni oluşturulan newOutersabit { inner: 'updated value' }, innermülkün geçersiz kılınmasından bu yana bir değer tutacaktır . Bu newOuter, durumdaki nesneyle bağlantılı olmayan yepyeni bir nesnedir, bu nedenle gerektiğinde mutasyona uğrayabilir ve durum, güncelleme komutu verilinceye kadar aynı kalır ve değişmez.
Son bölüm, durumdaki setOuter()orijinali outeryeni oluşturulan bir newOuternesneyle değiştirmek için ayarlayıcıyı kullanmaktır (yalnızca değer değişecektir, özellik adı değişmeyecektir outer).
Şimdi daha derin bir durumumuz olduğunu hayal edin state = { outer: { inner: { innerMost: 'initial value' } } }. newOuterNesneyi oluşturmaya ve durumun outeriçeriğiyle doldurmaya Object.assignçalışabiliriz , ancak çok derin iç içe olduğundan , innerMostdeğerini bu yeni oluşturulan newOuternesneye kopyalayamayacağız innerMost.
Hala kopya olabilir inneryukarıdaki örnekte olduğu gibi, ama şimdi bir nesne ve beri değil ilkel, referans gelen newOuter.innerkopyalanır outer.inneryerel ile sona erecek bunun yerine, hangi vasıta newOuterdoğrudan devlet nesneye bağlı nesne .
Bu, yerel olarak oluşturulan mutasyonların nesneyi (devlette) newOuter.innerdoğrudan etkileyeceği anlamına gelir outer.inner, çünkü bunlar aslında aynı şey haline gelmiştir (bilgisayar belleğinde).
Object.assign bu nedenle sadece ilkel tipte değerler tutan en içteki üyelere sahip nispeten basit bir seviye derin durum yapınız varsa işe yarayacaktır.
Güncellemeniz gereken daha derin nesneleriniz (2. seviye veya daha fazla) varsa kullanmayın Object.assign. Durumu doğrudan mutasyona uğratma riskiniz vardır.
2. Lodash'ın klonu
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>
Lodash'ın cloneDeep'inin kullanımı çok daha kolaydır. Derin bir klonlama yapar , bu nedenle içeride çok seviyeli nesneler veya dizilerle oldukça karmaşık bir duruma sahipseniz, sağlam bir seçenektir. Sadece cloneDeep()üst düzey durum özelliği, klonlanan parçayı istediğiniz şekilde değiştirin ve setOuter()tekrar duruma getirin.
3. değişmezlik yardımcısı
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-helperyepyeni bir seviyeye alır ve bu konuda serin şey olabilir sadece olmasıdır $setdevlet öğelere değerleri, aynı zamanda $push, $splice, $merge(vs) onları. İşte mevcut komutların bir listesi .
Yan notlar
Yine, derinden iç içe geçmiş ( ) değil, durum nesnesinin ( bu örneklerde) setOuteryalnızca birinci düzey özelliklerini değiştirdiğini unutmayın . Farklı bir şekilde davransaydı, bu soru olmazdı.outerouter.inner
Hangisi projeniz için doğru ?
Harici bağımlılıklar istemiyorsanız veya kullanamıyorsanız ve basit bir durum yapısına sahipseniz , sadık kalın Object.assign.
Büyük ve / veya karmaşık bir durumu manipüle ederseniz , Lodash's cloneDeepakıllıca bir seçimdir.
Gelişmiş yeteneklere ihtiyacınız varsa , yani devlet yapınız karmaşıksa ve üzerinde her türlü işlemi yapmanız gerekiyorsa, immutability-helperdurum manipülasyonu için kullanılabilen çok gelişmiş bir araçtır.
... yoksa bunu gerçekten yapmak zorunda mısın ?
React'ın durumunda karmaşık bir veri tutarsanız, belki de bu veriyi ele almanın diğer yollarını düşünmek için iyi bir zamandır. React bileşenlerinde karmaşık bir durum nesnesini doğru ayarlamak basit bir işlem değildir ve farklı yaklaşımları düşünmenizi şiddetle tavsiye ederim.
Büyük olasılıkla karmaşık verilerinizi bir Redux mağazasında tutmanız, redüktörler ve / veya sagalar kullanarak orada ayarlamanız ve seçiciler kullanarak erişmeniz daha iyi olur.