React State(状态): React通过this.sta

作者: 光剑书架上的书 | 来源:发表于2017-11-23 00:47 被阅读57次

    React State(状态)

    React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
    React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
    以下实例中创建了 LikeButton 组件,getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>菜鸟教程 React 实例</title>
        <script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
        <script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
        <script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
      </head>
      <body>
        <div id="example"></div>
        <script type="text/babel">
          var LikeButton = React.createClass({
            getInitialState: function() {
              return {liked: false};
            },
            handleClick: function(event) {
              this.setState({liked: !this.state.liked});
            },
            render: function() {
              var text = this.state.liked ? '喜欢' : '不喜欢';
              return (
                <p onClick={this.handleClick}>
                  你<b>{text}</b>我。点我切换状态。
                </p>
              );
            }
          });
    
          ReactDOM.render(
            <LikeButton />,
            document.getElementById('example')
          );
        </script>
      </body>
    </html>
    

    我们都知道,React通过this.state来访问state,通过this.setState()方法来更新state。当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UI

    setState异步更新

    setState方法通过一个队列机制实现state更新,当执行setState的时候,会将需要更新的state合并之后放入状态队列,而不会立即更新this.state(可以和浏览器的事件队列类比)。如果我们不使用setState而是使用this.state.key来修改,将不会触发组件的re-render。如果将this.state赋值给一个新的对象引用,那么其他不在对象上的state将不会被放入状态队列中,当下次调用setState并对状态队列进行合并时,直接造成了state丢失。(这里特别感谢@Dcatfly的指正)

    我们来看一下React文档中对setState的说明

        void setState(
          function|object nextState,
          [function callback]
        )
    

    The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.

    翻译一下,第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行。也就是说,我们可以通过这个回调来拿到更新的state的值。

    React也正是利用状态队列机制实现了setState的异步更新,避免频繁地重复更新state(pending的意思是未定的,即将发生的)

       //将新的state合并到状态更新队列中
       var nextState =  this._processPendingState(nextProps, nextContext);
       //根据更新队列和shouldComponent的状态来判断是否需要更新组件
       var shouldUpdate = 
          this._pendingForceUpdate ||
          !inst.shouldComponentUpdate ||
          inst.shouldComponentUpdate(nextProps, nextState, nextContext);
    

    setState循环调用风险

    当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pending-StateQueue更新队列进行合并操作,最终通过enqueueUpdate执行state更新

    而performUpdateIfNecessary方法会获取pendingElement, pendingStateQueue,_ pending-ForceUpdate,并调用receiveComponent和updateComponent方法进行组件更新

    如果在shouldComponentUpdate或者componentWillUpdate方法中调用setState,此时this._pending-StateQueue != null,就会造成循环调用,使得浏览器内存占满后崩溃

    调用栈

    既然setState最终是通过enqueueUpdate执行state更新,那么enqueueUpdate到底是如何更新state的呢? 首先看下面的问题

       import React, { Component } from 'react';
       class Example extends Component {
           constructor(){
               super();
               //在组件初始化可以直接操作this.state
               this.state = {
                   val: 0
               }
           }
           componentDidMount(){
               this.setState({
                  val: this.state.val + 1
               });
               //第一次输出
               console.log(this.state.val);
               this.setState({
                  val: this.state.val + 1
               });
               //第二次输出
               console.log(this.state.val);
               setTimeout(()=>{
                  this.setState({val: this.state.val + 1});
                   //第三次输出
                   console.log(this.state.val);
                   this.setState({
                      val: this.state.val + 1
                   });
                   //第四次输出
                   console.log(this.state.val);
               }, 0);  
           }
           render(){
               return null;
           }
       }
    

    上述代码中,4次console.log打印出来的val分别是: 0,0,2 ,3

    我们来看一个简化的setState的调用栈

       this.setState(newState) =>
       newState存入pending队列 =>
       调用enqueueUpdate =>
       是否处于批量更新模式 =>
       是的话将组件保存到dirtyComponents
       不是的话遍历dirtyComponents,调用updateComponent,更新pending state or props
    

    enqueueUpdate的源码如下(上面流程的第三步)(batching的意思是批量的)

       function enqueueUpdate(component){
           //injected注入的
           ensureInjected();
           //如果不处于批量更新模式
           if(!batchingStrategy.isBatchingUpdates){
               batchingStrategy.batchedUpdates(enqueueUpdate, component);
               return;
           }
           //如果处于批量更新模式
           dirtyComponents.push(component);
       }
    

    如果isBatchingUpdates为true,则对所有队列中的更新执行batchedUpdates方法,否则只把当前组件(即调用了setState的组件)放入dirtyComponents数组中,例子中4次setState调用的表现之所以不同,这里的逻辑判断起了关键作用

    事务

    事务就是将需要执行的方法使用wrapper封装起来,再通过事务提供的perform方法执行,先执行wrapper中的initialize方法,执行完perform之后,在执行所有的close方法,一组initialize及close方法称为一个wrapper。

    那么事务和setState方法的不同表现有什么关系,首先我们把4次setState简单归类,前两次属于一类,因为它们在同一调用栈中执行,setTimeout中的两次setState属于另一类。

    在setState调用之前,已经处在batchedUpdates执行的事务中了。那么这次batchedUpdates方法是谁调用的呢,原来是ReactMount.js中的_renderNewRootComponent方法。也就是说,整个将React组件渲染到DOM中的过程就是处于一个大的事务中。而在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设为了true,所以两次setState的结果没有立即生效。

    再反观setTimeout中的两次setState,因为没有前置的batchedUpdates调用,所以导致了新的state马上生效。

    相关文章

      网友评论

        本文标题:React State(状态): React通过this.sta

        本文链接:https://www.haomeiwen.com/subject/ifeavxtx.html