react 之 setState

2020-06-26  本文已影响0人  Lyan_2ab3
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  0

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

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

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log  3
    }, 0);
  }
  render() {
    return null;
  }
};
// 假如
 componentDidMount(){
  for ( let i = 0; i < 100; i++ ) {
    this.setState( { num: this.state.num + 1 } );
  }
}

组件渲染的结果是1,·且在控制台中输出了100次0,说明每个循环中,拿到的state仍然是更新之前的。

react 如何优化以上?

setState接收的参数还可以是 一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态

componentDidMount() {
    for ( let i = 0; i < 100; i++ ) {
        this.setState( prevState => {
            console.log( prevState.num );
            return {
                num: prevState.num + 1
            }
        } );
    }
}

合成事件中的setState

合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件
合成事件中也有batchedUpdates方法,是通过同样的事务完成的

class App extends Component {

  state = { val: 0 }

  increment = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新前的val --> 0
  }

  render() {
    return (
      <div onClick={this.increment}>
        {`Counter is: ${this.state.val}`}
      </div>
    )
  }
}

生命周期函数中的setState

整个生命周期中就是一个事物操作,所以标识位isBatchingUpdates = true,所以流程到了enqueueUpdate()时,实例对象都会加入到dirtyComponents 数组中

class App extends Component {

  state = { val: 0 }

 componentDidMount() {
    this.setState({ val: this.state.val + 1 })
   console.log(this.state.val) // 输出的还是更新前的值 --> 0
 }
  render() {
    return (
      <div>
        {`Counter is: ${this.state.val}`}
      </div>
    )
  }
}

其实还是和合成事件一样,componentDidmount 执行的时候,react内部并没有更新,执行完componentDidmount 后才去 commitUpdateQueue 更新。这就导致你在 componentDidmount 中 setState 完去console.log拿的结果还是更新前的值

原生事件中的setState

原生事件是指非react合成事件,原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick这种绑定事件的形式都属于原生事件

原生事件绑定不会通过合成事件的方式处理,自然也不会进入更新事务的处理流程。setTimeout也一样,在setTimeout回调执行时已经完成了原更新组件流程,不会放入dirtyComponent进行异步更新,其结果自然是同步的

class App extends Component {

  state = { val: 0 }

  changeValue = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新后的值 --> 1
  }

 componentDidMount() {
    document.body.addEventListener('click', this.changeValue, false)
 }
 
  render() {
    return (
      <div>
        {`Counter is: ${this.state.val}`}
      </div>
    )
  }
}

批量更新

在 setState 的时候react内部会创建一个 updateQueue ,通过 firstUpdate 、 lastUpdate 、 lastUpdate.next去维护一个更新的队列,在最终的 performWork 中,相同的key会被覆盖,只会对最后一次的 setState 进行更新

class App extends Component {

  state = { val: 0 }

  batchUpdates = () => {
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
 }

  render() {
    return (
      <div onClick={this.batchUpdates}>
        {`Counter is ${this.state.val}`} // 1
      </div>
    )
  }
}

生命周期函数里面可以setState 吗? 什么时候setState 合适?

原因是:组件只挂在一次,在componentWillMount里setState 会但是仅会更新state一次,而且会和 constructor 里的初始化 state 合并执行,因此这是无意义的 setState。

shouldComponentUpdate 或者 componentWillUpdate 里调用 setState 会再次触发这两个函数,然后在两个函数又触发了 setState,然后再次触发这两个函数

https://github.com/hujiulong/blog/issues/7

https://juejin.im/post/5b45c57c51882519790c7441

上一篇 下一篇

猜你喜欢

热点阅读