바닐라 자바 스크립트의 : 일반적으로 세 가지 방법이 사용의 상태, 반작용에 깊이 중첩 된 객체 / 변수를 수정하려면 Object.assign
, 불변성-도우미 와 cloneDeep
의 Lodash을 .
이것을 달성하기 위해 덜 인기있는 다른 타사 라이브러리도 많이 있지만이 답변에서는이 세 가지 옵션 만 다룰 것입니다. 또한 배열 확산과 같은 일부 추가 바닐라 JavaScript 메소드가 존재하지만 (예 : @mpen의 답변 참조) 직관적이지 않고 사용하기 쉽고 모든 상태 조작 상황을 처리 할 수 없습니다.
답변에 대한 최고 투표 의견에서 셀 수없이 많은 시간이 지적 되었 듯이, 저자는 국가의 직접적인 돌연변이를 제안 합니다 . 이것은 유비쿼터스 리 액트 안티 패턴으로 필연적으로 원치 않는 결과를 초래합니다. 올바른 방법을 배우십시오.
널리 사용되는 세 가지 방법을 비교해 봅시다.
이 상태 객체 구조가 주어지면 :
state = {
outer: {
inner: 'initial value'
}
}
다음 방법을 사용하여 inner
나머지 상태에 영향을주지 않으면 서 가장 안쪽 필드의 값 을 업데이트 할 수 있습니다 .
1. 바닐라 JavaScript의 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>
것을 명심 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의 cloneDeep
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 은 사용하기가 더 간단합니다. 딥 클로닝을 수행 하므로 다중 레벨 객체 또는 배열이 상당히 복잡한 상태 인 경우 강력한 옵션입니다. 그냥 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 cloneDeep
가 현명한 선택입니다.
고급 기능 이 필요한 경우 , 즉 상태 구조가 복잡하고 모든 종류의 작업을 수행해야하는 immutability-helper
경우 상태 조작에 사용할 수있는 매우 고급 도구입니다.
... 또는 정말로 이것을해야합니까?
복잡한 데이터를 React의 상태로 보유하고 있다면 다른 방법으로 처리하는 것이 좋습니다. React 컴포넌트에서 복잡한 상태 객체를 바로 설정하는 것은 간단한 작업이 아니며 다른 접근 방식에 대해 강력히 제안하는 것이 좋습니다.
복잡한 데이터를 Redux 스토어에 보관하지 않고 리듀서 및 / 또는 sagas를 사용하여 설정하고 선택기를 사용하여 액세스하는 것이 좋습니다.