# 一.基础

React中constructor是唯一可以初始化state的地方,也可以把它理解成一个钩子函数,该函数最先执行且只执行一次。

更新状态不要直接修改this.state。虽然状态可以改变,但不会触发组件的更新。

应当使用this.setState(),该方法接收两种参数:对象或函数。

  • 对象:即想要修改的state
  • 函数:接收两个函数,第一个函数接受两个参数,第一个是当前state,第二个是当前props,该函数返回一个对象,和直接传递对象参数是一样的,就是要修改的state;第二个函数参数是state改变后触发的回调。

# 二.setState同步还是异步?

# 1.setState执行后会发生什么

大致过程包括更新state,创建新的VNode,再经过diff算法比对差异,决定渲染哪一部分以及怎么渲染,最终形成最新的UI。主要生命周期:

  • shouleComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate
  • componentWillReceiveProps(子组件的数据依赖于父组件时)

假如setState是同步更新的,每更新一次,这个过程都要完整执行一次,无疑会造成性能问题。事实上这些生命周期为纯函数,对性能还好,但是diff比较、更新DOM总消耗时间和性能吧。

// 如果setState是一个同步执行的机制,那么这个组件会被重新渲染100次,这对性能是一个相当大的消耗。
for ( let i = 0; i < 100; i++ ) {
    this.setState( { num: this.state.num + 1 } );
}
1
2
3
4

此外为了批次和效能,多个setState有可能在执行过程中还会被合并,所以setState延时异步更新是很合理的

# 2.setState何时同步何时异步?

由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。

React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。

# 3.例子

# 异步

constructor() {
  this.state = {
    count: 10
  }

  this.handleClickOne = this.handleClickOne.bind(this)
  this.handleClickTwo = this.handleClickTwo.bind(this)
}
render() {
  return (
    <button onClick={this.hanldeClickOne}>clickOne</button>
    <button onClick={this.hanldeClickTwo}>clickTwo</button>
    <button id="btn">clickTwo</button>
  )
}
handleClickOne() {
  this.setState({ count: this.state.count + 1})
  console.log(this.state.count)//10
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 同步(绕过React)

// 例子1,原生事件中修改状态
componentDidMount() {
  document.getElementById('btn').addEventListener('clcik', () => {
    this.setState({ count: this.state.count + 1})
    console.log(this.state.count)//11
  })
}
//例子2,setTimeout
handleClickTwo() {
  setTimeout(() => {
    this.setState({ count: this.state.count + 1})
    console.log(this.state.count)//11
  }, 10)  
} 
// 例子3,使用回调函数
state = {
    number:1
};
componentDidMount(){
    this.setState({number:3},()=>{
        console.log(this.state.number)//3
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 三.React是怎样控制异步和同步的呢?

# react同步

// 最终的结果只加了1
handleClick() {
  this.setState({
    count: this.state.count + 1
  })
  this.setState({
    count: this.state.count + 1
  })
  this.setState({
    count: this.state.count + 1
  })
}
// 上面的代码等同于这样,count相当于一个快照,所以不管重复多少次,结果都是加1。
handleClick() {
  const count = this.state.count

  this.setState({
    count: count + 1
  })
  this.setState({
    count: count + 1
  })
  this.setState({
    count: count + 1
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# react异步

// 第一个函数接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数。
increment(state, props) {
  return {
    count: state.count + 1
  }
}

handleClick() {
  this.setState(this.increment)
  this.setState(this.increment)
  this.setState(this.increment)
}
// 在同一个事件处理程序中不要混用,第一次执行setState,count为11,第二次执行,this.state仍然是没有更新的状态,所以this.state.count又打回了原形为10,加1以后变成11,最后再执行setState,所以最终count的结果是12。(render依然只执行一次)
increment(state, props) {
  return {
    count: state.count + 1
  }
}

handleClick() {
  this.setState(this.increment)
  this.setState({ count: this.state.count + 1 })
  this.setState(this.increment)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 回调函数
  1. 语法:setState(updater[, callback]) updater是要改变的state对象,callback是state导致的页面重新渲染的回调,等价于componentDidUpdate 一般而言,在设置页面某些state的时候,需要先设置好state,然后再对页面的一些参数进行修改的时候,可以使用setState的回调函数。
  2. 使用

// 不在回调中使用参数,我们在设置state后立即使用state:
this.state = {foo: 1};
this.setState({foo: 123});
console.log(this.state.foo);// 1

// 在回调中调用设置好的state
this.state = {foo: 2};
this.setState({foo: 123}, ()=> {
 console.log(foo);// 123
});
1
2
3
4
5
6
7
8
9
10
11

# 参考

https://www.jianshu.com/p/799b8a14ef96