web-dev-qa-db-ja.com

入れ子になったReact状態プロパティをsetState()で更新する最良の方法は何ですか?

これらのオプションの中で、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
            }
          }
        });
      });
    }
  );
}
8
cbdeveloper

スプレッド演算子を使用する

  let {foo} = this.state;

  foo = {
    ...foo,
    bar: baz
  }

  this.setState({
    foo
  })
0
Mark