これらのオプションの中で、React setState()メソッドを使用してネストされたプロパティを更新するための最良の方法は何かを考えています。また、パフォーマンスを考慮し、可能な限り回避するためのより効率的なメソッドに開かれています。他の可能な同時状態変更と競合します。
注:私はReact.Component
を拡張するクラスコンポーネントを使用しています。 React.PureComponent
を使用している場合、ネストされたプロパティを更新するときは、state
のトップレベルのプロパティを変更しないと再レンダリングがトリガーされない可能性があるため、特に注意が必要です。この問題を示すサンドボックスは次のとおりです。
CodeSandbox-コンポーネントvs PureComponentおよびネストされた状態の変更
この質問に戻ります-ここでの私の懸念は、ネストされたプロパティの状態を更新する際のパフォーマンスと他の同時setState()
呼び出し間の競合についてです:
例:
フォームコンポーネントを構築していて、フォームの状態を次のオブジェクトで初期化するとします。
this.state = {
isSubmitting: false,
inputs: {
username: {
touched: false,
dirty: false,
valid: false,
invalid: false,
value: 'some_initial_value'
},
email: {
touched: false,
dirty: false,
valid: false,
invalid: false,
value: 'some_initial_value'
}
}
}
私の研究から、setState()
を使用して、Reactはshallow merge渡すオブジェクトをつまり、この例ではisSubmitting
およびinputs
である最上位のプロパティのみをチェックすることになります。
したがって、これらの2つの最上位プロパティ(isSubmitting
およびinputs
)を含む完全なnewStateオブジェクトに渡すか、これらのプロパティの1つを渡すと、前のオブジェクトに浅くマージされます。状態。
質問1
更新するstate
トップレベルプロパティのみを渡すことがベストプラクティスであることに同意しますか?たとえば、isSubmitting
プロパティを更新しない場合、setState()
への他の同時呼び出しとの競合/上書きの可能性を回避するために、それを他のsetState()
に渡さないようにする必要があります。これと一緒にキューに入れられたのでしょうか?これは正しいです?
この例では、inputs
プロパティのみを持つオブジェクトを渡します。これにより、isSubmitting
プロパティを更新しようとしている別のsetState()
との競合/上書きが回避されます。
質問2
現在の状態をコピーしてネストされたプロパティを変更するには、パフォーマンス面で最良の方法は何ですか?
この場合、state.inputs.username.touched = true
を設定したいとします。
あなたがこれを行うことができたとしても:
this.setState( (state) => {
state.inputs.username.touched = true;
return state;
});
すべきではない。 React Docs から、次のようになります。
stateは、変更が適用されているときのコンポーネントの状態への参照です。直接変更しないでください。代わりに、変更はstateとpropsからの入力に基づいて新しいオブジェクトを構築することで表される必要があります。
したがって、上記の抜粋から、現在のstate
オブジェクトから新しいオブジェクトを構築して、必要に応じて変更して操作し、setState()
に渡す必要があると推測できます。 state
を更新します。
また、ネストされたオブジェクトを扱っているため、オブジェクトをディープコピーする方法が必要です。サードパーティのライブラリ(lodash)を使用したくない場合、私が思いついたのは次のとおりです。
this.setState( (state) => {
let newState = JSON.parse(JSON.stringify(state));
newState.inputs.username.touched = true;
return ({
inputs: newState.inputs
});
});
state
にネストされたオブジェクトがある場合、let newState = Object.assign({},state)
も使用しないでください。 state
のネストされたオブジェクト参照を浅くコピーするため、newState.inputs === state.inputs === this.state.inputs
がtrueになるため、状態を直接変更することになります。それらはすべて同じオブジェクトinputs
を指します。
ただし、JSON.parse(JSON.stringify(obj))
にはパフォーマンスの制限があり、JSONに適さない可能性があるいくつかのデータ型または循環データもあるので、ネストされたオブジェクトをディープコピーして更新するために、他のどのアプローチをお勧めしますか?
私が考え出した他の解決策は次のとおりです:
this.setState( (state) => {
let usernameInput = {};
usernameInput['username'] = Object.assign({},state.inputs.username);
usernameInput.username.touched = true;
let newInputs = Object.assign({},state.inputs,usernameInput);
return({
inputs: newInputs
});
};
この2番目の選択肢で行ったことは、更新する最も内側のオブジェクト(この場合はusername
オブジェクト)から新しいオブジェクトを作成することでした。そして、これらの値をキーusername
内に取得する必要があります。そのため、後でusernameInput['username']
を使用してnewInputs
オブジェクトにマージします。すべてがObject.assign()
を使用して行われます。
この2番目のオプションは改善されました パフォーマンス結果 。少なくとも50%優れています。
このテーマに関する他のアイデアはありますか?長い質問で申し訳ありませんが、問題をよく表しています。
編集:以下の回答から私が採用したソリューション:
私のTextInputコンポーネントのonChangeイベントリスナー(React Contextを通じて提供しています):
onChange={this.context.onChange(this.props.name)}
フォームコンポーネント内の私のonChange関数
onChange(inputName) {
return(
(event) => {
event.preventDefault();
const newValue = event.target.value;
this.setState( (prevState) => {
return({
inputs: {
...prevState.inputs,
[inputName]: {
...prevState.inputs[inputName],
value: newValue
}
}
});
});
}
);
}
スプレッド演算子を使用する
let {foo} = this.state;
foo = {
...foo,
bar: baz
}
this.setState({
foo
})