前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React-setState函数必须掌握的pendingState状态

React-setState函数必须掌握的pendingState状态

作者头像
19组清风
发布2021-11-15 14:46:07
1.2K0
发布2021-11-15 14:46:07
举报
文章被收录于专栏:Web Front End

setState

背景介绍

最近开始从vue转向react,当然是从最基础(chun)开始一步一步踏实学起。

当使用到setState这个Api碰到了一点有意思的疑惑,顺手记录下来。

查询对应源码内容觉得比较难以理解所以在下方以一个简单Demo记录下setState不同状态下对应实现原理。

记录问题

异步更新原则

当然我们都清楚setState函数是react将对组件的state更改排入队列进行批量更新。注意这里的批量,我们来看一段伪代码:

代码语言:javascript
复制
export default class DemoPage extends React.Component {
  public state = {
    name: 1,
  };

  // ES7方式 修改组件this 对比vue太恶心了.. 如果是传参只能使用箭头函数的方式了
  private onBtnClick = () => {
    this.setState({
      name: this.state.name + 2,
    });
    this.setState({
      name: this.state.name + 1,
    });
  };

  render() {
    return (
      <React.Fragment>
        <div>{this.state.name}</div>;
        <button onClick={this.onBtnClick}>更新BTN值</button>
      </React.Fragment>
    );
  }
}
复制代码

当我点击页面上的按钮元素时候。

此时页面上展示this.state.name内容为2,并不是所期待的4。这很好理解,批量更新原则嘛,(react内部会对state的值进行缓存最终合并一次性更新)乍一看和Vue大同小异。

此时我们来看看另一种写法。

使用setTimeout处理

代码语言:javascript
复制
export default class DemoPage extends React.Component {
  public state = {
    name: 1,
  };

  // ES7方式 修改组件this 对比vue太恶心了.. 如果是传参只能使用箭头函数的方式了
  private onBtnClick = () => {
    setTimeout(() => {
      this.setState({
        name: this.state.name + 2,
      });
      this.setState({
        name: this.state.name + 1,
      });
    })
  };

  render() {
    return (
      <React.Fragment>
        <div>{this.state.name}</div>;
        <button onClick={this.onBtnClick}>更新BTN值</button>
      </React.Fragment>
    );
  }
}
复制代码

此时我们使用setTimeout将两次setState包裹起来,嗯。按照vue中的理解,期待的结果应该还是2

当我天真(zu gou cai)的以为页面上会打出2的时候,发现页面呈现结果是4!!

what! 怎么会这样,按照我的理解,不是说好了批量更新策略,即使在setTimeout之后,下一个队列中应该也是批量呀。这是什么操作,为什么会这样。不行我要翻出来看看!

原理解析

在一通源码(bai du)查阅下,终于搞懂了是个什么东西。为什么会这样。。

我们来看看这段伪代码,非常精简的react关于setState的解析,当然再高深了我也不会,我也写不出来。

代码语言:javascript
复制
// 为了方便阅读 我将相关方法都简化在了这个文件中
let isBatchingUpdate = true; // 默认页面未渲染过,react批量异步更新
function transcation(component) {
  // 同步缓存状态
  component.state = component.pendingState;
  // 渲染页面
  component.render();
  // 关闭批量更新内容 渲染一次之后 关于state的操作全都是非批量更新
  isBatchingUpdate = false;
}

class MyComponent {
  constructor() {
    this.state = { number: 0 }; // 组件自己拥有的state
    this.pendingState = { ...this.state }; // react内部会缓存一份state实例对象
  }
  // 自己实现这个setState方法
  setState(obj) {
    // 更新缓存的事例对象
    this.pendingState = { ...this.pendingState, ...obj };
    if (!isBatchingUpdate) {
      console.log('进入')
      // 如果页面已经更新过了 那么isBatchingUpdate为false
      // 此时就执行非批量更新 每次修改state都会进行更新state的值
      transcation(this);
    }
  }
  // 模拟页面更新方法
  update() {
    setTimeout(() => {
      this.setState({ number: this.state.number + 1 });
      this.setState({ number: this.state.number + 2 });
      this.setState({ number: this.state.number + 3 });
    }, 100);
    transcation(this);
  }
  render() {
    console.log(this.state.number);
  }
}

const cmp = new MyComponent();
// 模拟首次渲染
cmp.update();
复制代码

下面是一段可以跑起来的伪代码,当然你也可以注释掉setTimeout,测试出来和我们之前的验证结果一模一样。

react内部其实实现原理也是这样,在第一次页面渲染前(调用过一次render方法之后)关于setState(obj)的写法都是异步缓存更新的。

但是一旦在页面渲染之后,内部pendingState状态改变。此时每次通过setState(obj)更新,每次都会触发单独更新直接更新而不会异步更新。

其实内部并没有多么复杂,就是依赖与pendingState缓存值和isBatchingUpdate判断是否需要批量更新。(好吧其实内部没有异步什么事情,它压根没有micro/macro什么事情呀)。

API总结

此时我们再来看关于setState的官方Api就会通俗很多。

setState(obj)

首先当我们在react内部使用setState(obj)进行调用的时候,如果是第一次render之前,那么所有的修改都会被缓存到pendingState中,之后在render中批量将pengdingState更换为state然后进行渲染。

所以我们每次更改state的值并不能实施获取。

但是刚才也讲过在首次调用render之后,再次调用setState(obj)之后,因为isBatchingUpdate已经打开,所以每次调用setState就会实时修改state的值并且进行页面渲染,此时我们就可以直接获取。

setState(callback)

react官方提供一种setState直接传入一个callback的写法。callback中支持传入一个state参数,这个state每次都会实时的拿到更改后的state,其实就是和我们上文的pendingState是一模一样的。

代码语言:javascript
复制
this.setState((state, props) => {
  return {counter: state.counter + props.step};
});
复制代码

这样一段代码

代码语言:javascript
复制
this.setState((state) => ({ name:state.name+1 }))
this.setState((state) => ({ name:state.name+3 }))
this.setState((state) => ({ name:state.name+2 }))
复制代码

传入的callback每次拿到的state都是在修改后的新值。

callback 函数中接收的 stateprops 都保证为最新。callback 的返回值会与 state 进行浅合并。

注意前两种写法的执行时机都是在组件更新之前进行合并state并且更新到最新的state中去。

setState(obj[,callback])

react官方提供setState支持传入第二个参数,它会保证在应用更新后(组件更新后执行,compnentDIdUpdate之后)会进行执行。

代码语言:javascript
复制
this.setState({name:11},() => {
    console.log('更新完毕')
})
复制代码

这种方式也会在callback中拿到最新的state,不过需要额外注意的是:

这样的写法类似Vue中的nextTick这个api,它的callback是会在componentDidUpdate之后进行执行。也就是它将在 setState完成合并并重新渲染组件后执行`。 这是和上边两种写法执行实际的不同。

写在结尾

当然我对于react的探索还在继续深入,也许之后在翻回来会发现有一部分的理解很片面。当然也希望大家可以积极指出文章中的不足,共同探讨。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年08月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • setState
    • 背景介绍
      • 记录问题
        • 异步更新原则
        • 使用setTimeout处理
        • 原理解析
        • API总结
      • 写在结尾
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档