资讯专栏INFORMATION COLUMN

深入React技术栈之setState详解

leeon / 2440人阅读

摘要:除此之外指的是绕过通过直接添加的事件处理函数和产生的异步调用。但是,当在调用事件处理函数之前就会调用,这个函数会把修改为,造成的后果就是由控制的事件处理过程不会同步更新。

抛出问题
class Example extends Component {
  contructor () {
    super()
    this.state = {
      value: 0,
      index: 0
    }
  }

  componentDidMount () {
    this.setState({value: this.state.value + 1})
    console.log(this.state.value) // 第一次输出
    this.setState({value: this.state.value + 1})
    console.log(this.state.value) // 第二次输出
    setTimeout(() => {
      this.setState({value: this.state.value + 1})
      console.log(this.state.value) // 第三次输出
      this.setState({value: this.state.value + 1})
      console.log(this.state.value) // 第四次输出
    }, 0);
        this.refs.button.addEventListener("click", this.click)
  }

  click = () => {
    this.setState({value: this.state.index + 1})
    this.setState({value: this.state.index + 1})
  }

  render () {
    return (
      
value: {this.state.value}index: {this.props.index}
) } }

这四次输出,按常理来说分别是: 1,2,3,4。但是,实际输出为: 0, 0, 2, 3

setState的注意点

setState不会立刻改变React组件中state的值(即setState是异步更新)

setState通过一个队列机制实现state更新;

当执行setState时,会将需要更新的state合并后放入状态队列,而不会立即更新,队列可以高效的批量更新state;

通过this.state直接修改的值,state不会放入状态队列,当下次调用setState并对状态队列进行合并时,会忽略之前直接被修改的state.

setState通过引发一次组件的更新过程来引发重新绘制

此处重绘指的就是引起React的更新生命周期函数4个函数:

shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)

componentWillUpdate(被调用时this.state没有更新)

render(被调用时this.state得到更新)

componentDidUpdate

多个相邻的state的修改可能会合并到一起一次执行

 this.setState({name: "Pororo"})
 this.setState({age: 20})

等同于

 this.setState({name: "Pororo",age: 20})

上面两块代码的效果是一样的。如果每次调用都引发一次生命周期更新,那性能就会消耗很大了。所以,React会将多个this.setState产生的修改放进一个队列里,等差不多的时候就会引发一次生命周期更新。

问题分析

对于前两次setState:

this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第一次输出
this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第二次输出

由于setState不会立即改变React组件中state的值,所以两次setState中this.state.value都是同一个值0,故而,这两次输出都是0。因而value只被加1。

既然这样,那么是不是可以直接操作this.state呢?比如:this.state.value=this.state.value+1;

这样的确可以修改this.state.value的状态但是却不可以引发重复渲染。

所以,就必须通过React设定的setState函数去改变this.state,从而引发重新渲染。

setTimeout里面的两次setState:

setTimeout(() => {
  this.setState({value: this.state.value + 1})
  console.log(this.state.value) // 第三次输出
  this.setState({value: this.state.value + 1})
  console.log(this.state.value) // 第四次输出
}, 0);

这两次this.state的值同步更新了;

同步更新:是由React引发的事件处理(比如:onClick引发的事件处理),调用setState会异步更新this.state;

异步更新:除此之外的setState调用会同步执行this.setState。 “除此之外”指的是:绕过React通过addEventListener直接添加的事件处理函数和setTimeout/setInterval产生的异步调用。

this.setState更新机制图解:

每次setState产生新的state会依次被存入一个队列,然后会根据isBathingUpdates变量判断是直接更新this.state还是放进dirtyComponent里回头再说。

isBatchingUpdates默认是false,也就表示setState会同步更新this.state。

但是,当React在调用事件处理函数之前就会调用batchedUpdates,这个函数会把isBatchingUpdates修改为true,造成的后果就是由React控制的事件处理过程setState不会同步更新this.state。

同步更新(函数式setState)

如果this.setState的参数不是一个对象而是一个函数时,这个函数会接收到两个参数,第一个是当前的state值,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改;

换句话说,之前你想给this.setState传递什么对象参数,在这种函数里就返回什么对象。不过,计算这个对象的方法有些改变,不再依赖于this.state,而是依赖于输入参数state。

function increment(state, props) {
  return {count: state.count + 1};
}

function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState(increment);
}

假如当前this.state.count的值是0,第一次调用this.setState(increment),传给increment的state参数是0,第二调用时,state参数是1,第三次调用是,参数是2,最终incrementMultiple让this.state.count变成了3。

对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。

要注意的是,在increment函数被调用时,this.state并没有被改变,依然,要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变。
同步异步setState的用法混合
function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState({count: this.state.count + 1});
  this.setState(increment);
}

在几个函数式setState调用中插入一个传统式setState调用,最后得到的结果是让this.state.count增加了2,而不是增加4。

这是因为React会依次合并所有setState产生的效果,虽然前两个函数式setState调用产生的效果是count加2,但是中间出现一个传统式setState调用,一下子强行把积攒的效果清空,用count加1取代。

所以,传统式setState与函数式setState一定不要混用。

总结自:掘金(不洗碗工作室)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95186.html

相关文章

  • 深入React技术栈之setState详解

    摘要:除此之外指的是绕过通过直接添加的事件处理函数和产生的异步调用。但是,当在调用事件处理函数之前就会调用,这个函数会把修改为,造成的后果就是由控制的事件处理过程不会同步更新。 抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, ...

    TNFE 评论0 收藏0
  • 2017-07-17 前端日报

    摘要:前端日报精选听说你没来总结个人使用过的移动端布局方法新特性简介用写组件坦然面对应对前端疲劳中文深入理解笔记函数前端架构经验分享系列教程之创建页面元素龙云全栈系列教程之定位页面元素龙云全栈第期与表单验证技术周刊期知乎 2017-07-17 前端日报 精选 听说你没来 JSConf 2017?总结个人使用过的移动端布局方法 - Rni-L - SegmentFaultNode.js v8....

    caiyongji 评论0 收藏0
  • 2017-07-01 前端日报

    摘要:前端日报精选腾讯前端团队社区源码分析入门指南一些关于使用的心得基本类型与引用类型知多少掘金中文第期框架选型周刊第期入门系列模块车栈重构基于的网络请求库某熊的全栈之路的那些奇技淫巧的平凡之路模仿写个数组监听掘 2017-07-01 前端日报 精选 Why you shouldn`t use Preact, Fast-React, etc. to replace React today -...

    _DangJin 评论0 收藏0
  • Flutter 面试知识点集锦

    摘要:中的的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是内部队列,一个是外部队列,而的优先级又高于。同时还有处理按住时的事件额外处理,同时手势处理一般在的子类进行。谷歌大会之后,有不少人咨询了我 Flutter 相关的问题,其中有不少是和面试相关的,如今一些招聘上也开始罗列 Flutter 相关要求,最后想了想还是写一期总结吧,也算是 Flutter 的阶段复习。 ⚠️系统完...

    andong777 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<