react面试题之setState

1,133 阅读2分钟

react经典面试题,看似简单,实际分析是比较复杂的。

声明:以下的所有分析适用于Legacy Mode与Blocking Mode,不确定是否适用于最新的Concurrent Mode,不了解的请移步react三种模式的功能比较


codesanbox

import React, { Component } from "react";

export default class extends Component {
  state = {
    count1: 1,
    count2: 1
  };

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

    setTimeout(() => {
      this.setState({ count2: this.state.count2 + 1 });
      this.setState({ count2: this.state.count2 + 1 });
    }, 0);
  }

  render() {
    return (
      <div>
        <h1>{this.state.count1}</h1>
        <h1>{this.state.count2}</h1>
      </div>
    );
  }
}

1.没有setTimeout的setState

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

第一次setState的调用栈

从图中可以发现excecutionContext不等于NoContext,所以关键函数flushSyncCallbackQueue是不会执行的

第二次setState的调用栈

与第一次一样关键函数flushSyncCallbackQueue没有执行

react每次setState都会生成一个update对象,两次setState生成的update通过next指针关联起来形成一个链表,fiber节点中updateQueue对象指向的就是这个副作用链表

flushSyncCallbackQueue函数正是处理updateQueue的入口

第一次渲染后的componentDidMount(应该包括componentWillMount)生命周期中所有的setState都不会更新DOM,而是被合并进updateQuene中在最后由flushSyncCallbackQueue函数调用performSyncWorkOnRoot函数统一处理后更新DOM。

而更新实例state的函数的入口也是flushSyncCallbackQueue函数,所以在调用它之前实例的state并不会改变,下一次setState拿到的this.state.count依然是1。

2.有setTimeout的setState

在第一次渲染和处理完副作用链表之后会将excecutionContext置为0

每次都会调用flushSyncCallbackQueue函数,意味着调用setState都会更新实例state和更新对应DOM执行完这些后才会调用下一次的setState。

3.总结

react在第一次渲染后调用的componentDidMount(componentWillMount)生命周期中的setState都会合并成进updateQuene,然后批量处理相当于batching。个人理解:这样做的好处就是减少对DOM的操作,优化首屏速度,优化的代价就是产生setState数据不同步的问题,当然解决办法也很简单这里就不做阐述了。