私の状態は次のとおりです。
this.setState({ selected: { id: 1, name: 'Foobar' } });
次に、状態を更新します。
this.setState({ selected: { name: 'Barfoo' }});
SetStateはマージすることを前提としているため、次のようになります。
{ selected: { id: 1, name: 'Barfoo' } };
しかし、代わりにidを食べ、状態は次のとおりです。
{ selected: { name: 'Barfoo' } };
これは予想される動作ですか?ネストされた状態オブジェクトの1つのプロパティのみを更新するソリューションは何ですか?
setState()
は再帰的なマージをしないと思います。
現在の状態this.state.selected
の値を使用して新しい状態を構築し、その上でsetState()
を呼び出すことができます。
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
ここでは、関数の_.extend()
関数(underscore.jsライブラリから)を使用して、状態の浅いコピーを作成することにより、状態の既存のselected
部分への変更を防ぎました。
別の解決策は、setStateRecursively()
を記述することです。これは、新しい状態で再帰マージを行い、それを使用してreplaceState()
を呼び出します。
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
不変性ヘルパーは最近React.addonsに追加されたので、それで次のようなことができます:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
不変性ヘルパーのドキュメント: http://facebook.github.io/react/docs/update.html
回答の多くは現在の状態を新しいデータのマージの基礎として使用しているため、これが壊れる可能性があることを指摘したいと思いました。状態の変更はキューに入れられ、コンポーネントの状態オブジェクトをすぐには変更しません。したがって、キューが処理される前に状態データを参照すると、setStateで行った保留中の変更を反映しない古いデータが得られます。ドキュメントから:
setState()はすぐにthis.stateを変更しませんが、保留状態遷移を作成します。このメソッドを呼び出した後にthis.stateにアクセスすると、既存の値が返される可能性があります。
つまり、後続のsetStateの呼び出しで「現在の」状態を参照として使用することは信頼できません。例えば:
現在の状態を使用する必要がある場合(たとえば、ネストされたオブジェクトにデータをマージするため)、setStateはオブジェクトの代わりに引数として関数を受け入れます。関数は、状態に対する以前の更新後に呼び出され、状態を引数として渡します。したがって、これを使用して、以前の変更を尊重することが保証されたアトミックな変更を行うことができます.
別のライブラリをインストールしたくなかったので、別のソリューションを紹介します。
の代わりに:
this.setState({ selected: { name: 'Barfoo' }});
代わりにこれを行います:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
または、コメントの@ icc97のおかげで、さらに簡潔ですが、間違いなく読みにくいです:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
また、明確にするために、この回答は上記の@bgannonplが懸念することのいずれにも違反していません。
@bgannonplの回答に基づいて以前の状態を保持する:
Lodash例:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
適切に動作することを確認するには、2番目のパラメーター関数コールバックを使用できます。
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
merge
は他のプロパティを破棄するため、extend
を使用しました。
React Immutability例:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
この種の状況に対する私の解決策は、指摘された別の答えのように、 不変性ヘルパー を使用することです。
状態を詳細に設定することは一般的な状況であるため、次のミックスインを作成しました。
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
このミックスインはほとんどのコンポーネントに含まれており、通常はsetState
を直接使用しなくなりました。
このミックスインを使用して、目的の効果を得るために必要なことは、次の方法で関数setStateinDepth
を呼び出すことだけです。
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
詳細については:
setStateinDepth
に渡されるパラメーターの構文については、Immutability Helpers documentation を参照してください。今のところ、
次の状態が前の状態に依存している場合は、代わりにアップデーター関数フォームを使用することをお勧めします。
ドキュメントによると https://reactjs.org/docs/react-component.html#setstate 、以下を使用:
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
編集:このソリューションは、 スプレッド構文 を使用していました。目標は、prevState
への参照なしでオブジェクトを作成し、prevState
が変更されないようにすることでした。しかし、私の使用法では、prevState
は時々変更されるように見えました。そのため、副作用のない完全なクローンを作成するために、prevState
をJSONに変換してから、再び元に戻します。 (JSONを使用するためのインスピレーションは MDN から来ました。)
覚えておいてください:
setState(prevState => stateChange)
を使用してください手順
state
のrootレベルのプロパティのコピーを作成しますステップ3と4は1行で組み合わせることができます。
this.setState(prevState => {
var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
newSelected.name = 'Barfoo'; //2
var update = { selected: newSelected }; //3
return update; //4
});
this.setState(prevState => {
var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
selected.name = 'Barfoo'; //2
return { selected }; //3, 4
});
これはReactガイドラインにきちんと従っています。 eicksl's に基づく同様の質問への回答。
最初に状態を設定します
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
状態オブジェクトのあるレベルのプロパティを変更しています:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
私はes6クラスを使用していますが、トップステートにいくつかの複雑なオブジェクトがあり、メインコンポーネントをよりモジュラー化しようとしていたので、トップコンポーネントの状態を維持するためにシンプルなクラスラッパーを作成しましたが、より多くのローカルロジックを許可します。
ラッパークラスは、メインコンポーネント状態のプロパティを設定するコンストラクターとして関数を受け取ります。
export default class StateWrapper {
constructor(setState, initialProps = []) {
this.setState = props => {
this.state = {...this.state, ...props}
setState(this.state)
}
this.props = initialProps
}
render() {
return(<div>render() not defined</div>)
}
component = props => {
this.props = {...this.props, ...props}
return this.render()
}
}
次に、トップステートの各複雑なプロパティに対して、StateWrappedクラスを1つ作成します。ここでコンストラクタでデフォルトの小道具を設定できます。クラスの初期化時に設定されます。値についてはローカル状態を参照し、ローカル状態を設定し、ローカル関数を参照し、チェーンに渡すことができます。
class WrappedFoo extends StateWrapper {
constructor(...props) {
super(...props)
this.state = {foo: "bar"}
}
render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>
onClick = () => this.setState({foo: "baz"})
}
したがって、トップレベルのコンポーネントには、各クラスをそのトップレベルの状態プロパティに設定するためのコンストラクター、単純なレンダリング、およびコンポーネント間で通信するすべての関数が必要です。
class TopComponent extends React.Component {
constructor(...props) {
super(...props)
this.foo = new WrappedFoo(
props => this.setState({
fooProps: props
})
)
this.foo2 = new WrappedFoo(
props => this.setState({
foo2Props: props
})
)
this.state = {
fooProps: this.foo.state,
foo2Props: this.foo.state,
}
}
render() {
return(
<div>
<this.foo.component onClick={this.onClickFoo} />
<this.foo2.component />
</div>
)
}
onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}
私の目的では非常にうまく機能しているようです、ラップされた各コンポーネントは独自の状態を追跡しているが、トップコンポーネントの状態を更新しているため、トップレベルコンポーネントでラップされたコンポーネントに割り当てるプロパティの状態を変更できないことに注意してくださいそれが変わるたびに。
React状態は、setState
で再帰的マージを実行しませんが、インプレース状態メンバーの更新が同時に行われないことを期待しています。囲まれたオブジェクト/配列を自分で(array.sliceまたはObject.assignで)コピーするか、専用ライブラリを使用する必要があります。
このように。 NestedLink は、複合React状態の処理を直接サポートします。
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
また、selected
またはselected.name
へのリンクは、単一のプロップとしてどこにでも渡すことができ、そこでset
で変更できます。