다음과 같이 중첩 속성을 사용하여 상태를 구성하려고합니다.
this.state = {
someProperty: {
flag:true
}
}
하지만 이런 상태를 업데이트하면
this.setState({ someProperty.flag: false });
작동하지 않습니다. 어떻게 이것을 올바르게 할 수 있습니까?
다음과 같이 중첩 속성을 사용하여 상태를 구성하려고합니다.
this.state = {
someProperty: {
flag:true
}
}
하지만 이런 상태를 업데이트하면
this.setState({ someProperty.flag: false });
작동하지 않습니다. 어떻게 이것을 올바르게 할 수 있습니까?
답변:
하기 위해 setState
내가 setState를 중첩 업데이트를 처리하지 않습니다 생각하는 중첩 된 객체에 대해 당신이 접근 아래에 따를 수 있습니다.
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
아이디어는 더미 객체를 생성하여 작업을 수행 한 다음 구성 요소의 상태를 업데이트 된 객체로 교체하는 것입니다.
이제 스프레드 연산자는 객체의 하나의 레벨 중첩 복사본 만 만듭니다. 상태가 다음과 같이 고도로 중첩 된 경우 :
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
각 수준에서 스프레드 연산자를 사용하여 state를 설정할 수 있습니다.
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
그러나 위의 구문은 상태가 점점 더 중첩 될 때마다 추악합니다. 따라서 사용하는 것이 좋습니다 immutability-helper
패키지를 사용하여 상태를 업데이트하는 .
로 상태를 업데이트하는 방법에 대한이 답변을 참조하십시오 immutability helper
.
someProperty
우리는 그 수정하는
this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}})
. 조금 지루하지만 ...
...prevState,
마지막 예에서 당신 someProperty
과 그 자녀들 만 필요하다고 생각하지 않습니다
한 줄에 쓰려면
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
this.state
바로 읽지 말고 setState
새로운 가치를 기대하십시오. this.state
내에서 호출하는 것과 비교합니다 setState
. 아니면 나에게 분명하지 않은 후자에도 문제가 있습니까?
this.state
후를 setState
. 게다가, 우리는 전달되지 않습니다 this.state
에 setState
. …
운영자 확산 특성. Object.assign ()과 동일합니다. github.com/tc39/proposal-object-rest-spread
this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });
문제를 피할 수 있습니까?
때로는 직접 답변이 가장 좋은 답변이 아닙니다. :)
짧은 버전 :
이 코드
this.state = {
someProperty: {
flag: true
}
}
다음과 같이 단순화해야합니다
this.state = {
somePropertyFlag: true
}
긴 버전 :
현재 React에서 중첩 상태로 작업하고 싶지 않아야합니다. . React는 중첩 된 상태에서 작동하도록 설계되지 않았으므로 여기에서 제안 된 모든 솔루션은 해킹으로 보입니다. 그들은 프레임 워크를 사용하지 않고 싸워야합니다. 일부 속성을 그룹화하려는 의심스러운 목적을 위해 명확한 코드를 작성하지 않는 것이 좋습니다. 따라서 그들은 도전에 대한 답변으로 매우 흥미롭지 만 실제로는 쓸모가 없습니다.
다음과 같은 상태를 상상해 봅시다.
{
parent: {
child1: 'value 1',
child2: 'value 2',
...
child100: 'value 100'
}
}
값을 변경하면 어떻게 child1
됩니까? React는 얕은 비교를 사용하기 때문에 뷰를 다시 렌더링하지 않습니다.parent
하지 않으며 속성이 변경되지 않았 음을 알 수 있습니다. 상태 객체를 직접 변경하는 BTW는 일반적으로 나쁜 습관으로 간주됩니다.
그래서 당신은 전체를 다시 만들어야합니다 parent
개체 합니다. 그러나이 경우 우리는 다른 문제를 만날 것입니다. React는 모든 어린이가 자신의 가치를 바꾸고 다시 렌더링 할 것이라고 생각합니다. 물론 성능에는 좋지 않습니다.
복잡한 논리를 작성 하여이 문제를 해결할 수는 shouldComponentUpdate()
있지만 여기서 멈추고 짧은 버전의 간단한 솔루션을 사용하는 것이 좋습니다.
React의 중첩 상태가 잘못 설계되었습니다.
이 훌륭한 답변을 읽으십시오 .
이 답변 뒤에 추론 :
React의 setState는 기본 제공 편의 기능이지만 제한이 있음을 곧 깨닫게됩니다. 사용자 정의 속성을 사용하고 forceUpdate를 지능적으로 사용하면 훨씬 더 많은 것을 얻을 수 있습니다. 예 :
class MyClass extends React.Component { myState = someObject inputValue = 42 ...
예를 들어 MobX는 상태를 완전히 도랑 화하고 사용자 지정 관찰 가능 속성을 사용합니다.
React 컴포넌트에서 상태 대신 Observables를 사용하십시오.
중첩 속성을 업데이트하는 또 다른 짧은 방법이 있습니다.
this.setState(state => {
state.nested.flag = false
state.another.deep.prop = true
return state
})
this.setState(state => (state.nested.flag = false, state))
참고 :이 여기 쉼표 연산자 ~ MDN은 행동에서 볼 여기 (샌드 박스) .
상태 참조를 변경하지는 않지만 비슷합니다.
this.state.nested.flag = false
this.forceUpdate()
이 컨텍스트 사이의 미묘한 차이 forceUpdate
및setState
연결된 예를 참조.
물론 이것은 state
읽기 전용이어야하기 때문에 몇 가지 핵심 원칙을 남용하고 있지만 이전 상태를 즉시 버리고 새로운 상태로 교체하기 때문에 완전히 괜찮습니다.
상태 를 포함하는 구성 요소가 올바르게 업데이트되고 다시 렌더링 되지만 ( 이 gotcha 제외 ) 소품이 하위 항목으로 전파 되지 않습니다 (아래 Spymaster의 설명 참조). . 수행중인 작업을 알고있는 경우에만이 기술을 사용하십시오.
예를 들어, 업데이트되어 쉽게 전달 된 변경된 플랫 소품을 전달할 수 있습니다.
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
complexNestedProp에 대한 참조가 변경되지 않았더라도 ( shouldComponentUpdate )
this.props.complexNestedProp === nextProps.complexNestedProp
구성 요소가 됩니다 호출 한 후의 경우 부모 구성 요소를 업데이트 할 때마다 다시 쓰게 this.setState
또는 this.forceUpdate
부모에 있습니다.
사용하여 중첩 된 상태를 다른 개체가 (고의 또는하지) 보유하고있는 서로 다른 (이상) 참조 수 있기 때문에 직접 상태를 돌연변이 것은 위험 상태 때 갱신을 반드시 알고하지 않을 수 있습니다 (예를 들어, 사용하는 경우 PureComponent
또는 경우 shouldComponentUpdate
반환에 구현 false
) 또는 이다 아래 예와 같이 이전 데이터를 표시하기위한 것입니다.
과거 데이터를 렌더링해야하는 타임 라인을 상상해보십시오. 데이터를 변경하면 이전 항목도 변경되므로 예기치 않은 동작이 발생합니다.
어쨌든 여기 Nested PureChildClass
에서 소품이 전파되지 않아서 다시 렌더링되지 않았다는 것을 알 수 있습니다 .
state
는 완전히 분리되고 사용자 지정 관찰 가능 속성을 사용 합니다. 리 액트 setState
는 편의상 내장되어 있지만 곧 한계가 있음을 알게됩니다. 사용자 지정 속성을 사용하고 지능적으로 사용 forceUpdate
하면 더 많은 것을 얻을 수 있습니다. React 컴포넌트에서 상태 대신 Observables를 사용하십시오.
this.forceUpdate()
specific을 쉽게 트리거 하거나 구현할 수 있습니다 shouldComponentUpdate
.
ES2015를 사용하는 경우 Object.assign에 액세스 할 수 있습니다. 다음과 같이 사용하여 중첩 된 객체를 업데이트 할 수 있습니다.
this.setState({
someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});
업데이트 된 속성을 기존 속성과 병합하고 반환 된 개체를 사용하여 상태를 업데이트합니다.
편집 : carkod가 지적한 것처럼 상태가 직접 변경되지 않도록 할당 함수에 대상으로 빈 객체를 추가했습니다.
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
property
에 여러 개의 중첩 속성 이 포함되어 있으면 첫 번째 속성이 삭제되고 두 번째 속성은 삭제되지 않습니다. codesandbox.io/s/nameless-pine-42thu
이를 돕기 위해 많은 라이브러리가 있습니다. 예를 들어, 불변성 헬퍼 사용 :
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
사용 lodash / FP 세트를 :
import {set} from 'lodash/fp';
const newState = set(["someProperty", "flag"], false, this.state);
사용 lodash / FP 병합 :
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
lodash
하려고 _.cloneDeep
할 수 있습니다.
lodash/fp
했으므로 돌연변이에 대해 걱정할 필요가 없습니다.
merge
또는 set
기능에 어떤 이점이 있습니까?
this.setState(newState)
은 lodash / fp 메소드도 호출해야한다는 점 입니다. 또 다른 문제는 lodash .set
(fp가 아닌)가 다른 순서의 인수를 사용하여 잠시 동안 나를 트립한다는 것입니다.
우리는 이러한 종류의 문제를 처리하기 위해 Immer https://github.com/mweststrate/immer 를 사용 합니다.
구성 요소 중 하나에서이 코드를 교체했습니다.
this.setState(prevState => ({
...prevState,
preferences: {
...prevState.preferences,
[key]: newValue
}
}));
이것으로
import produce from 'immer';
this.setState(produce(draft => {
draft.preferences[key] = newValue;
}));
immer를 사용하면 상태를 "일반 객체"로 처리합니다. 마술은 프록시 객체로 무대 뒤에서 발생합니다.
다음은 추가 스레드, 라이브러리 또는 특수 함수가 필요하지 않은이 스레드에서 제공된 첫 번째 답변에 대한 변형입니다.
state = {
someProperty: {
flag: 'string'
}
}
handleChange = (value) => {
const newState = {...this.state.someProperty, flag: value}
this.setState({ someProperty: newState })
}
특정 중첩 필드의 상태를 설정하기 위해 전체 개체를 설정했습니다. 변수를 만들고 ES2015 스프레드 연산자를 사용하여 newState
현재 상태의 내용을 먼저 펼치면 됩니다. 그런 다음 값을 새 값으로 바꿨습니다 (현재 상태를 객체에 펼친 후에 설정했기 때문에 현재 상태의 필드가 무시됩니다). 그럼, 간단히의 상태를 설정 내에 객체입니다.this.state.flag
flag: value
flag
someProperty
newState
중첩은 실제로 구성 요소 상태를 처리하는 방법이 아니지만 단일 계층 중첩을 위해 쉬운 경우가 있습니다.
이런 상태에
state = {
contact: {
phone: '888-888-8888',
email: 'test@test.com'
}
address: {
street:''
},
occupation: {
}
}
재사용 가능한 방법 ive는 다음과 같습니다.
handleChange = (obj) => e => {
let x = this.state[obj];
x[e.target.name] = e.target.value;
this.setState({ [obj]: x });
};
그런 다음 주소를 지정하려는 각 중첩에 대해 obj 이름을 전달하십시오.
<TextField
name="street"
onChange={handleChange('address')}
/>
이 솔루션을 사용했습니다.
다음과 같이 중첩 된 상태 인 경우 :
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
현재 상태를 복사하고 변경된 값으로 재 할당하는 handleChange 함수를 선언 할 수 있습니다.
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
이벤트 리스너가있는 html
<input type="text" onChange={this.handleChange} " name="friendName" />
상태의 사본을 작성하십시오.
let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
이 객체를 변경하십시오.
someProperty.flag = "false"
이제 상태를 업데이트하십시오
this.setState({someProperty})
클래스 기반 React 구성 요소의 상태에 대해 질문했지만 useState 후크와 동일한 문제점이 있습니다. 더 나쁜 것은 : useState 후크는 부분 업데이트를 허용하지 않습니다. 따라서이 질문은 useState 후크가 도입되었을 때 매우 관련이되었습니다.
useState 후크가 사용되는보다 현대적인 시나리오에 문제가 있는지 확인하기 위해 다음 답변을 게시하기로 결정했습니다.
당신이 가지고 있다면 :
const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })
현재를 복제하고 필요한 데이터 세그먼트를 패치하여 중첩 속성을 설정할 수 있습니다. 예를 들면 다음과 같습니다.
setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });
또는 Immer 라이브러리를 사용하여 객체의 복제 및 패치를 단순화 할 수 있습니다.
또는 Hookstate 라이브러리 (면책 조항 : 저자입니다)를 사용하여 복잡한 (로컬 및 글로벌) 상태 데이터를 간단히 관리하고 성능을 향상시킬 수 있습니다 (읽기 : 렌더링 최적화에 대해 걱정하지 마십시오).
import { useStateLink } from '@hookstate/core'
const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })
렌더링 할 필드를 가져옵니다.
state.nested.someProperty.nested.flag.get()
// or
state.get().someProperty.flag
중첩 필드를 설정하십시오.
state.nested.someProperty.nested.flag.set(false)
다음은 상태가 트리와 같은 데이터 구조 에 깊게 / 재귀 적으로 중첩되어있는 Hookstate 예제 입니다.
아직 언급되지 않은 두 가지 옵션 :
일반적인 사항을 만들기 위해 @ShubhamKhatri와 @Qwerty의 답변을 연구했습니다.
상태 객체
this.state = {
name: '',
grandParent: {
parent1: {
child: ''
},
parent2: {
child: ''
}
}
};
입력 컨트롤
<input
value={this.state.name}
onChange={this.updateState}
type="text"
name="name"
/>
<input
value={this.state.grandParent.parent1.child}
onChange={this.updateState}
type="text"
name="grandParent.parent1.child"
/>
<input
value={this.state.grandParent.parent2.child}
onChange={this.updateState}
type="text"
name="grandParent.parent2.child"
/>
updateState 메소드
@State of @ShubhamKhatri의 답변으로 setState
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const oldstate = this.state;
const newstate = { ...oldstate };
let newStateLevel = newstate;
let oldStateLevel = oldstate;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
newStateLevel[path[i]] = event.target.value;
} else {
newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
oldStateLevel = oldStateLevel[path[i]];
newStateLevel = newStateLevel[path[i]];
}
}
this.setState(newstate);
}
@Statewer의 답변으로 setState
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const state = { ...this.state };
let ref = state;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
ref[path[i]] = event.target.value;
} else {
ref = ref[path[i]];
}
}
this.setState(state);
}
참고 : 위의 방법은 배열에서 작동하지 않습니다
구성 요소 상태의 전체 복사본을 만드는 데 대해 이미 우려한 점이 매우 심각합니다 . 그 말로, 나는 Immer를 강력하게 제안 할 것 입니다.
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
프록시 객체를 영리하게 사용하여 임의로 깊은 상태 트리를 효율적으로 복사하기 때문에 React.PureComponent
(즉 React의 얕은 상태 비교) 작동합니다 Immer
. Immer는 또한 Immutability Helper와 같은 라이브러리에 비해 형식이 안전하며 Javascript 및 Typescript 사용자 모두에게 이상적입니다.
타이프 스크립트 유틸리티 기능
function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s:
Draft<Readonly<S>>) => any) {
comp.setState(produce(comp.state, s => { fn(s); }))
}
onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
stateUpdate = () => {
let obj = this.state;
if(this.props.v12_data.values.email) {
obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
}
this.setState(obj)
}
obj
상태에 대한 참조 일 뿐이 므로 이것이 옳지 않다고 생각합니다 . 그래서 그것을 통해 당신은 실제 this.state
객체를 변경하고 있습니다
예를 들어 ID가 있고 이름이 있고 중첩 된 프로젝트의 상태를 유지하려는 경우 프로젝트 양식을 사용 하여이 기능이 작동한다는 것을 알았습니다.
return (
<div>
<h2>Project Details</h2>
<form>
<Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
<Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
</form>
</div>
)
알려주세요!
이런 식으로 충분할 수도 있습니다.
const isObject = (thing) => {
if(thing &&
typeof thing === 'object' &&
typeof thing !== null
&& !(Array.isArray(thing))
){
return true;
}
return false;
}
/*
Call with an array containing the path to the property you want to access
And the current component/redux state.
For example if we want to update `hello` within the following obj
const obj = {
somePrimitive:false,
someNestedObj:{
hello:1
}
}
we would do :
//clone the object
const cloned = clone(['someNestedObj','hello'],obj)
//Set the new value
cloned.someNestedObj.hello = 5;
*/
const clone = (arr, state) => {
let clonedObj = {...state}
const originalObj = clonedObj;
arr.forEach(property => {
if(!(property in clonedObj)){
throw new Error('State missing property')
}
if(isObject(clonedObj[property])){
clonedObj[property] = {...originalObj[property]};
clonedObj = clonedObj[property];
}
})
return originalObj;
}
const nestedObj = {
someProperty:true,
someNestedObj:{
someOtherProperty:true
}
}
const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true
console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty)
나는 그것이 오래된 질문이라는 것을 알고 있지만 여전히 이것을 달성 한 방법을 공유하고 싶었습니다. 생성자의 상태를 다음과 같이 가정하십시오.
constructor(props) {
super(props);
this.state = {
loading: false,
user: {
email: ""
},
organization: {
name: ""
}
};
this.handleChange = this.handleChange.bind(this);
}
내 handleChange
기능은 다음과 같습니다
handleChange(e) {
const names = e.target.name.split(".");
const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
this.setState((state) => {
state[names[0]][names[1]] = value;
return {[names[0]]: state[names[0]]};
});
}
입력 이름을 적절하게 지정하십시오.
<input
type="text"
name="user.email"
onChange={this.handleChange}
value={this.state.user.firstName}
placeholder="Email Address"
/>
<input
type="text"
name="organization.name"
onChange={this.handleChange}
value={this.state.organization.name}
placeholder="Organization Name"
/>
축소 검색으로 중첩 업데이트를 수행합니다 .
예:
상태에있는 중첩 변수 :
state = {
coords: {
x: 0,
y: 0,
z: 0
}
}
함수:
handleChange = nestedAttr => event => {
const { target: { value } } = event;
const attrs = nestedAttr.split('.');
let stateVar = this.state[attrs[0]];
if(attrs.length>1)
attrs.reduce((a,b,index,arr)=>{
if(index==arr.length-1)
a[b] = value;
else if(a[b]!=null)
return a[b]
else
return a;
},stateVar);
else
stateVar = value;
this.setState({[attrs[0]]: stateVar})
}
사용하다:
<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
이것은 내 초기 상태입니다
const initialStateInput = {
cabeceraFamilia: {
familia: '',
direccion: '',
telefonos: '',
email: ''
},
motivoConsulta: '',
fechaHora: '',
corresponsables: [],
}
후크 또는 상태 (클래스 구성 요소)로 바꿀 수 있습니다
const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);
handleChange의 메소드
const actualizarState = e => {
const nameObjects = e.target.name.split('.');
const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
setInfoAgendamiento({...newState});
};
중첩 된 상태로 상태를 설정하는 방법
const setStateNested = (state, nameObjects, value) => {
let i = 0;
let operativeState = state;
if(nameObjects.length > 1){
for (i = 0; i < nameObjects.length - 1; i++) {
operativeState = operativeState[nameObjects[i]];
}
}
operativeState[nameObjects[i]] = value;
return state;
}
마지막으로 이것은 내가 사용하는 입력입니다.
<input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />
프로젝트에서 formik를 사용하는 경우이 물건을 처리하는 쉬운 방법이 있습니다. 다음은 formik를 사용하는 가장 쉬운 방법입니다.
먼저 formik initivalues 속성 또는 반응에서 초기 값을 설정하십시오. 상태
여기서 초기 값은 반응 상태로 정의됩니다.
state = {
data: {
fy: {
active: "N"
}
}
}
formik initiValues
속성 내에서 formik 필드의 초기 값을 정의하십시오.
<Formik
initialValues={this.state.data}
onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
<Form>
<Field type="checkbox" name="fy.active" onChange={(e) => {
const value = e.target.checked;
if(value) setFieldValue('fy.active', 'Y')
else setFieldValue('fy.active', 'N')
}}/>
</Form>
)}
</Formik>
formik 함수 string
대신 업데이트 된 상태를 확인하기 위해 콘솔을 상태 를 설정하거나 반응 디버거 도구를 사용하여 formik 상태 값의 변경 사항을 확인하십시오.boolean
setFieldValue