React的setState方法

React的setState方法,可以接收两个参数,一个是对象/函数【必须】,一个是回调方法callback【非必须】

这是setState部分的源码

    Component.prototype.setState = function (partialState, callback) {
      (function () {
        if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
          {
            throw ReactError(Error('setState(...): takes an object of state variables to update or a function which returns an object of state variables.'));
          }
        }
      })();

      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };

按照官方文档的说法,setState有三点需要注意:

一、不要直接修改 State

直接修改state并不会重新渲染组件,我们来可以做个试验

import React from 'react';
export default class C1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 1
    };
  }
  handle() {
    this.state.counter = 2;
  }
  componentDidUpdate() {
    console.log('C1组件更新完毕 componentDidUpdate');
  }
  render() {
    console.log('C1子组件render');
    return (
      <div>
        <p>C1组件</p>
        {this.state.counter}
        <button
          type="button"
          onClick={() => {
            this.handle();
          }}
        >
          更新界面
        </button>
      </div>
    );
  }
}

结果肯定是没有效果的,非但没有效果,在保存的时候还会提示需要用setState

二、State 的更新会被合并

这个也很简单,setState方法只会更新方法的参数对象里面相应的键值,功能类似ES6的Object.assign静态方法

const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const returnedTarget = Object.assign(target, source);
console.log(target);// { a: 1, b: 3, c: 4 }
console.log(returnedTarget);// { a: 1, b: 3, c: 4 }

三、State 的更新可能是异步的

这句话什么意思呢?我琢磨了很久~

出于性能考虑,React 可能会把多个 setState()调用合并成一个调用。

例子

import React from 'react';
export default class C1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 1
    };
  }
  handle() {
    this.setState({
      counter: this.state.counter + 1
    });
    this.setState({
      counter: this.state.counter + 2
    });
  }
  componentDidUpdate() {
    console.log('C1组件更新完毕 componentDidUpdate');
  }
  render() {
    console.log('C1子组件render');
    return (
      <div>
        <p>C1组件</p>
        {this.state.counter}
        <button
          type="button"
          onClick={() => {
            this.handle();
          }}
        >
          更新界面
        </button>
      </div>
    );
  }
}

点击更新界面按钮之后,counter在界面的显示还是3,这是为什么?原因就是上面那句话,出于性能考虑,React 可能会把多个 setState()调用合并成一个调用。

我们都知道,渲染DOM会消耗性能,所以React为了提高整体的性能,在同一个渲染周期内,对setState进行合并,因此在上面这个例子中,react只认准了当前这次渲染周期的最后一个setState,所以1+2等于3。

那什么时候react不会合并同一个渲染周期里面的setState呢?这就涉及到setState的另一种用法:

import React from 'react';
export default class C1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 1
    };
  }
  handle() {
    this.setState(state => ({
      counter: state.counter + 1
    }));
    this.setState(state => ({
      counter: state.counter + 1
    }));
  }
  componentDidUpdate() {
    console.log('C1组件更新完毕 componentDidUpdate');
  }
  render() {
    return (
      <div>
        <p>C1组件</p>
        {this.state.counter}
        <button
          type="button"
          onClick={() => {
            this.handle();
          }}
        >
          更新界面
        </button>
      </div>
    );
  }
}

点击更新界面按钮之后counter渲染为3,setState也可以传入一个函数作为第一个参数

this.setState((state,props,context)=>({
    xx:state.xx+?
}))

函数里面传入了两个参数,即上一次更新的state和当前的props,这样在第二次调用setState方法时便可以通过state.counter拿到最新的值从而更新本次的state.counter。

所以通过state参数对属性进行修改,就不会被合并处理,在函数里面如果还用this.state方式进行操作的话,还是会被合并处理,因为此时的this.state里面的值还不是最新的。为什么这么说?我们可以通过一个例子来验证一下。

import React from 'react';
export default class C1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 1
    };
  }
  handle() {
    // this.setState(state => ({
    //   counter: state.counter + 1
    // }));
    this.setState({
      counter: this.state.counter + 1
    });
    console.log('handle执行时的counter',this.state.counter);
  }
  componentDidUpdate() {
    console.log('C1组件更新完毕 componentDidUpdate');
    console.log('更新完毕的counter',this.state.counter);
  }
  render() {
    console.log('render的counter',this.state.counter);
    return (
      <div>
        <p>C1组件</p>
        {this.state.counter}
        <button
          type="button"
          onClick={() => {
            this.handle();
          }}
        >
          更新界面
        </button>
      </div>
    );
  }
}

点击更新界面之后

我们可以看到初始的counter为1,在handle的时候this.state.counter在setState之后,state并没有合并到this对象上面去,所以值并没有发生改变,直到render执行之后,渲染到页面之后,才发生改变。当然,如果更改为上面那段注释的代码进行赋值操作的话,也会出现这种情况。

我们在源码中可以找到这段注释

* When a function is provided to setState, it will be called at some point in
* the future (not synchronously). It will be called with the up to date
* component arguments (state, props, context). These values can be different
* from this.* because your function may be called after receiveProps but before
* shouldComponentUpdate, and this new state, props, and context will not yet be
* assigned to this.

这段注释,也从另一角度解释了一开始出现的,通过this.state.counter的方式修改counter无论怎样都只认最后一个setState的问题。

发布了32 篇原创文章 · 获赞 22 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_34551390/article/details/104313759