Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 中的 setState 什么时候是异步的,什么时候是同步的? #4

Open
z0nka1 opened this issue Nov 16, 2020 · 0 comments
Labels

Comments

@z0nka1
Copy link
Owner

z0nka1 commented Nov 16, 2020

让我们先从一道典型面试题开始:

class Test extends Component {

    state = {

        count: 0

    }

    componentDidMount(){

        this.setState({ count: this.state.count + 1 });

        console.log(this.state.count);

        setTimeout(() => {

          this.setState({ count: this.state.count + 1 });

          console.log("setTimeout: " + this.state.count);

        }, 0);

    }

    render(){

        ...

    }

}

上面代码打印结果是什么?

如果你认为setState是异步的,所以这里应该打印0,0

这样是错的。

正确答案应该是0,2

要知道为什么,就要理解React中的合成事件。因为在合成事件中触发的setState才是异步的,而在合成事件之外触发的就是同步的。

合成事件

大家都知道事件委托,合成事件也是如此:

  • React给html标签挂上事件监听
  • DOM事件触发后冒泡到html标签
  • React找到触发事件的组件,造出一个合成事件
  • 并按组件树模拟一遍事件冒泡

那么哪些事件才能被React捕获生成合成事件呢?

下面的测试快照罗列了可以被React捕获生成合成事件的事件:

// react/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap

Array [

	  "abort",

	  "animationEnd",

	  "animationIteration",

	  "animationStart",

	  "auxClick",

	  "beforeInput",

	  "blur",

	  "canPlay",

	  "canPlayThrough",

	  "cancel",

	  "change",

	  "click",

	  "close",

	  "compositionEnd",

	  "compositionStart",

	  "compositionUpdate",

	  "contextMenu",

	  "copy",

	  "cut",

	  "doubleClick",

	  "drag",

	  "dragEnd",

	  "dragEnter",

	  "dragExit",

	  "dragLeave",

	  "dragOver",

	  "dragStart",

	  "drop",

	  "durationChange",

	  "emptied",

	  "encrypted",

	  "ended",

	  "error",

	  "focus",

	  "gotPointerCapture",

	  "input",

	  "invalid",

	  "keyDown",

	  "keyPress",

	  "keyUp",

	  "load",

	  "loadStart",

	  "loadedData",

	  "loadedMetadata",

	  "lostPointerCapture",

	  "mouseDown",

	  "mouseEnter",

	  "mouseLeave",

	  "mouseMove",

	  "mouseOut",

	  "mouseOver",

	  "mouseUp",

	  "paste",

	  "pause",

	  "play",

	  "playing",

	  "pointerCancel",

	  "pointerDown",

	  "pointerEnter",

	  "pointerLeave",

	  "pointerMove",

	  "pointerOut",

	  "pointerOver",

	  "pointerUp",

	  "progress",

	  "rateChange",

	  "reset",

	  "scroll",

	  "seeked",

	  "seeking",

	  "select",

	  "stalled",

	  "submit",

	  "suspend",

	  "timeUpdate",

	  "toggle",

	  "touchCancel",

	  "touchEnd",

	  "touchMove",

	  "touchStart",

	  "transitionEnd",

	  "volumeChange",

	  "waiting",

	  "wheel",

	]

由此我们看到,addEventListenersetTimeoutsetInterval等原生事件并不在此列,所以,这些原生事件中触发的setState就是同步的。

其实,在React源码中,会有一个变量标记state应该同步更新还是异步更新,这个变量就是isBatchingUpdates。如果为true,就是异步更新,反之则是同步更新。

那为什么要有异步更新呢?

2017年的时候就已经有人问过这个问题,官方回答主要提到了以下两个点:

  1. 保持内部一致性。因为props是异步的,所以state也要是异步
  2. React后期会启用并发更新,state异步更新是为了并发更新做准备。为了完成异步渲染,React会在setState时根据来源不同分配不同的优先级,再根据优先级并发处理,提升渲染性能。
@z0nka1 z0nka1 added the React label Dec 10, 2020
@z0nka1 z0nka1 changed the title React 中的 setState 什么时候是异步的,什么时候是同步的? React 中的 setState 什么时候是异步的,什么时候是同步的?(待完成) Dec 14, 2020
@z0nka1 z0nka1 changed the title React 中的 setState 什么时候是异步的,什么时候是同步的?(待完成) React 中的 setState 什么时候是异步的,什么时候是同步的? Dec 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant