このようにネストしたプロパティを使って自分の状態を整理しようとしています。
this.state = {
someProperty: {
flag:true
}
}
しかし、このように状態を更新すると、
this.setState({ someProperty.flag: false });
うまくいきません。どうすればこれを正しくできますか?
入れ子になったオブジェクトに対してsetState
を実行するには、setStateは入れ子になった更新を処理しないと思うので、以下のアプローチに従うことができます。
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
これは、ダミーオブジェクトを作成してダミーオブジェクトを操作し、コンポーネントの状態を更新されたオブジェクトに置き換えることです。
現在、スプレッド演算子は、オブジェクトの1レベルのネストされたコピーのみを作成します。あなたの状態が次のように高度にネストされているとします。
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
次のように各レベルでスプレッド演算子を使用してsetStateを設定できます。
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
しかし、上記の構文は、状態がますますネストされるにつれて醜くなるので、状態を更新するには immutability-helper
packageを使用することをお勧めします。
immutability helper
で状態を更新する方法については、この回答を参照してください。
一行で書く
this.setState({ someProperty: { ...this.state.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
プロパティは変更されていません。ところで、状態オブジェクトを直接変更することは、一般的に悪い方法と考えられています。
そのため、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))
これは事実上同じです
this.state.nested.flag = false
this.forceUpdate()
この文脈におけるforceUpdate
とsetState
の微妙な違いについては、リンクされた例を見てください。
state
は読み取り専用でなければならないので、もちろんこれはいくつかのコア原則を悪用していますが、すぐに古い状態を破棄して新しい状態に置き換えるので、まったく問題ありません。
状態{willを含むコンポーネントが正しく更新され、正しくレンダリングされた場合でも、{( これ以外は ) _になりますが、小道具はfailで子に伝播します(下記のSpymasterのコメント参照)。自分がしていることがわかっている場合にのみ、この手法を使用してください。
たとえば、更新されて簡単に渡される、変更されたフラットプロップを渡すことができます。
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
ComplexNestedPropの参照は変更されませんでしたが( shouldComponentUpdate )
this.props.complexNestedProp === nextProps.complexNestedProp
コンポーネントwillは、親コンポーネントが更新されるたびに再描画されます。これは、親でthis.setState
またはthis.forceUpdate
を呼び出した後のケースです。
ネストしたstate を使用して状態を直接変更するのは危険です。なぜなら、異なるオブジェクトが(意図的かどうかにかかわらず) state への異なる(古い)参照を保持し、 PureComponent
を使用する場合、またはshouldComponentUpdate
がfalse
を返すように実装されている場合の例) _または_ は、以下の例のように古いデータを表示するためのものです。
履歴データをレンダリングすることになっているタイムラインを想像してください。手元のデータを変更すると、前の項目も変更されるため、予期しない動作が発生します。
とにかくここであなたはNested PureChildClass
が小道具が伝播できなかったためにレンダリングされていないことがわかります。
ES2015を使用している場合は、Object.assignにアクセスできます。次のように使用して、入れ子になったオブジェクトを更新できます。
this.setState({
someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});
更新したプロパティを既存のプロパティとマージし、返されたオブジェクトを使用して状態を更新します。
編集:carkodが指摘したように、状態が直接変化しないようにするために、割り当て関数に空のオブジェクトをターゲットとして追加.
これを手助けするライブラリはたくさんあります。たとえば、 immutability-helper を使用します。
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
lodash/fp setを使用します。
import {set} from 'lodash/fp';
const newState = set(["someProperty", "flag"], false, this.state);
lodash/fp mergeを使用します。
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
ES6の場合:
this.setState({...this.state, property: {nestedProperty: "new value"}})
これは以下と同等です。
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
追加のパッケージ、ライブラリ、特別な関数を必要としない、このスレッドで与えられた最初の答えのバリエーションです。
state = {
someProperty: {
flag: 'string'
}
}
handleChange = (value) => {
const newState = {...this.state.someProperty, flag: value}
this.setState({ someProperty: newState })
}
特定のネストしたフィールドの状態を設定するために、オブジェクト全体を設定しました。これを行うには、変数newState
を作成し、ES2015 spread演算子 を使用して現在の状態の内容をfirstに展開します。その後、this.state.flag
の値を新しい値に置き換えました(flag: value
afterを設定したため、現在の状態をオブジェクトに展開したので、現在の状態のflag
フィールドは上書きされます)。それから、someProperty
の状態を自分のnewState
オブジェクトに設定します。
このような問題に対処するには、Immer https://github.com/mweststrate/immer を使用します。
このコードをコンポーネントの1つに置き換えただけです。
this.setState(prevState => ({
...prevState,
preferences: {
...prevState.preferences,
[key]: newValue
}
}));
これとともに
import produce from 'immer';
this.setState(produce(draft => {
draft.preferences[key] = newValue;
}));
水浸しであなたはあなたの状態を「普通のオブジェクト」として扱う。マジックはプロキシオブジェクトを使って舞台裏で起こります。
私はこの解決策を使いました。
あなたがこのような入れ子になった状態があるならば:
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" />
私はあなたのコンポーネントの状態の完全なコピーを作成することをめぐる懸念{ すでに表明されている を真剣に考えています。それでも、Immerを強くお勧めします。
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
Immer
は巧妙に任意の深い状態ツリーを効率的にコピーするためにプロキシオブジェクトを使用するので、これはReact.PureComponent
(すなわちReactによる浅い状態比較)のために働くべきです。 ImmerはImmutability Helperのようなライブラリに比べて型保証も多く、JavascriptユーザーとTypeScriptユーザーの両方にとって理想的です。
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)}
まだ言及されていない他の2つのオプション:
状態のコピーを作成します。let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
このオブジェクトを変更します。someProperty.flag = "false"
今状態を更新しますthis.setState({someProperty})
入れ子にすることは実際にはコンポーネントの状態をどのように扱うべきかということではありませんが、単層の入れ子にするのが簡単なこともあります。
こんな状態に
state = {
contact: {
phone: '888-888-8888',
email: '[email protected]'
}
address: {
street:''
},
occupation: {
}
}
使用できる再利用可能なメソッドはこのようになります。
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')}
/>
物事を一般的なものにするために、私は@ 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メソッド
@ 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);
}
@ Qwertyの回答として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);
}
注意:上記のメソッドは配列には使えません
私はそれが古い質問であることを知っていますが、それでも私がこれを達成した方法を共有したいと思いました。コンストラクタ内の状態を仮定すると、次のようになります。
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"
/>
私はこれがうまくいくことがわかりました。私の場合はプロジェクトフォームを用意しました。たとえば、あなたは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>
)
お知らせ下さい!
reduce検索でネストされた更新を行います:
例:
状態のネストされた変数:
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 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)
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)
}