react之setState解析

在react开发过程中,state是组件一个重要的属性,对state的管理也尤为重要,这里记录一下踩坑经历

修改state正确方式

对于修改组件state正确的方式如下:

// 错误
this.state.title = 'React';

// 正确
this.setState({title: 'React'});

有两个比较重要的点,一是对state的修改是异步,二是对多个相邻的state的修改可能会合并到一起一次执行。代码举例:

异步执行

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = { num: 1 };
  }

  handleClick() {
    this.setState((prevState, props) => ({
      num: prevState.num + 1
    }));
    console.info(this.state.num);
  }

  render() {
    return (
      <div>
        <h1 onClick={this.handleClick.bind(this)}>Hello, world!</h1>
        <h2>It is {this.state.num}.</h2>
      </div>
    );
  }
}

ReactDOM.render(<Clock />, document.getElementById("root"));

结果在ui显示每一次加1之后的结果,而console打印的总是上一次ui的值,也就可以确定state并没有立即执行,而是异步去执行的。我们可以通过添加一个回调函数到第二个参数,让state更新完成之后来执行该回调,代码如下:

handleClick() {
    this.setState({
      num : this.state.num + 1
    });
  }

此时ui上显示的数值与console打印的会是一致。另一种情况,如果对state的修改依赖state和prop,那么由于异步的原因就会产生很多异常情况,我们可以可以传入包含两个参数的函数到第一个参数来解决,代码如下:

handleClick() {
    this.setState((prevState, props) => ({
      num: prevState.num + 1
    }),() => console.info(this.state.num));

  }

第一个参数prevState表示前一次的state,后一个参数表示当前状态组件的prop值,显示的让该操作同步执行

批量单次执行

对于多次相邻的state修改操作的执行会被合并在一起执行,代码如下:

handleClick() {
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
  }

最终数值每次只会加1,我们可以看作是以下代码的执行:

Object.assign(
  previousState,
  {num : this.state.num + 1},
  {num : this.state.num + 1}
)

因此,被合并之后最终只会保留一个更新。解决方法和上述解决异步的方式类似:

handleClick() {
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
  }

结果是我们期望的每次加3,但是需要注意的是,console打印的只会是每次加3之后的数值,并不会是每次加1的结果

State与Immutable

React官方建议把State当作是不可变对象,一方面是如果直接修改this.state,组件并不会重新render;另一方面State中包含的所有状态都应该是不可变对象。当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。那么,当状态发生变化时,如何创建新的状态呢?根据状态的类型,可以分成三种情况:

  1. 状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)
    这种情况最简单,因为状态是不可变类型,直接给要修改的状态赋一个新值即可。如要修改count(数字类型)、title(字符串类型)、success(布尔类型)三个状态:

    this.setState({
    count: 1,
    title: 'Redux',
    success: true
    })
  2. 状态的类型是数组
    如有一个数组类型的状态books,当向books中增加一本书时,使用数组的concat方法或ES6的数组扩展语法(spread syntax):

    // 方法一:将state先赋值给另外的变量,然后使用concat创建新数组
    var books = this.state.books; 
    this.setState({
    books: books.concat(['React Guide']);
    })
    // 方法二:使用preState、concat创建新数组
    this.setState(preState => ({
    books: preState.books.concat(['React Guide']);
    }))
    // 方法三:ES6 spread syntax
    this.setState(preState => ({
    books: [...preState.books, 'React Guide'];
    }))

    当从books中截取部分元素作为新状态时,使用数组的slice方法:

    // 方法一:将state先赋值给另外的变量,然后使用slice创建新数组
    var books = this.state.books; 
    this.setState({
    books: books.slice(1,3);
    })
    // 方法二:使用preState、slice创建新数组
    this.setState(preState => ({
    books: preState.books.slice(1,3);
    }))

    注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。

  3. 状态的类型是普通对象(不包含字符串、数组)
    3.1 使用ES6 的Object.assgin方法

    // 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象
    var owner = this.state.owner;
    this.setState({
    owner: Object.assign({}, owner, {name: 'Jason'});
    })
    // 方法二:使用preState、Object.assign创建新对象
    this.setState(preState => ({
    owner: Object.assign({}, preState.owner, {name: 'Jason'});
    }))

    3.2 使用对象扩展语法

    // 方法一:将state先赋值给另外的变量,然后使用对象扩展语法创建新对象
    var owner = this.state.owner;
    this.setState({
    owner: {...owner, name: 'Jason'};
    })
    // 方法二:使用preState、对象扩展语法创建新对象
    this.setState(preState => ({
    owner: {...preState.owner, name: 'Jason'};
    }))

参考:
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/state-and-lifecycle.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值