react 常见setState的原理解析

3,703 阅读3分钟

React.setState

首先引入一个栗子

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }
  render() {
    return null;
  }
};

4次log的值 分别为 0 0 2 3

setState 干了什么

image

说一下批量更新

image

解读为什么直接修改this.state无效

要知道setState本质是通过一个队列机制实现state更新的。 执行setState时,会将需要更新的state合并后放入状态队列,而不会立刻更新state,队列机制可以批量更新state。 如果不通过setState而直接修改this.state,那么这个state不会放入状态队列中,下次调用setState时对状态队列进行合并时,会忽略之前直接被修改的state,这样我们就无法合并了,而且实际也没有把你想要的state更新上去。

什么是批量更新 Batch Update

在一些mv*框架中,,就是将一段时间内对model的修改批量更新到view的机制。比如那前端比较火的React、vue(nextTick机制,视图的更新以及实现)为例。

vue的nextTick机制 www.cnblogs.com/hity-tt/p/6…

html5新特性变动观察器 www.cnblogs.com/jscode/p/36…

消息进程 www.ruanyifeng.com/blog/2013/1…

vue的批量更新体现

  1. Mutation Observer(变动观察器)是监视DOM变动的接口。当DOM对象树发生任何变动时,Mutation Observer会得到通知。
  2. 概念上,它很接近事件。可以理解为,当DOM发生变动会触发Mutation Observer事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说DOM发生变动立刻会触发相应的事件;
  3. Mutation Observer则是异步触发,DOM发生变动以后,并不会马上触发,而是要等到当前所有DOM操作都结束后才触发。
  4. 这样设计是为了应付DOM变动频繁的情况。举例来说,如果在文档中连续插入1000个段落(p元素),会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;
  5. 而Mutation Observer完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。

setState之后发生的事情

  • 在官方的描述中,setState操作并不保证是同步的,也可以认为是异步的。
  • React在setState之后,会经对state进行diff,判断是否有改变,然后去diff dom决定是否要更新UI。如果这一系列过程立刻发生在每一个setState之后,就可能会有性能问题。
  • 在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。

总结

  1. 通过setState去更新this.state,不要直接操作this.state,请把它当成不可变的。
  2. 调用setState更新this.state不是马上生效的,它是异步滴,所以不要天真以为执行完setState后this.state就是最新的值了。
  3. 多个顺序执行的setState不是同步地一个一个执行滴,会一个一个加入队列,然后最后一起执行,即批处理

如何知道state已经被更新

传入回调函数

setState({
    index: 1
}}, function(){
    console.log(this.state.index);
})

在钩子函数中体现

componentDidUpdate(){
    console.log(this.state.index);
}

setState的另外一种方式 (需要使用上一次的state的值)

在setState的第一个参数中传入function,该function会被压入调用栈中,在state真正改变后,按顺序回调栈里面的function。该function的第一个参数为上一次更新后的state。这样就能确保你下一次的操作拿到的是你预期的值

lass Com extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            index: 0
        }
        this.add = this.add.bind(this);
    }

    add(){
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
    }
}

注意点

  1. setState可能会引发不必要的渲染(renders)
  2. setState无法完全掌控应用中所有组件的状态