React setState有同步有异步,那么同情况下的Vue呢?

1,560 阅读4分钟

React setState有同步有异步,那么同情况下的Vue呢?

首先:关于 React useState和setState到底是同步还是异步?

可以看这位朋友写的:juejin.cn/post/695988…

总结一下(React useState和setState到底是同步还是异步?)

  1. 通过react事件流控制的(比如onClick)或生命周期或hook(总之是react api控制的),就是异步
  • setState和useState都是异步执行的(不会立即更新state的结果)
  • 多次执行setState和useState,只会调用一次重新渲染render
  • 不同的是,setState会进行state的合并,而useState则不会
  1. 通过js原生api,比如setTimeout,promise,addEventListener等。都超出了react的控制范围的,就是同步执行的(有几个setState就render几次
  • setState和useState是同步执行的(立即更新state的结果)
  • 多次执行setState和useState,每一次的执行setState和useState,都会调用一次render

此例子能说明(注意看注释)react在原生js api内执行多个setState 有性能问题

  • 放在 promise和addEventListener内的 setState 是同步的,会render N次(性能更不好,重复render了)
class Component extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      a: 1,
    }
  }
  
  componentDidMount() {
    document.addEventListener('click', () => { // 非按钮处,点击一下
      this.handleClickWithoutPromise() // 两次 setState 各自 render 一次,分别打印 2,3
    })
  }
  
  handleClickWithPromise = () => {
    Promise.resolve().then(() => {
      this.setState({a: this.state.a + 1})
      this.setState({a: this.state.a + 1})
      console.log(this.state.a) // 同步执行 拿最终结果 3
    })
  }

  handleClickWithoutPromise = () => {
    this.setState({a: this.state.a + 1})
    this.setState({a: this.state.a + 1})
    console.log(this.state.a) // 异步执行 拿初始值 1 
  }

  render() {
    // 当点击同步执行按钮时,两次 setState 合并,只执行了最后一次,此处log 打印 2
    // 当点击异步执行按钮时,两次 setState 各自 render 一次,此处log分别 打印 2,3
    console.log('a', this.state.a)
    return (
        <>
          {this.state.a}
          <button onClick={this.handleClickWithPromise}>放在promise内, 结果是同步执行setState, 会render N次, 损害性能</button>
          <button onClick={this.handleClickWithoutPromise}>受react控制, 异步执行, 会合并setState, 只会render 1次, 性能更好</button>
        </>
    )
  }
}

相同的demo,在vue中是什么情况呢?

先说结论:

对于vue来说,并没有这种性能问题,vue不受js原生api的影响

  1. 因为vue是对数据的变化做劫持无论是否是原生js api操作数据,数据都可以被劫持到
    • 数据劫持的api的使用(源码自带):vue2是es5的Object.definedProperty。vue3是es6的new Proxy()
  2. vue会把一次同步任务内的操作,都合并起来,只render一次。 不受setTimeout,promise,addEventListener等api的影响

以下demo配置:vue3 + jsx 写法 (vue3和vue2都是对数据做劫持)

  • 注意看注释,和按钮的名字
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup () {
    const a = ref(1) // 不熟悉vue的,可以把这个理解为 react的 const [a, setA] = useState(1)
    console.log(a.value) // 打印a的值
    
    return () => {
      console.log(a.value) // 此处可以测试,vue的render的次数
      return (
        <div class="projectList">
          <div>{a.value}</div>
          <button onClick={() => {
            a.value = a.value + 1
            a.value = a.value + 1
            console.log(a.value) // 正常打印3,只触发一次render
          }}>普通按钮,正常打印,只触发一次render</button>
          
          <button onClick={() => {
            setTimeout(() => {
              a.value = a.value + 1
              a.value = a.value + 1
              console.log(a.value) // 正常打印3,只触发一次render
            })
          }}>不受setTimeout影响,正常打印,只触发一次render</button>
          
          <button onClick={() => {
            setTimeout(() => { // 第一个异步任务
              a.value = a.value + 1
              a.value = a.value + 1
              console.log(a.value) // 正常打印3,触发第一次render
              setTimeout(() => { // 第二个异步任务
                a.value = a.value + 1
                a.value = a.value + 1
                console.log(a.value) // 正常打印5,触发第二次render
              })
            })
          }}>两层setTimeout按钮,只受异步任务的影响,正常打印,只触发两次render</button>
        </div>
      )
    }
  }
})

总结

react的多个setState并发执行 区分同步和异步

  1. 正常如果受react api(react事件流、生命周期钩子、hook)控制的话,就是异步的(提高性能,只会render一次
  2. 如果不受react控制(比如js原生api:setTimeout,promise,addEventListener等),就会同步执行,render N次,性能更不好

对于vue来说,并没有这种性能问题,vue不受js原生api的影响

  1. 因为vue是对数据的变化做劫持无论是否是原生js api操作数据,数据都可以被劫持到
  2. vue会把一次同步任务内的操作,都合并起来,只render一次。  不受setTimeout,promise,addEventListener等api的影响 多个数据并发被修改的情况,vue是同步还是异步?
  3. 数据的变更是同步的。但是render是异步的
  4. 可以理解为:一次同步任务内,无论怎么同步执行修改数据,都不会render,最终的render会在同步任务结束后的 异步微任务中去执行
    • vue的源码内,就是用Promise.resolve().then( 在这里面执行render )
    • 微任务的优先级比setTimeout要高,所以两层setTimeout会执行2次render

谢谢思考!

码字不易,点赞鼓励!