React - state 与 setState

作者: 果汁凉茶丶 | 来源:发表于2018-06-13 14:12 被阅读119次

      React 中与数据相关的属性有: propsstatecontext。其中,props表示父组件传递给子组件的值, state 表示 组件自身的数据对象。context则表示上下文, 因其不太稳定只是个实验性API,不建议大家使用。

    直接修改 this.state 的值?

    2018-06-21 更新 】 是否直接修改this.state的值还取决你的业务需求,假如你在子组件的状态提升中获得一个值的更新,而你的这个值又在state中的某个对象中,你需要更新这个对象然后再操作如发起axios请求,这种不需要更新UI内容的情况下可以临时修改state中的数据。合理使用state的关键是 state作为一个被React特殊看待的对象,但其实它与普通数据对象无差异,不让直接更新state的原因是在后续执行setState过程中会覆盖或者回退之前只修改state而没有setState的那部分state


      在React中,一个组件中要读取当前状态需要访问 this.state, 而 state 是可以被修改的,因此,大部分初学者都会认为要更新数据直接给 this.state 赋值即可,如下

    let sum= this.state.sum;
    // 错误的累加, 无意义
    this.state.sum = sum + 1;
    

      为什么说是无意义的累加,首先,this.state实质上就是一个对象,它存放的是组件当前状态的数据值,单纯的去修改数据值然而不将这些值的变化反映给页面这是毫无意义的,因此只修改 this.state 并不能实现页面数据的更新。 React的数据更新,不仅仅需要修改当前状态数据值,还需要驱动UI的更新使组件重新渲染,这个过程就是 this.setState 过程

      那是不是做了 this.setState 页面数据就一定会正确更新呢?也未必见得,来看下边这个经典例子:

    function incrementMultiple() {
        this.setState({ count: this.state.count + 1 });
        this.setState({ count: this.state.count + 1 });
        this.setState({ count: this.state.count + 1 });
    }
    

      按 javascript 的编程思路,这段代码的执行结果应该是 this.state.count 增加3,然而实际结果并非如此,只增加了1。

      官网文档有介绍:

    React 为了优化性能,会将多个 setState 的调用合并为一次更新,并且该更新过程是异步的状态更新合并

    数据更新 setState() 执行过程

      React的 setState 的过程,是将被修改的数据值驱动UI更新反映到页面上的过程,它会引起UI的重新绘制,组件的更新,有如下几个声明周期 lifecircle:

    1. shouldComponentUpdate: 组件是否应该被个更新
    2. componentWillUpdate: 组件即将被更新
    3. render: 组件更新重新渲染过程
    4. componentDidUpdate: 组件已经更新完成

      这里提到一点,在生命周期中,以 render 为界,无论是挂在还是更新过程,render 之前,this.stateprops 都是不会发生更新的,直到 render 执行执行完成才得以更新,除非遇到 shouldComponentUpdate 返回 false, 只有这种情况,即使不执行 render 也能得到数据更新

      再回到之前的问题,什么叫状态合并?来看看以下这个例子:

    function refUser() {
        this.setState({ firstName: 'Jane' });
        this.setState({ lastName: 'Chou' });
    }
    // 等价于
    function refUser()  {
        this.setState({firstName: 'Jane', lastName: 'Chou'});
    }
    

    为什么React要做状态合并?
     每一次使用 setState,React都会去调用一次Update生命周期,即上方提到的四个过程,这难免导致不必要的时间和空间浪费,我们知道React的重新渲染过程中,将前后两次的虚拟DOM做比较,并将最终变化数据更新渲染到DOM上,这个过程如果频繁产生,那就会失去React虚拟DOM的优势,状态合并巧妙的规避了这一点。

      或许你会问,上例中的状态合并没有异议,那连续增加三次 count 为什么 this.state.count 只增加了1呢?我们来看看 setState 步骤:


      如果存在多个 setState 方法,即批量模式下,需要更新state的组件会被push到dirtyComponents中,再执行更新。

      我们将前面的代码案例稍微变化如下:

    class Plus extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                val: 0
            }
        }
    
        componentDidMount() {
            this.setState({ val: this.state.val + 1 });
            console.log(this.state.val);    //  返回 0
    
            this.setState({ val: this.state.val + 1 });
            console.log(this.state.val);    // 返回 0
    
            setTimeout(() => {
                this.setState({ val: this.state.val + 1 });
                console.log(this.state.val);    // 返回 2
    
                this.setState({ val: this.state.val + 1 });
                console.log(this.state.val);     // 返回 3
            }, 0)
        }
    
        render() {
            return null;
        }
    }
    

      分析一下这个过程:

    1. this.setState首先会把state推入pendingState队列中
    2. 然后将组件标记为dirty
    3. React中有事务的概念,最常见的就是更新事务,如果不在事务中,则会开启一次新的更新事务,更新事务执行的操作就是把组件标记为dirty
    4. 判断是否处于batch update
    5. 是的话,保存组建于dirtyComponent中,在事务结束的时候才会通过 ReactUpdates.flushBatchedUpdates 方法将所有的临时 state merge 并计算出最新的 propsstate,然后将其批量执行,最后再关闭结束事务。
    6. 不是的话,直接开启一次新的更新事务,在标记为dirty之后,直接开始更新组件。因此当setState执行完毕后,组件就更新完毕了,所以会造成定时器同步更新的情况。

    防止循环调用

      部分react新手可能和我遇到过相同的问题,即在设置开发过程中突然发现浏览器非常容易卡死,仔细一个控制台才知道有个接口一直在不停的发起递归调用直至电脑内存不足
      生命周期中,updateComponent 方法会调用 shouldComponentUpdate
    componentWillUpdate 方法。因此,不要在 shouldComponentUpdate
    componentWillUpdate 中调用 setState 。如果在这两个生命周期里调用 setState ,会造成造成循环调用。

    相关文章

      网友评论

        本文标题:React - state 与 setState

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