결과 : setState에서 state.item [1]을 어떻게 업데이트합니까? (JSFiddle 사용)
사용자가 자신의 양식을 디자인 할 수있는 앱을 만들고 있습니다. 예를 들어 필드 이름과 포함해야 할 다른 열의 세부 사항을 지정하십시오.
컴포넌트는 여기 에서 JSFiddle로 사용 가능 합니다 .
내 초기 상태는 다음과 같습니다.
var DynamicForm = React.createClass({
getInitialState: function() {
var items = {};
items[1] = { name: 'field 1', populate_at: 'web_start',
same_as: 'customer_name',
autocomplete_from: 'customer_name', title: '' };
items[2] = { name: 'field 2', populate_at: 'web_end',
same_as: 'user_name',
autocomplete_from: 'user_name', title: '' };
return { items };
},
render: function() {
var _this = this;
return (
<div>
{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
populate_at={data.populate_at} />
</div>
);
}, this)}
<button onClick={this.newFieldEntry}>Create a new field</button>
<button onClick={this.saveAndContinue}>Save and Continue</button>
</div>
);
}
사용자가 값을 변경할 때 상태를 업데이트하고 싶지만 올바른 객체를 타겟팅하기가 어렵습니다.
var PopulateAtCheckboxes = React.createClass({
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
},
render: function() {
var populateAtCheckbox = this.props.populate_at.map(function(value) {
return (
<label for={value}>
<input type="radio" name={'populate_at'+this.props.id} value={value}
onChange={this.handleChange} checked={this.props.checked == value}
ref="populate-at"/>
{value}
</label>
);
}, this);
return (
<div className="populate-at-checkboxes">
{populateAtCheckbox}
</div>
);
}
});
this.setState
업데이트하려면 어떻게해야 items[1].name
합니까?
update
불변 도우미를 사용할 수 있습니다 .
this.setState({
items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})
또는을 shouldComponentUpdate()
사용하여 수명주기 방법 에서이 항목의 변경 사항을 감지하지 못하는 경우 ===
상태를 직접 편집하고 구성 요소를 다시 렌더링하도록 할 수 있습니다. 이는 @limelights의 답변과 동일합니다. 객체를 상태에서 벗어나 편집합니다.
this.state.items[1].name = 'updated field name'
this.forceUpdate()
편집 후 추가 :
콜백 함수를 상태 유지 상위에서 상태 변경을 트리거해야하는 하위 컴포넌트로 전달하는 방법에 대한 예제는 반응 훈련 에서 단순 컴포넌트 통신 학습을 확인하십시오 .
잘못된 방법!
handleChange = (e) => {
const { items } = this.state;
items[1].name = e.target.value;
// update state
this.setState({
items,
});
};
의견에서 많은 우수한 개발자가 지적한 것처럼 상태를 변경하는 것은 잘못되었습니다!
이것을 알아내는 데 시간이 걸렸습니다. 위는 작동하지만 React의 힘을 빼앗습니다. 예를 들어 componentDidUpdate
직접 수정 되었기 때문에이 업데이트를 업데이트로 볼 수 없습니다.
그래서에게 올바른 방법은 다음과 같습니다
handleChange = (e) => {
this.setState(prevState => ({
items: {
...prevState.items,
[prevState.items[1].name]: e.target.value,
},
}));
};
이 스레드에는 많은 잘못된 정보가 있으므로 도우미 라이브러리없이 수행 할 수있는 방법은 다음과 같습니다.
handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you're intested in
item.name = 'newName';
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},
원하는 경우 2 단계와 3 단계를 결합 할 수 있습니다.
let item = {
...items[1],
name: 'newName'
}
또는 한 줄로 모든 것을 할 수 있습니다.
this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: 'newName',
},
...items.slice(2)
]
}));
참고 : items
배열을 만들었습니다 . OP는 개체를 사용했습니다. 그러나 개념은 동일합니다.
터미널 / 콘솔에서 진행중인 작업을 확인할 수 있습니다.
❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
(가)의 상태 반응에 깊이 중첩 된 객체 / 변수를 수정하려면, 일반적으로 세 가지 방법이 사용된다 : 바닐라 JS Object.assign
, 불변성 헬퍼 와 cloneDeep
에서 Lodash은 . 이것을 달성하기 위해 덜 인기있는 다른 타사 라이브러리도 많이 있지만이 답변에서는이 세 가지 옵션 만 다룰 것입니다. 또한 배열 확산과 같은 바닐라 JavaScript 이외의 다른 방법을 사용하는 방법이 있지만 (예 : @mpen의 답변 참조) 직관적이지 않고 사용하기 쉽고 모든 상태 조작 상황을 처리 할 수 없습니다.
답변에 대한 최고 투표 의견에서 셀 수없이 많은 시간이 지적 되었 듯이, 저자는 직접적인 국가 변이를 제안하지만, 그렇게하지 마십시오 . 이것은 유비쿼터스 리 액트 안티 패턴으로 필연적으로 원치 않는 결과를 초래합니다. 올바른 방법을 배우십시오.
널리 사용되는 세 가지 방법을 비교해 봅시다.
참고 :이 예제는 클래스 구성 요소 및 수명주기와 함께 이전 방식을 사용하여 상태를 업데이트합니다. 최신 후크 API에는 다른 구문이 있지만 아이디어는 여전히 동일합니다. 이는이 모든 예제가 여전히 유효 함을 의미합니다.
이 상태 구조는 다음과 같습니다.
state = {
foo: {
bar: 'initial value'
}
}
1. 바닐라 JavaScript의 Object.assign
(...essential imports)
class App extends Component {
state = {
foo: {
bar: 'initial value'
}
}
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = Object.assign({}, this.state.foo, { bar: 'further value' })
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
})
}
(...rest of code)
것을 명심 Object.assign이 깊은 복제를 수행하지 않습니다 , 이후 그것은 단지 복사 속성 값 과의 그 이유는 그것이라고 무엇을 복사 얕은을 (주석 참조).
이것이 작동하려면이 객체의 최상위 항목 ( state.foo
) 만 조작해야합니다 . 그리고 그 값 ( state.foo.bar
)은 기본 (문자열, 숫자, 부울)이어야합니다.
이 예제에서, 우리는 새로운 상수 (만들 const foo...
사용) Object.assign
(빈 객체를 생성하는 {}
), 복사 state.foo
(객체 { bar: 'initial value' }
그것으로) 한 후 사본을 다른 객체 { bar: 'further value' }
위에. 결국 새로 생성 된 foo
상수는 속성이 재정 의 된 { bar: 'further value' }
이후의 값을 유지합니다 bar
. 이것은 foo
상태 객체에 연결되지 않은 새로운 객체이므로 필요에 따라 변경 될 수 있으며 상태는 변경되지 않습니다.
마지막 부분은 setState()
setter 를 사용 state.foo
하여 상태 의 원본 을 새로 만든 foo
객체 로 바꾸는 것 입니다.
이제 우리가보다 깊은 상태를 가지고 있다고 상상해보십시오 state = { foo: { bar: { baz: 'initial value' } } }
. 새 foo
객체 를 만들어 foo
상태 의 내용으로 채울 수는 있지만 너무 깊게 중첩되어 있으므로 새로 만든 객체에 값을 Object.assign
복사 할 수 없습니다 . 위의 예와 같이 여전히 복사 할 수는 있지만 프리미티브가 아닌 객체이므로 참조 가 대신 복사 되므로 상태에 직접 연결된 로컬 객체로 끝납니다 . 즉,이 경우 로컬에서 생성 된 모든 돌연변이가 실제로 동일한 것을 가리 키기 때문에 객체에 영향을 미칩니다 .baz
foo
baz
bar
state.foo.bar
foo
foo
state.foo
Object.assign
따라서 가장 기본적인 멤버가 기본 유형의 값을 보유하는 비교적 단순한 1 단계 딥 스테이트 구조 인 경우에만 작동합니다.
업데이트해야하는 더 깊은 개체 (2 단계 이상)가있는 경우을 사용하지 마십시오 Object.assign
. 상태를 직접 변경할 위험이 있습니다.
2. Lodash의 cloneDeep
(...essential imports)
import cloneDeep from 'lodash.clonedeep'
class App extends Component {
state = {
foo: {
bar: 'initial value'
}
}
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = cloneDeep(this.state.foo)
foo.bar = 'further value'
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
})
}
(...rest of code)
Lodash의 cloneDeep 은 사용하기가 더 간단합니다. 딥 클로닝을 수행 하므로 다중 레벨 객체 또는 배열이 상당히 복잡한 상태 인 경우 강력한 옵션입니다. cloneDeep()
최상위 상태 속성 만으로 원하는 부분을 복제 한 setState()
후 상태로 되돌릴 수 있습니다.
3. 불변 헬퍼
(...essential imports)
import update from 'immutability-helper'
class App extends Component {
state = {
foo: {
bar: 'initial value'
}
};
componentDidMount() {
console.log(this.state.foo.bar) // initial value
const foo = update(this.state.foo, { bar: { $set: 'further value' } })
console.log(this.state.foo.bar) // initial value
this.setState({ foo }, () => {
console.log(this.state.foo.bar) // further value
});
}
(...rest of code)
immutability-helper
takes it to the whole new level, and the cool thing about it is that it can not only $set
values to state items, but also $push
, $splice
, $merge
(etc.) them. Here is a list of commands available.
Side notes
Again, keep in mind, that this.setState()
only modifies the first-level properties of the state object (foo
property in this case), not the deeply nested (foo.bar
). If it behaved another way, this question wouldn't exist.
And by the way, this.setState({ foo })
is just a shorthand for this.setState({ foo: foo })
. And () => { console.log(this.state.foo.bar) }
after the { foo }
is a callback which gets executed immediately after setState
have set the state. Convenient, if you need to do some things after it did its job (in our case to display the state immediately after it was set).
프로젝트에 적합한 것은 무엇입니까?
외부 의존성을 원하지 않거나 사용할 수없고 간단한 상태 구조를 가지고 있다면을 고수하십시오 Object.assign
.
거대하고 복잡한 상태 를 조작하는 경우 Lodash cloneDeep
가 현명한 선택입니다.
고급 기능 이 필요한 경우 , 즉 상태 구조가 복잡하고 모든 종류의 작업을 수행해야하는 immutability-helper
경우 상태 조작에 사용할 수있는 매우 고급 도구입니다.
나는 같은 문제가 있었다. 작동하는 간단한 해결책이 있습니다!
const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
먼저 원하는 항목을 가져 와서 해당 개체에서 원하는 것을 변경 한 다음 상태로 다시 설정하십시오. getInitialState
키가있는 객체를 사용하면 객체 만 전달하여 상태를 사용하는 방법이 훨씬 쉬워집니다.
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
}
setState 의 React 문서에 따르면 Object.assign
다른 답변에서 제안한대로 사용 하는 것이 이상적이지 않습니다. setState
의 비동기 동작 특성으로 인해이 기술을 사용한 후속 호출은 이전 호출을 무시하여 원하지 않는 결과를 초래할 수 있습니다.
대신 React 문서 setState
는 이전 상태에서 작동 하는 업데이터 양식을 사용하는 것이 좋습니다 . React가 상태 불변성을 보존해야하기 때문에 배열이나 객체 를 업데이트 할 때 새로운 배열이나 객체 를 반환해야합니다 . ES6 구문의 spread 연산자를 사용하여 배열을 얕게 복사하고 주어진 배열 인덱스에서 객체의 속성을 만들거나 업데이트하면 다음과 같습니다.
this.setState(prevState => {
const newItems = [...prevState.items];
newItems[index].name = newName;
return {items: newItems};
})
상태를 변경하지 마십시오. 예기치 않은 결과가 발생할 수 있습니다. 나는 나의 교훈을 배웠다! 항상 복사 / 복제 작업을 수행 Object.assign()
하는 것이 좋습니다.
item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
정말 간단합니다.
First pull the entire items object from state, updated the part of the items object as desired, and put the entire items object back in state via setState.
handleChange: function (e) {
items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
items[1].name = 'newName'; // update the items object as needed
this.setState({ items }); // Put back in state
}
Mutation free:
// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}
// This will work without mutation as it clones the modified item in the map:
this.state.items
.map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)
this.setState(newItems)
Use the event on handleChange
to figure out the element that has changed and then update it. For that you might need to change some property to identify it and update it.
See fiddle https://jsfiddle.net/69z2wepo/6164/
I would move the function handle change and add an index parameter
handleChange: function (index) {
var items = this.state.items;
items[index].name = 'newName';
this.setState({items: items});
},
to the Dynamic form component and pass it to the PopulateAtCheckboxes component as a prop. As you loop over your items you can include an additional counter (called index in the code below) to be passed along to the handle change as shown below
{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
handleChange={boundHandleChange}
populate_at={data.populate_at} />
</div>
);
}, this)}
Finally you can call your change listener as shown below here
<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
If you need to change only part of the Array
, You've a react component with state set to.
state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}
It's best to update the red-one
in the Array
as follows:
const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
this.state.items.slice(0, itemIndex),
{name: 'red-one', value: 666},
this.state.items.slice(itemIndex)
]
this.setState(newItems)
As none of the above options was ideal to me I ended up using map:
this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
or if you have a dynamically generated list and you don't know the index but just have the key or id:
let ItemsCopy = []
let x = this.state.Items.map((entry) =>{
if(entry.id == 'theIDYoureLookingFor')
{
entry.PropertyToChange = 'NewProperty'
}
ItemsCopy.push(entry)
})
this.setState({Items:ItemsCopy});
Try with code:
this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);
this.setState({items: cloneObj });
Following piece of code went easy on my dull brain. Removing the object and replacing with the updated one
var udpateditem = this.state.items.find(function(item) {
return item.name == "field_1" });
udpateditem.name= "New updated name"
this.setState(prevState => ({
items:prevState.dl_name_template.filter(function(item) {
return item.name !== "field_1"}).concat(udpateditem)
}));
How about creating another component(for object that needs to go into the array) and pass the following as props?
- component index - index will be used to create/update in array.
- set function - This function put data into the array based on the component index.
<SubObjectForm setData={this.setSubObjectData} objectIndex={index}/>
Here {index} can be passed in based on position where this SubObjectForm is used.
and setSubObjectData can be something like this.
setSubObjectData: function(index, data){
var arrayFromParentObject= <retrieve from props or state>;
var objectInArray= arrayFromParentObject.array[index];
arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
}
In SubObjectForm, this.props.setData can be called on data change as given below.
<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
Try this it will definetly work,other case i tried but didn't work
import _ from 'lodash';
this.state.var_name = _.assign(this.state.var_name, {
obj_prop: 'changed_value',
});
'Programing' 카테고리의 다른 글
Windows에서 코드 서명을 위해 자체 서명 된 인증서를 작성하는 방법 (0) | 2020.05.05 |
---|---|
CFNetwork SSLHandshake가 iOS 9에 실패했습니다 (0) | 2020.05.05 |
문자열 유형의 기본값이 빈 문자열 대신 널인 이유는 무엇입니까? (0) | 2020.05.05 |
mysql 사용자가 user / local / mysql / data 디렉토리를 소유하고 있지 않다는 경고 (0) | 2020.05.05 |
각 측면에 2 개의 y 축과 다른 스케일이있는 ggplot (0) | 2020.05.05 |