[javascript] ReactJSでは、なぜ `setState`は同期的に呼び出されたときに動作が異なりますか?



Answers

valueではなくdefaultValueを使用することで、私の問題が解決されました。 私はこれが最善の解決策であるかどうかは分かりませんが、例えば:

から:

return React.DOM.input({value: valueToSet,
    onChange: this.changeHandler});

に:

return React.DOM.input({defaultValue: valueToSet,
    onChange: this.changeHandler});

JS Binの例

http://jsbin.com/xusefuyucu/edit?js,output

Question

ReactJSのソースコードを読めば、私は完全には説明できないし、明らかになっていないいくつかのやや "魔法の"振る舞いの根本的な原因を理解しようとしています。

入力上でonChangeイベントに応答してsetStateメソッドを同期的に呼び出すと、 setState機能します。 入力の「新しい」値はすでに存在しているため、DOMは実際には更新されません。 これは、カーソルが入力ボックスの最後にジャンプしないことを意味するため、非常に望ましいです。

しかし、構造が全く同じであるが非同期setStateを呼び出すコンポーネントを実行すると、入力の「新しい」値は存在しないように見えるため、ReactJSがDOMに実際に触れ、カーソルが最後にジャンプします入力。

どうやら、何かが介入して非同期の場合に入力を元のvalueに「リセット」するが、これは同期の場合には行われない。 このメカニックは何ですか?

同期の例

var synchronouslyUpdatingComponent =
    React.createFactory(React.createClass({
      getInitialState: function () {
        return {value: "Hello"};
      },

      changeHandler: function (e) {
        this.setState({value: e.target.value});
      },

      render: function () {
        var valueToSet = this.state.value;

        console.log("Rendering...");
        console.log("Setting value:" + valueToSet);
        if(this.isMounted()) {
            console.log("Current value:" + this.getDOMNode().value);
        }

        return React.DOM.input({value: valueToSet,
                                onChange: this.changeHandler});
    }
}));

コードはrenderメソッドにログインし、実際のDOMノードの現在のvalueを出力します。

「Hello」の2つのLの間に「X」を入力すると、次のコンソール出力が表示され、カーソルは期待どおりの位置にとどまります。

Rendering...
Setting value:HelXlo
Current value:HelXlo

非同期の例

var asynchronouslyUpdatingComponent =
  React.createFactory(React.createClass({
    getInitialState: function () {
      return {value: "Hello"};
    },

    changeHandler: function (e) {
      var component = this;
      var value = e.target.value;
      window.setTimeout(function() {
        component.setState({value: value});
      });
    },

    render: function () {
      var valueToSet = this.state.value;

      console.log("Rendering...");
      console.log("Setting value:" + valueToSet);
      if(this.isMounted()) {
          console.log("Current value:" + this.getDOMNode().value);
      }

      return React.DOM.input({value: valueToSet,
                              onChange: this.changeHandler});
    }
}));

これは、 setStateの呼び出しがsetTimeoutコールバックにあることを除いて、上記とsetStateです。

この場合、2つのLの間にXを入力すると、次のコンソール出力が得られ、カーソルは入力の最後にジャンプします。

Rendering...
Setting value:HelXlo
Current value:Hello

どうしてこれなの?

私はReactのControlled Componentのコンセプトを理解しているので、 valueに対するユーザーの変更は無視されます。 しかし、 valueが実際に変更され、明示的にリセットされたように見えます。

どうやら、 setState同期的に呼び出すと、リセット前にその設定が有効なることが保証されます。リセット後に setStateを呼び出すと、再描画が強制されます。

実際にこれは起こっているのですか?

JS Binの例

http://jsbin.com/sogunutoyi/1/




私たちも同様の問題を抱えています。私たちのケースでは、非同期状態アップデートを使用する必要があります。

したがって、defaultValueを使用し、入力が反映しているモデルに関連付けられた入力にkeyパラメータを追加しkey 。 これにより、どのモデルでも入力はモデルに同期されたままになりますが、実際のモデルが変更されると、新しい入力が強制的に生成されます。




前述のように、Reactは入力の値を更新するのではなく、Reactが変更要求を傍受して一致するように状態を更新するため、制御されたコンポーネントを使用するときに問題になります。

FakeRainBrigandの答えは素晴らしいですが、更新が同期または非同期であるかどうかではなく、入力がこのように動作するかどうかは完全にはわかりません。 マスクを適用して戻り値を変更するような何かを同期して実行している場合、カーソルが行の最後にジャンプすることもあります。 残念ながら(?)これは、Reactが制御された入力に関してどのように機能するかです。 しかし、手動で作業することができます。

この問題については、 ベン・アルパーによるJSBinソリューションへのリンクが含まれています[手動でカーソルを置く必要がある場所に残っていることを確認する]

これは以下のような<Input>コンポーネントを使って実現されます:

var Input = React.createClass({
  render: function() {
    return <input ref="root" {...this.props} value={undefined} />;
  },
  componentDidUpdate: function(prevProps) {
    var node = React.findDOMNode(this);
    var oldLength = node.value.length;
    var oldIdx = node.selectionStart;
    node.value = this.props.value;
    var newIdx = Math.max(0, node.value.length - oldLength + oldIdx);
    node.selectionStart = node.selectionEnd = newIdx;
  },
});



Links