详解setState 方法

文章目录

为什么要是用 setStatesetState 异步更新为什么要异步更新如何获取更新后的值同步 or 异步

setState的合并数据的合并多个setState合并

setState性能优化react 更新机制diff算法的作用传统 diff算法react diff算法对比不同类型的元素对比同一类型的元素对子节点进行递归

keys的优化在最后位置插入数据在前面插入数据

为什么要是用 setState

当我们希望通过点击一个按钮从而改变一个文本时,就需要使用到 setState方法,比如:

import React, { Component } from 'react'

export default class App extends Component {

constructor(props) {

super(props);

this.state = {

message: "Hello World"

}

}

render() {

return (

{this.state.message}

)

}

changeText() {

this.setState({

message: "文本已经被改变"

})

}

}

当我们调用setState时,会重新执行render函数,根据最新的State来创建ReactElement对象;然后再根据最新的ReactElement对象,对DOM进行修改

另外,setState方法是从Component中继承过来的

setState 异步更新

changeText() {

this.setState({

message: "文本已经被改变"

})

console.log(this.state.message); // Hello World

}

上面代码中再使用 setState 方法后,再打印 message,发现 message 没有被改变,由此可以看到 setState 是异步操作,在执行完setState之后不能够立刻拿到最新的state的结果

为什么要异步更新

传送门

设计成异步,可以显著提升性能,如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的,所以最好的办法是获取到多个更新,然后进行批量的更新如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步,这样做会引起很多问题

如何获取更新后的值

1、setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行

changeText() {

this.setState({

message: "文本已经被改变"

}, () => {

console.log(this.state.message); // 文本已经被改变

});

}

2、通过生命周期函数 componentDidUpdate

componentDidUpdate(prevProps, provState, snapshot) {

console.log(this.state.message);

}

同步 or 异步

setState 方法到底是同步还是异步分两种情况

在组件生命周期或React合成事件中,setState是异步

在setTimeout或者原生dom事件中,setState是同步

// setTimeout

changeText() {

setTimeout(() => {

this.setState({

message: "文本已经被改变"

});

console.log(this.state.message); // 文本已经被改变

}, 0);

}

// 原生DOM事件

componentDidMount() {

const btnEl = document.getElementById("btn");

btnEl.addEventListener('click', () => {

this.setState({

message: "文本已经被改变"

});

console.log(this.state.message); // 文本已经被改变

})

}

setState的合并

数据的合并

this.state = {

name: '小冯',

age: 19

}

// 通过setState去修改age,是不会对name产生影响的

this.setState({

age: 18

})

多个setState合并

this.state = {

counter: 0

}

increment() {

this.setState({

counter: this.state.counter + 1

});

this.setState({

counter: this.state.counter + 1

});

this.setState({

counter: this.state.counter + 1

});

}

setState 方法执行了3次,但是 counter 的值还是为1,这就是多个state进行合并

如果想要变为3,如何去做:传入一个函数

increment() {

this.setState((state, props) => {

return {

counter: state.counter + 1

}

})

this.setState((state, props) => {

return {

counter: state.counter + 1

}

})

this.setState((state, props) => {

return {

counter: state.counter + 1

}

})

}

setState性能优化

react 更新机制

react 渲染流程

react 更新流程

React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。

React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI:

如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数量;https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf;如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围;这个开销太过昂贵了,React的更新性能会变得非常低效;

于是,React对这个算法进行了优化,将其优化成了O(n),如何优化的呢?

同层节点之间相互比较,不会跨层级进行节点的比较。不同类型的节点,产生不同的树结构;开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

diff算法的作用

计算出虚拟 dom中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面

传统 diff算法

通过循环递归节点进行依次比较,复杂度为O(n^3) ,n为是树中节点(元素)的数量

如果在 React 中使用了该算法,那么展示 1000 个元素就需要进行上亿次比较,太浪费性能

传送门

react diff算法

同层节点之间相互比较,不会跨节点比较;不同类型的节点,产生不同的树结构;开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

对比不同类型的元素

当节点为不同的元素,React会拆卸原有的树,并且建立起新的树:

当一个元素从 变成 ,从

变成 ,或从

)

}

insertMovie() {

}

}

在最后位置插入数据

这种情况,有无key意义并不大

insertMovie() {

const newMovies = [...this.state.movies, "大话西游"];

this.setState({

movies: newMovies

})

}

在前面插入数据

这种做法,在没有key的情况下,所有的li都需要进行修改;

insertMovie() {

const newMovies = ["大话西游", ...this.state.movies];

this.setState({

movies: newMovies

})

}

当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素:

在下面这种场景下,key为111和222的元素仅仅进行位移,不需要进行任何的修改;将key为333的元素插入到最前面的位置即可;

  • 星际穿越
  • 盗梦空间

  • Connecticut
  • 星际穿越
  • 盗梦空间

key的注意事项:

key应该是唯一的;key不要使用随机数(随机数在下一次render时,会重新生成一个数字);使用index作为key,对性能是没有优化的;