ในการปรับเปลี่ยนวัตถุ / ตัวแปรที่ซ้อนกันอย่างลึกซึ้งในสถานะของ React โดยทั่วไปจะใช้วิธีการสามวิธี: vanilla JavaScript's Object.assign
, immutability-helperและcloneDeep
จากLodash Lodash
นอกจากนี้ยังมี libs บุคคลที่สามที่ได้รับความนิยมน้อยกว่าอื่น ๆ มากมายเพื่อให้บรรลุเป้าหมายนี้ แต่ในคำตอบนี้ฉันจะกล่าวถึงตัวเลือกทั้งสามนี้ นอกจากนี้ยังมีวิธีการบางอย่างเพิ่มเติมของ vanilla JavaScript เช่นการกระจายอาร์เรย์ (ดูคำตอบของ @ mpen ตัวอย่าง) แต่มันไม่ง่ายนักใช้งานง่ายและสามารถจัดการกับสถานการณ์การจัดการสถานะทั้งหมด
ตามที่ได้ชี้ไปครั้งนับไม่ถ้วนในการโหวตความคิดเห็นด้านบนเพื่อตอบซึ่งผู้เขียนเสนอการกลายพันธุ์โดยตรงของรัฐ: เพียงแค่ไม่ทำอย่างนั้นก็ไม่ได้ทำอย่างนั้นนี่คือรูปแบบการต่อต้านปฏิกิริยาที่แพร่หลายซึ่งจะนำไปสู่ผลลัพธ์ที่ไม่พึงประสงค์อย่างหลีกเลี่ยงไม่ได้ เรียนรู้วิธีการที่เหมาะสม
ลองเปรียบเทียบสามวิธีที่ใช้กันอย่างแพร่หลาย
รับโครงสร้างวัตถุสถานะนี้:
state = {
outer: {
inner: 'initial value'
}
}
คุณสามารถใช้วิธีการต่อไปนี้เพื่ออัปเดตส่วนใหญ่ inner
ค่าของเขตข้อมูลโดยไม่ส่งผลกระทบต่อส่วนที่เหลือของรัฐ
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>
โปรดทราบว่าObject.assign จะไม่ทำการโคลนนิ่งอย่างลึกล้ำเนื่องจากคัดลอกเฉพาะค่าคุณสมบัติและนั่นคือสาเหตุที่สิ่งที่เรียกว่าการคัดลอกตื้น (ดูความคิดเห็น)
สำหรับการทำงานเราควรจัดการคุณสมบัติของดั้งเดิมชนิด ( outer.inner
) นั่นคือสตริงตัวเลขบูลีน
ในตัวอย่างนี้เรากำลังสร้างค่าคงที่ใหม่ ( const newOuter...
) โดยใช้Object.assign
ซึ่งสร้างวัตถุว่างเปล่า ( {}
) คัดลอกouter
วัตถุ ( { inner: 'initial value' }
) ลงในมันแล้วคัดลอกวัตถุอื่น{ inner: 'updated value' }
ไปมัน
วิธีนี้ในที่สุดnewOuter
ค่าคงที่ที่สร้างขึ้นใหม่จะเก็บค่า{ inner: 'updated value' }
ตั้งแต่inner
คุณสมบัติถูกแทนที่ นี้newOuter
เป็นวัตถุใหม่ที่ไม่ได้เชื่อมโยงกับวัตถุในสถานะดังนั้นจึงสามารถกลายพันธุ์ได้ตามต้องการและสถานะจะยังคงเหมือนเดิมและจะไม่เปลี่ยนแปลงจนกว่าจะมีการรันคำสั่งเพื่ออัปเดต
ส่วนสุดท้ายคือการใช้setOuter()
setter เพื่อแทนที่ต้นฉบับouter
ในสถานะด้วยnewOuter
วัตถุที่สร้างขึ้นใหม่(เฉพาะค่าจะเปลี่ยนชื่อคุณสมบัติouter
จะไม่)
state = { outer: { inner: { innerMost: 'initial value' } } }
ตอนนี้คิดว่าเรามีสถานะลึกมากขึ้นเช่น เราสามารถลองสร้างnewOuter
วัตถุและเติมด้วยouter
เนื้อหาจากสถานะ แต่Object.assign
จะไม่สามารถคัดลอกinnerMost
ค่าของnewOuter
วัตถุที่สร้างขึ้นใหม่นี้ตั้งแต่innerMost
ซ้อนกันลึกเกินไป
คุณยังคงสามารถคัดลอกinner
ได้เช่นในตัวอย่างด้านบน แต่เนื่องจากตอนนี้มันเป็นวัตถุไม่ใช่แบบดั้งเดิมการอ้างอิงจากnewOuter.inner
จะถูกคัดลอกไปที่outer.inner
แทนซึ่งหมายความว่าเราจะจบลงด้วยท้องถิ่นnewOuter
วัตถุเชื่อมโยงกับวัตถุในสถานะโดยตรง .
ซึ่งหมายความว่าในกรณีนี้การกลายพันธุ์ของสิ่งที่สร้างขึ้นในท้องถิ่นnewOuter.inner
จะส่งผลโดยตรงต่อouter.inner
วัตถุ (ในสถานะ) เนื่องจากในความเป็นจริงแล้วพวกมันกลายเป็นสิ่งเดียวกัน (ในหน่วยความจำของคอมพิวเตอร์)
Object.assign
ดังนั้นจะทำงานได้ก็ต่อเมื่อคุณมีโครงสร้างสถานะระดับลึกที่เรียบง่ายในระดับเดียวกับสมาชิกที่อยู่ด้านในสุดที่มีค่าประเภทดั้งเดิม
ถ้าคุณมีวัตถุลึก (ระดับ 2 หรือมากกว่า) Object.assign
ซึ่งคุณควรปรับปรุงไม่ได้ใช้ คุณเสี่ยงต่อการกลายพันธุ์รัฐโดยตรง
2. โคลนของ Lodash ลึก
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นั้นใช้ง่ายกว่ามาก มันทำการโคลนนิ่งที่ลึกดังนั้นจึงเป็นตัวเลือกที่มีประสิทธิภาพหากคุณมีสถานะที่ค่อนข้างซับซ้อนที่มีวัตถุหลายระดับหรืออาร์เรย์ภายใน เพียงแค่cloneDeep()
คุณสมบัติของรัฐระดับบนสุดกลายพันธุ์ส่วนโคลนในสิ่งที่คุณต้องการและsetOuter()
มันกลับไปที่รัฐ
3. การเปลี่ยนไม่ได้ - ผู้ช่วย
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
นำมันไปสู่ระดับใหม่ทั้งหมดและสิ่งที่ยอดเยี่ยมเกี่ยวกับมันคือมันไม่เพียง แต่$set
สามารถให้คุณค่ากับรายการไอเท็ม แต่ยัง$push
รวม$splice
ถึง$merge
(ฯลฯ ) นี่คือรายการคำสั่งที่มีให้
หมายเหตุด้านข้าง
โปรดจำไว้ว่าsetOuter
เพียงปรับเปลี่ยนคุณสมบัติระดับแรกของออบเจ็กต์ของรัฐ ( outer
ในตัวอย่างเหล่านี้) ไม่ใช่การซ้อนแบบเชิงลึก (outer.inner
) หากมันทำงานในลักษณะที่แตกต่างกันคำถามนี้จะไม่มีอยู่จริง
เป็นที่หนึ่งที่เหมาะสมสำหรับโครงการของคุณ?
หากคุณไม่ต้องการหรือไม่สามารถใช้อ้างอิงภายนอกและมีโครงสร้างของรัฐที่เรียบง่าย , Object.assign
ติด
หากคุณจัดการกับรัฐที่มีขนาดใหญ่และ / หรือซับซ้อนซับซ้อน Lodash's cloneDeep
เป็นตัวเลือกที่ชาญฉลาด
หากคุณต้องการความสามารถขั้นสูงเช่นหากโครงสร้างรัฐของคุณซับซ้อนและคุณต้องดำเนินการทุกอย่างกับมันลองimmutability-helper
มันเป็นเครื่องมือขั้นสูงที่สามารถใช้สำหรับการจัดการสถานะ
... หรือคุณต้องการทำสิ่งนี้จริง ๆหรือ?
หากคุณเก็บข้อมูลที่ซับซ้อนในสถานะของ React บางทีนี่อาจเป็นเวลาที่ดีที่คุณจะคิดถึงวิธีการจัดการอื่น ๆ การตั้งค่าสถานะของวัตถุที่ซับซ้อนในส่วนประกอบของ React ไม่ใช่การดำเนินการที่ตรงไปตรงมาและฉันขอแนะนำให้คิดถึงวิธีการที่แตกต่างกัน
เป็นไปได้ว่าคุณควรเก็บข้อมูลที่ซับซ้อนของคุณในร้าน Redux ให้ดีขึ้นควรตั้งค่าไว้ที่นั่นโดยใช้ reducers และ / หรือ sagas