SetState 状態の更新が同期的であると誤解させる新参者に悪名高い評判があります。 その間 setState 調整アルゴリズムを介して状態を計算するのは非常に高速ですが、それでも非同期操作であるため、不適切に使用すると、Reactコンポーネントで複雑な状態を管理することが非常に困難になる可能性があります。 この記事では、 setState's アトミック状態の更新を保証するあまり知られていない機能シグネチャ

SetStateClassic™

Reactをしばらく使用している場合は、次のような状況に遭遇した可能性があります。

/*
The initial value of
this.state.count = 0
*/

// multiple calls
this.setState(count: this.state.count + 1);
this.setState(count: this.state.count + 1);
console.log(this.state.count); // 1

// for-loop
for (let i = 0; i < 10; i++) {
  this.setState({count: this.state.count + 1});
}
console.log(this.state.count); // 1

// if-statement
this.setState({count: this.state.count + 1});
if (this.state.count === 0) {
  console.log(this.state.count);  // 0
}

SetState Reactコミュニティへの新規参入者に多くの混乱を引き起こしました。 キーワード「reactsetstate」を使用してStackOverflowをすばやく検索すると、まだ曇りがあるかどうかがわかります setState 非同期または同期です。 Eric Elliotは、 setStateをすべて一緒に回避し、Reduxにすべての状態の更新を処理させることさえ提案しました。

そして、不満は部分的に正当化されます。 公式のReactドキュメントでは、この古典的なパスの方法 setState オブジェクトリテラルは、修正が難しい「競合状態」のバグを引き起こす傾向があります。 この問題は、コンポーネントに多くのコンポーネントがある場合に悪化します state 管理する。

機能的なSetState

別の使い方があります setState これは、ドキュメントであまり目立つように宣伝されていません。

this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  console.log(prevState.count);  // 3
  return {count: prevState.count + 1};
})

これまで見たことがなければ、私がこれを作っていると思うのは間違いないでしょうが、それは本物のだと確信しています。 (使い慣れたオブジェクトリテラルの代わりに)関数を渡すことにより、現在の状態ツリーを最初の引数として取得し、制御を次の引数に渡す前に、独自の任意の計算を実行する機会があります。 setState 呼び出します。 つまり、これらの関数はアトミック状態の更新を受け取ることが保証されています。

「関数を使用してsetStateを複数回呼び出すのは安全です。 更新はキューに入れられ、後で呼び出された順序で実行されます。」 –ダン・アブラモフ

この使い方 setState Reactで状態を操作するための予測可能で信頼性の高い方法を提供します。 より冗長ですが、状態を更新するために大規模な状態ツリーや高度なロジックを使用している場合は不可欠になります。

両方のアプローチを比較できるように、実際の例を見てみましょう。

パスワードバリデーターの構築

ユーザーがパスワードをリセットできるようにするフォームを作成していると想像してください。 いくつかの状態を保持する必要があります。 そのほとんどは、パスワード強度を検証するためのものです。

class PasswordForm extends Component {

  constructor(props) {
    super(props);
    this.state = {
      password: '',
      hasEnoughChars: false,
      hasUpperAndLowercaseChars: false,
      hasSpecialChars: false,
      isPasswordValid: false
    };
  }

  render() {
    return (
      <div>

        /*input*/
        <input onChange={this.handleInput} type="password" value={this.state.password}/>

        /*visual prompts*/
        <div>
          <span style={bgColor(this.state.hasEnoughChars)}>
            Minimum 8 characters
          </span>
          <span style={bgColor(this.state.hasUpperAndLowercaseChars)}>
            Include 1 uppercase and lowercase letter
          </span>
          <span style={bgColor(this.state.hasSpecialChars)}>
            Minimum 1 special character
          </span>
        </div>

        /*button*/
        <button disabled={!this.state.isPasswordValid}>
          Submit
        </button>

      </div>
    );
  }

  bgColor(condition) {
    return {backgroundColor: condition ? 'green' : 'red'};
  }

  // Object literal & Callback
  handleInput(e) {

    this.setState({
      password: e.target.value,
      hasEnoughChars: e.target.value.length >= 8,
      hasUpperAndLowercaseChars: /[a-z]/.test(e.target.value) && /[A-Z]/.test(e.target.value),
      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(e.target.value)
    }, () => {

      if (this.state.hasEnoughChars && this.state.hasUpperAndLowercaseChars && this.state.hasSpecialChars) {
        this.setState({isPasswordValid: true});
      }
      else {
        this.setState({isPasswordValid: false});
      }
    });
  }

  // Functions
  handleInput(e) {

    this.setState({password: e.target.value});

    this.setState((prevState) => ({
      hasEnoughChars: prevState.password.length >= 8,
      hasUpperAndLowercaseChars: /[a-z]/.test(prevState.password) && /[A-Z]/.test(prevState.password),
      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(prevState.password)
    }));

    this.setState((prevState) => ({
      isPasswordValid: prevState.hasEnoughChars
        && prevState.hasUpperAndLowercaseChars
        && prevState.hasSpecialChars
    }));
  }
}

コールバックの使用は、関数を入れ子にすることによって物事の順序を管理するという考え方に私たちを置きます。 今のところそれほど悪くはありませんが、後にコールバックを追加する必要がある場合 this.setState({isPasswordValid}) 物事はかなりすぐに醜くなります。

機能の使用それぞれ setState 1つの関心領域に合わせて調整されています。 このため、状態を遷移するために必要なすべての関連情報が最初の引数で提供されるため、これらの関数定義内で状態更新の順序を「ハードコーディング」する必要はありません。 prevState.

まとめ

この関数シグネチャを使用して setState 最新の状態ツリーを直接操作しています。 この予測可能性により、問題についてより効果的に推論し、自信を持って状態の豊富なReactコンポーネントを構築できます。

このパターンを試してみて、気に入ったかどうかを確認してください。 この使い方のチャンスがあります setState 事実上になります。 ツイートで、Dan Abramovは、「将来、Reactは最終的にこのパターンをより直接的にサポートする可能性が高い」と示唆しました。

👉パスワードフォームのCodePenをチェックしてください