setState是同步还是异步

2022-11-30  本文已影响0人  糖糖不加糖_

setState方法是同步还是异步

在react源码中他是同步的方法,通过队列的形式更新state的值,因此展现给人是异步更新的状态,但实际上它是一个同步的方法

setState两种传值格式

Object.assign() 方法将所有可枚举Object.propertyIsEnumerable() 返回 true)的自有Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。

setState({  index: 1  })
setState((prevState, props) => {
// 获取上一个prevState做值操作
return {XX: prevState.XX + 1}
})

两种类型的调用方式(对象方式、函数方式)混用

两种方式不能混用,否则合并后就会出问题了

const root = ReactDOM.createRoot(document.getElementById('root'));
class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
      this.setState({index: this.index + 2});
      console.log('=========0,', this.state.index);
      this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
  };

  render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
root.render(<Clock  />)

点击button后呈现结果


image.png

原理

image.png

具体操作为:
1、首先将setState中的参数partialState存储到state暂存队列中
2、判断当前是否处于批量处理状态,是,则将组件推入待更新队列,不是,则使用更新批量处理状态为ture,然后再将组件推入待更新队列(dirtyComponents)(批量更新完成后,设置批量处理状态为false,执行事务步骤)
3、调用事务(Transaction)waper方法遍历组件待更新队列dirtyComponents,执行更新
4、componentWillRecevieProps执行
5、state暂存队列合并,获取最终要更新的state,队列置空(ps:函数参数也是在这里确定值获取到prevState)

 Object.assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

6、componentShouldUpdate执行,根据返回值判断是否更新
7、componentWillUpdate执行
8、render执行
9、componentDidUpdate执行

最简单的话来讲就是state接收到一个新状态不会被立即执行,而是会被推入到pendingState队列(Queue)中,随后判断isBatchedUpdates的值,为true,则将新状态保存到dirtyComponents(脏组件)中;为false的话,遍历dirtyComponents,并调用updateComponent方法更新pengdingState中的state或props,将队列初始化

React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook ,在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。
但是【setTimeout】这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。
原文链接:https://blog.csdn.net/qq_39207948/article/details/113803273

总结

image.png

样例

1、在同步函数中的setState

在对象中的写法


class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
      console.log('=========0,', this.state.index);
      this.setState({
        index: this.state.index + 1,
      }, () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState({
        index: this.state.index + 5,
      }, () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
  };
 
 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);
image.png

点击button,此时,setState将设置的值推到队列里等待执行,发现有两个待更新的setState,因此在队列里增加这两个值,等待没有其他的setState后一起执行,此时同步事件的console.log已经执行过了,因此提前输出了值,随后在确认没有其他的setState时,进行render,render后再去执行两个setState的回调函数,因为有推送到队列的操作,又是对象数据,在源码内部会调用Object.assign,将对象合并,此时有同属性key值的就会被合并到一起,后面的值会覆盖前面的值,因此最后输出的是5

在函数中的写法

class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
      console.log('=========0,', this.state.index);
      this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
  };
 
 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);
image.png

点击button,两个setState同样会被推入队列中,因此跟上面一样,也会是在console.log执行完毕后才会更新state的值,即render,不同的是,此时render出来的结果是6,因为setState中使用的是函数,因此不会和对象一样使用Object.assign做合并处理,它本身不会合并,而是使用setState中更新后获取到的state值去传递给下一个setState函数,然后在进行计算,因此得到的值为6

2、在异步函数或原生dom事件中的setState

class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
    setTimeout(() => {
      console.log('=========0,', this.state.index);
      this.setState({
        index: this.state.index + 1,
      }, () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState({
        index: this.state.index + 5,
      }, () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
    });
  };

 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);

image.png

点击button后,看到结果为下图,即每次调用setState后都会去render,render后立即调用setState的callback函数。在异步函数中能够同步执行,能获取到上一次setState后执行的结果去作为已知值更新当前的值

在函数写法中参见下方

class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
     setTimeout(() => {
      console.log('=========0,', this.state.index);
      this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
    });
  };

 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);
image.png

由上图结果可见,在react16中,函数式结果和对象式结果一致

const root = ReactDOM.createRoot(document.getElementById('root'));

class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
     setTimeout(() => {
      console.log('=========0,', this.state.index);
      this.setState({
        index: this.state.index + 1,
      }, () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState({
        index: this.state.index + 5,
      }, () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
    });
  };

  render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}

root.render(<Clock  />)
image.png

点击button,setTimeout中的setState也可以同步执行,但是是批量执行了,将this.setState需要更新的对象合并到了一起,因此只取了index:this.state.index + 5的那个赋值,前面的赋值被覆盖掉了,但是render只会更新一次,就是在批量处理完数据,最后更新的时候覆盖的

在函数写法中参见下方

const root = ReactDOM.createRoot(document.getElementById('root'));

class Clock extends React.Component {
  state = {
    index: 0,
  };

  handleClick = () => {
      setTimeout(() => {
      console.log('=========0,', this.state.index);
      this.setState((prevState, props) => ({index: prevState.index + 1}),
           () => {console.log('函数更新后-------0', this.state.index)}
      );
      console.log('=========1,', this.state.index);
      this.setState((prevState, props) => ({index: prevState.index + 5}), 
           () => {console.log('函数更新后-------1', this.state.index)}
      );
      console.log('=========2,', this.state.index);
    });
  };

  render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}

root.render(<Clock  />)
image.png

点击button,会发现,他会先执行内部的同步方法,打印所有console.log后,也对setState进行了批量执行,因为最后只输出了一个render变量,但是与【对象写法】不同的是最后输出的结果是6,属于在setTimeout中将两个setState函数方法全都执行了,后面的方法执行拿到了上面的结果index,然后用第一个得到的index对下一个setState方法做值的处理,只是等所有值都更新完了,他在去进行render

在对象中的写法

class Clock extends React.Component {
  state = {
    index: 0,
  };
 handleClick = () => {
    setTimeout(() => {
      ReactDOM.unstable_batchedUpdates(() => {
        console.log('=========0,', this.state.index);
      this.setState({
        index: this.state.index + 1,
      }, () => {console.log('函数更新后-------0', this.state.index)});
      console.log('=========1,', this.state.index);
      this.setState({
        index: this.state.index + 5,
      }, () => {console.log('函数更新后-------1', this.state.index)});
      console.log('=========2,', this.state.index);
      })
  });
  };

 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);
image.png

在函数写法中参见下方

class Clock extends React.Component {
  state = {
    index: 0,
  };

 handleClick = () => {
    setTimeout(() => {
      ReactDOM.unstable_batchedUpdates(() => {
        console.log('=========0,', this.state.index);
        this.setState((prevState, props) => ({index: prevState.index + 1}),
             () => {console.log('函数更新后-------0', this.state.index)}
        );
        console.log('=========1,', this.state.index);
        this.setState((prevState, props) => ({index: prevState.index + 5}), 
             () => {console.log('函数更新后-------1', this.state.index)}
        );
        console.log('=========2,', this.state.index);
      })
  });
  };

 render() {
    console.log('======render', this.state.index);
    return <button onClick={this.handleClick}>Hello, world!</button>;
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
);
image.png

react测试链接:https://codepen.io/gaearon/pen/zKRGpo?editors=0010
上述代码均可在其中运行

事件循环event loop
setTimeout和dom事件都属于js线程中的异步操作

componentDidMount() {
  const btnEl = document.getElementById("btn");
// dom事件
  btnEl.addEventListener('click', () => {
    this.setState({
      count: 1
    });
  })
}

参考文献:
https://juejin.cn/post/6844903781813993486
https://blog.csdn.net/qq_39207948/article/details/113803273
https://www.51cto.com/article/711386.html

上一篇下一篇

猜你喜欢

热点阅读