次のようにネストされたプロパティを使用して自分の状態を整理しようとしています:
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})
アイデアは、ダミーオブジェクトを作成して操作を実行し、コンポーネントの状態を更新されたオブジェクトで置き換えることです。
これで、spreadオペレーターは、オブジェクトの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
。そのため、パッケージを使用して状態を更新することをお勧めします。
で状態を更新する方法については、この回答を参照してください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
しません。また、プロパティは変更されていません。ちなみに、状態オブジェクトを直接変更することは、一般に悪い習慣と見なされています。
したがって、全体を再作成する必要があります parent
オブジェクトます。しかし、この場合、別の問題が発生します。Reactは、すべての子供が値を変更したと考え、それらすべてを再レンダリングします。もちろん性能は良くありません。
いくつかの複雑なロジックを記述することでその問題を解決することはまだ可能shouldComponentUpdate()
ですが、ここで止めて、短いバージョンの簡単な解決策を使用したいと思います。
Reactのネストされた状態は間違った設計です
この優れた答えを読んでください。
この回答の理由:
ReactのsetStateは組み込みの便利さですが、すぐに制限があることに気付くでしょう。カスタムプロパティを使用し、forceUpdateをインテリジェントに使用すると、さらに多くのことが可能になります。例えば:
class MyClass extends React.Component { myState = someObject inputValue = 42 ...
たとえば、MobXは状態を完全に切り離し、カスタムの監視可能なプロパティを使用します。
Reactコンポーネントで状態の代わりにオブザーバブルを使用します。
ネストされたプロパティを更新する別の短い方法があります。
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
読み取り専用で必要があるためていますが、古い状態をすぐに破棄して新しい状態に置き換えるため、完全に問題ありません。
状態を含むコンポーネントは適切に更新および再レンダリングされますが(この落とし穴を除く)、プロップは子への伝播に失敗します(下記の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
)ORは以下の例のように古いデータを表示するためのものです。
履歴データをレンダリングすることになっているタイムラインを想像してください。手元のデータを変更すると、以前のアイテムも変更されるため、予期しない動作が発生します。
とにかく、ここNested PureChildClass
では、プロップが伝播に失敗したために再レンダリングされていないことがわかります。
state
完全に切り離され、カスタムの監視可能なプロパティを使用することにも注意してください。React setState
は組み込みの便利さですが、すぐに制限があることに気付くでしょう。カスタムプロパティを使用してインテリジェントに使用すると、forceUpdate
さらに多くのことが可能になります。Reactコンポーネントで状態の代わりにオブザーバブルを使用します。
this.forceUpdate()
特定のをトリガーまたは実装するのは簡単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
に複数のネストされたプロパティが含まれている場合、最初のプロパティはそれらを削除し、2番目のプロパティは削除しません。codesandbox.io/s/nameless-pine-42thu
これに役立つライブラリがたくさんあります。たとえば、immutability-helperを使用します。
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メソッドも呼び出す必要があることを追加したいと思うかもしれません。もう1つの問題は、lodash .set
(非fp)が異なる順序の引数を持っていることです。
これらの種類の問題を処理するには、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;
}));
イマーを使用すると、状態を「通常のオブジェクト」として扱います。魔法は、プロキシオブジェクトを使用して舞台裏で起こります。
これは、追加のパッケージ、ライブラリ、または特別な関数を必要としない、このスレッドで与えられた最初の回答のバリエーションです。
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: {
}
}
使用される再利用可能なメソッドは次のようになります。
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)
これはフックステートの例です。ここでは、状態はツリーのようなデータ構造に深く/再帰的にネストされています。
まだ言及されていない他の2つのオプション:
一般的なものにするために、@ 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);
}
注:上記のメソッドは配列では機能しません
私は、コンポーネントの状態の完全なコピーを作成することに関してすでに述べられている懸念を非常に真剣に受け止めています。そうは言っても、私はイマーを強くお勧めします。
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のユーザーにとって理想的です。
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
代わりにに更新された状態をチェックするコンソールを作成して状態を設定するか、reactデバッガーツールを使用してformik状態の値の変更を確認します。boolean
setFieldValue