Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
243 views
in Technique[技术] by (71.8m points)

javascript - In ReactJS, why does `setState` behave differently when called synchronously?

I am trying to understand the underlying cause for some somewhat "magical" behavior I am seeing that I cannot fully explain, and which is not apparent from reading the ReactJS source code.

When calling the setState method synchronously in response to an onChange event on an input, everything works as expected. The "new" value of the input is already present, and so the DOM is not actually updated. This is highly desirable because it means the cursor will not jump to the end of the input box.

However, when running a component with exactly the same structure but that calls setState asynchronously, the "new" value of the input does not appear to be present, causing ReactJS to actually touch the DOM, which causes the cursor to jump to the end of the input.

Apparently, something is intervening to "reset" the input back to its prior value in the asynchronous case, which it is not doing in the synchronous case. What is this mechanic?

Synchronous Example

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});
    }
}));

Note that the code will log in the render method, printing out the current value of the actual DOM node.

When typing an "X" between the two Ls of "Hello", we see the following console output, and the cursor stays where expected:

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

Asynchronous Example

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});
    }
}));

This is precisely the same as the above, except that the call to setState is in a setTimeout callback.

In this case, typing an X between the two Ls yields the following console output, and the cursor jumps to the end of the input:

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

Why is this?

I understand React's concept of a Controlled Component, and so it makes sense that user changes to the value are ignored. But it looks like the value is in fact changed, and then explicitly reset.

Apparently, calling setState synchronously ensures that it takes effect before the reset, while calling setState at any other time happens after the reset, forcing a re-render.

Is this in fact what's happening?

JS Bin Example

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's what's happening.

Synchronous

  • you press X
  • input.value is 'HelXlo'
  • you call setState({value: 'HelXlo'})
  • the virtual dom says the input value should be 'HelXlo'
  • input.value is 'HelXlo'
    • no action taken

Asynchronous

  • you press X
  • input.value is 'HelXlo'
  • you do nothing
  • the virtual DOM says the input value should be 'Hello'
    • react makes input.value 'Hello'.

Later on...

  • you setState({value: 'HelXlo'})
  • the virtual DOM says the input value should be 'HelXlo'
    • react makes input.value 'HelXlo'
    • the browser jumps the cursor to the end (it's a side effect of setting .value)

Magic?

Yes, there's a bit of magic here. React calls render synchronously after your event handler. This is necessary to avoid flickers.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...