为什么需要setState方法
首先我们需要知道,React不同于Vue,他并没有实现Vue的响应式原理,我们需要手动的调用setState方法修改数据通知React进行更新界面
setState方法数据的合并
首先需要知道,setState在执行的时候,并不是覆盖原来的state,而是合并,看下面例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { name:'coderwei', message: "13", }; } change() { this.setState({ message: "hello", }); } render() { return ( <div> <h2>{this.state.message}</h2> <h2>{this.state.name}</h2> <button onClick={this.change.bind(this)}>改变state</button> </div> ); } }
|
setState方法是同步的还是异步的
setState并没有绝对的同步或者是异步,他在不同的场景下表现是不同的,首先先看个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { message: "13", }; } change() { this.setState({ message: "hello", }); console.log(this.state.message) } render() { return ( <div> <h2>{this.state.message}</h2> <button onClick={this.change.bind(this)}>改变state</button> </div> ); } }
|
上述场景就可以证明setState是一个异步方法,明明我们在输出之前就已经修改了message的代码,但是输出的依旧是修改之前的值
为什么setState是异步的
明确他是异步的之后,我们就需要知道为什么?了解真相才能获得真正的自由,首先贴一个链接,redux作者 Dan Abramov对这个方法是同步还是异步的回应,大概在四分之一的位置。
- 首先,如果setState方法不是异步的,会导致一个很明显的问题,那就是render函数会频繁的会调用,这样子效率是非常低下的,所以react内部将多个setState维护到一个队列中,然后一起进行更新,在其中如果key相同的则会进行覆盖,这样就大大的提升了性能。
- 其次,如果setState是同步的,那么会出现一个问题,也是上面redux作者回应举的一个例子,当setState执行完毕之后,可能App组件的render函数还没有调用完,如果里面用到了其他组件并且其他组件依赖message,那么就会导致state和props的数据不一致,这在开发中可能会导致出现很多问题
在setState异步的情况下,如何获取跟新后的数据
如果我们需要获取setState更新后的数据,那么setState的第二个参数就派上用场了,setState第二个参数是一个回调函数,当setState执行完毕后会执行这个回调,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { message: "13", }; } change() { this.setState( { message: "hello", }, () => { console.log("state的回调:", this.state.message); } ); } render() { return ( <div> <h2>{this.state.message}</h2> <button onClick={this.change.bind(this)}>改变state</button> </div> ); } }
|
同步的setState
当我们将setState丢在一个定时器内部,这个时候setState就会变成一段同步代码,因为当代码块执行到setTimeout的时候,并没有去执行,而是将他丢到一个任务队列里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { message: "13", }; } change() { setTimeout(() => { this.setState( { message: "hello", }, () => { console.log("state的回调:", this.state.message); } ); console.log("方法里面的输出", this.state.message); }, 0); } render() { return ( <div> <h2>{this.state.message}</h2> <button onClick={this.change.bind(this)}>改变state</button> </div> ); } }
|
不建议直接修改state的数据
在react里,不建议直接修改state的值,官方称 不可变的力量,如果我们书写代码都是继承自Component,那么其实修改也没有任何问题,不建议修改state的值,是为了考虑到性能优化,我们在父组件修改了一个属性,父组件重新渲染,也会导致子组件的内容重新渲染,这并不是我们希望看到的,你父组件修改个属性,跟我子组件有什么关系,我又不依赖你修改的这个属性,我是保持不变的,重新渲染就太浪费性能了,于是shouldComponentUpdate这个生命周期就派上用场了
shouldComponentUpdate是控制组件是否执行render函数的,他默认返回true,就是无论什么情况下都执行render函数,于是我们就可以在这上面下手,我们可以判断,我子组件的props 是否和父组件的state保持一致,保持一致的话就代表数据没有发生变化,我就不需要执行render刷新界面。shouldComponentUpdate在实际开发中用的还是蛮频繁的。文字总是苍白无力的,还是用代码来表述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { arr: [1, 2, 3, 4, 5], }; } change = () => { this.state.arr.push(6); this.setState({ arr: this.state.arr, }); }; shouldComponentUpdate(newProps, newState) { console.log(newState); console.log(this.state.arr); return false; } render() { return ( <div> {this.state.arr.map(item => { return <li key={item}>{item}</li>; })} <button onClick={this.change}>=1</button> </div> ); } }
|
每次点击按钮,最新的state和当前组件实例对象的state是一模一样的,因为javascript中复杂数据类型保存的是内存地址,也就是说他们保存的都是一个对象的引用,最后他们指向的都是同一个对象,于是他们就永远保持一致,这样就没办法判断state有没有发生变化,因为我们需要通过state的变化来判断界面需不需要重新渲染。
所以上面的代码我们稍微修改一下,不去直接修改state的值,而是创建一个新的arr数组,然后赋值给arr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import React, { Component } from "react";
export default class App extends Component { constructor(props) { super(props); this.state = { arr: [1, 2, 3, 4, 5], }; } change = () => { let newArr = [...this.state.arr, 6]; this.setState({ arr: newArr, }); }; shouldComponentUpdate(newProps, newState) { console.log( newState); console.log(this.state.arr); return false; } render() { return ( <div> {this.state.arr.map(item => { return <li key={item}>{item}</li>; })} <button onClick={this.change}>=1</button> </div> ); } }
|
我们就可以在shouldComponentUpdate中判断,如果新的state和当前对象的state保持一致,那我们就不需要执行render函数渲染界面,这样就可以极大程度上避免毫无意义的render函数的执行