资讯专栏INFORMATION COLUMN

【React进阶系列】 setState机制

Yuqi / 2444人阅读

摘要:的批量更新优化也是建立在异步合成事件钩子函数之上的,在原生事件和中不会批量更新,在异步中如果对同一个值进行多次,的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时多个不同的值,在更新时会对其进行合并批量更新。

api解析: setState(updater, [callback])
updater: 更新数据 FUNCTION/OBJECT
callback: 更新成功后的回调 FUNCTION
// updater - Function
this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

// update - Object
this.setState({quantity: 2})
setState的特点:
1.异步:react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能
2.浅合并 Objecr.assign()
setState问题与解决

在使用setState改变状态之后,立刻通过this.state去拿最新的状态
解决: componentDidUpdate或者setState的回调函数里获取

// setState回调函数
changeTitle: function (event) {
  this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
  // Call API with the updated value
}

有一个需求,需要在在onClick里累加两次,使用对象的方法更新,则只会加一次
解决: 使用updater function

onClick = () => {
    this.setState({ index: this.state.index + 1 });
    this.setState({ index: this.state.index + 1 });
}

// 最后解析为,后面的数据会覆盖前面的更改,所以最终只加了一次.
Object.assign(
  previousState,
  {index: state.index+ 1},
  {index: state.index+ 1},
)

//正确写法
onClick = () => {
    this.setState((prevState, props) => {
      return {quantity: prevState.quantity + 1};
    });
    this.setState((prevState, props) => {
      return {quantity: prevState.quantity + 1};
    });
}
注意:
1.不要在render()函数里面写setstate(),除非你自己定制了shouldComponentUpdate方法,要不然会引起无限循环
render() {
    //this.setState
    return(
        //...dom
    )
}
2.为什么不能使用第一个方式更新
react为了实现高效render, state其实是一个队列,setState是将数据插入队列中,使用第一种不会触发渲染, react提供了setState的实例方法可以触发render,可以看后面的源码
// 1
this.state.num = 1
// 2
this.setState({
    num: this.state.num + 1
})
3.对数组和对象等引用对象操作时,使用返回新对象的方法
  array: 不要使用push、pop、shift、unshift、splice可使用concat、slice、filter、扩展语法
  object: Object.assgin/扩展语法
setState更新机制

如图: pending queue 和 update queue

setState源码 this.setState()
ReactComponent.prototype.setState = function (partialState, callback) {
  //  将setState事务放进队列中
  //  this.updater就是ReactUpdateQueue, this是组件的实例
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, "setState");
  }
};
enqueueSetState()
enqueueSetState: function (publicInstance, partialState) {
     // 获取当前组件的instance
     // 实例中有两个非常重要的属性:_pendingStateQueue(待更新队列) 与 _pendingCallbacks(更新回调队列)
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, "setState");

     // 初始化待更新队列
     var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
     // 将要更新的state放入一个数组里
    queue.push(partialState);

     //  将要更新的component instance也放在一个队列里
    enqueueUpdate(internalInstance);
  }
enqueueUpdate
当前如果正处于创建/更新组件的过程,就不会立刻去更新组件,而是先把当前的组件放在dirtyComponent里,所以不是每一次的setState都会更新组件
function enqueueUpdate(component) {
  // 如果没有处于批量创建/更新组件的阶段,则处理update state事务
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
  dirtyComponents.push(component);
}
batchingStrategy
var ReactDefaultBatchingStrategy = {
  // 用于标记当前是否出于批量更新
  isBatchingUpdates: false,
  // 当调用这个方法时,正式开始批量更新
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
    // 否则执行更新事务
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};
transaction
initalize(空函数) -> perform(anyMethos) -> close

// 将isBatchingUpdates置为false
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

// 循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最后实现组件的更新
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
setState面试
面试官:“react中setState是同步的还是异步?”
我:“异步的,setState不能立马拿到结果。”
面试官:“那什么场景下是异步的,可不可能是同步,什么场景下又是同步的?”

下题结果是:

class App extends React.Component {
  state = { val: 0 }

  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);

      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
    }, 0)
  }

  render() {
    return 
{this.state.val}
} } // 结果就为 0, 0, 2, 3
总结
1.setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
2.setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
3.setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
1、合成事件中的setState
合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件
合成事件中也有batchedUpdates方法,是通过同样的事务完成的
class App extends Component {

  state = { val: 0 }

  increment = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新前的val --> 0
  }

  render() {
    return (
      
{`Counter is: ${this.state.val}`}
) } }
2、生命周期函数中的setState
整个生命周期中就是一个事物操作,所以标识位isBatchingUpdates = true,所以流程到了enqueueUpdate()时,实例对象都会加入到dirtyComponents 数组中
class App extends Component {

  state = { val: 0 }

 componentDidMount() {
    this.setState({ val: this.state.val + 1 })
   console.log(this.state.val) // 输出的还是更新前的值 --> 0
 }
  render() {
    return (
      
{`Counter is: ${this.state.val}`}
) } }
3、原生事件中的setState
原生事件是指非react合成事件,原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick 这种绑定事件的形式都属于原生事件
原生事件绑定不会通过合成事件的方式处理,自然也不会进入更新事务的处理流程。setTimeout也一样,在setTimeout回调执行时已经完成了原更新组件流程,不会放入dirtyComponent进行异步更新,其结果自然是同步的。
class App extends Component {

  state = { val: 0 }

  changeValue = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新后的值 --> 1
  }

 componentDidMount() {
    document.body.addEventListener("click", this.changeValue, false)
 }
 
  render() {
    return (
      
{`Counter is: ${this.state.val}`}
) } }
4、setTimeout中的setState

基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值。

class App extends Component {

  state = { val: 0 }

 componentDidMount() {
    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val) // 输出更新后的值 --> 1
    }, 0)
 }

  render() {
    return (
      
{`Counter is: ${this.state.val}`}
) } }
5、批量更新

在 setState 的时候react内部会创建一个 updateQueue ,通过 firstUpdate 、 lastUpdate 、 lastUpdate.next 去维护一个更新的队列,在最终的 performWork 中,相同的key会被覆盖,只会对最后一次的 setState 进行更新

class App extends Component {

  state = { val: 0 }

  batchUpdates = () => {
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
 }

  render() {
    return (
      
{`Counter is ${this.state.val}`} // 1
) } }

引发感想来源:

https://juejin.im/post/5b45c5...

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

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

相关文章

  • setState异步、同步与进阶

    摘要:根本原因在于,并不是真正意义上的异步操作,它只是模拟了异步的行为。而合成事件和生命周期函数中,是受控制的,其会将设置为,从而走的是类似异步的那一套。总结此处总结是直接引用了只在合成事件和钩子函数中是异步的,在原生事件和中都是同步的。 如何使用setState 在 React 日常的使用中,一个很重要的点就是,不要直接去修改 state。例如:this.state.count = 1是无...

    widuu 评论0 收藏0
  • React 深入系列4:组件的生命周期

    摘要:因为是深入系列文章,本文不会仔细介绍每个生命周期方法的使用,而是会重点讲解在使用组件生命周期时,经常遇到的疑问和错误使用方式。父组件发生更新导致的组件更新,生命周期方法的调用情况同上所述。 文:徐超,《React进阶之路》作者授权发布,转载请注明作者及出处 React 深入系列4:组件的生命周期 React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深...

    warnerwu 评论0 收藏0
  • React 深入系列3:Props 和 State

    摘要:当真正执行状态修改时,依赖的并不能保证是最新的,因为会把多次的修改合并成一次,这时,还是等于这几次修改发生前的。下篇预告深入系列组件的生命周期新书推荐进阶之路作者徐超毕业于浙江大学,硕士,资深前端工程师,长期就职于能源物联网公司远景智能。 文:徐超,《React进阶之路》作者授权发布,转载请注明作者及出处 React 深入系列3:Props 和 State React 深入系列,深...

    endiat 评论0 收藏0
  • React进阶系列】手写实现react-redux api

    简介:简单实现react-redux基础api react-redux api回顾 把store放在context里,所有子组件可以直接拿到store数据 使组件层级中的 connect() 方法都能够获得 Redux store 根组件应该嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...

    刘玉平 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0

发表评论

0条评论

Yuqi

|高级讲师

TA的文章

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