Vue的异步更新原理和nextTick的实现

Vue的视图更新,是同步的还是异步的

答案是异步的

如何更新是异步的,那么如何快速拿到dom里面的值呢?

nextTick

那么为什么是异步的?

下面的代码可以当成是vue的响应式更新

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button id="btn">count++</button>
    <div id="el"></div>
  </div>
  <script>
    const el = document.querySelector("#el")
    const btn = document.querySelector('#btn')

    function effect(fn) {
      //保存当时的effect
      activeEffect = fn
      fn()
      //执行完之后清空
      activeEffect = null
    }

    //当前正在执行的effect
    let activeEffect = null
    //保存依赖
    const set = new Set()
    //响应式对象
    const count = {
      _value: 0,
      get value() {
        //收集依赖
        activeEffect && set.add(activeEffect)
        return this._value
      },
      set value(val) {
        this._value = val
        //触发更新
        set.forEach((cb)=>cb())
      }
    }
    effect(()=>{
      console.log('effect')
      el.innerText = count.value
    })
    btn.addEventListener('click',()=>{
      count.value++
    })
  </script>
</body>
</html>

当点击按钮时重复更新counts数据

btn.addEventListener('click',()=>{
      count.value++
     count.value++
    })

点击按钮时,直接加2了
如图:


image.png

由于是同步的其中effect执行了两次,点击按钮直接加2了,那么effect中+1的更新就没有必要,如何不触发呢
新增任务队列
代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button id="btn">count++</button>
    <div id="el"></div>
  </div>
  <script>
    const el = document.querySelector("#el")
    const btn = document.querySelector('#btn')

    function effect(fn) {
      //保存当时的effect
      activeEffect = fn
      fn()
      //执行完之后清空
      activeEffect = null
    }

    //当前正在执行的effect
    let activeEffect = null
    //保存依赖
    const set = new Set()
    //
    const tasks = new Set()

    function runTasks() {
      //因为是异步更新
      Promise.resolve().then(()=>{
        tasks.forEach((task)=>task())
        tasks.clear()
      })
    }
    //响应式对象
    const count = {
      _value: 0,
      get value() {
        //收集依赖
        activeEffect && set.add(activeEffect)
        return this._value
      },
      set value(val) {
        this._value = val
        //触发更新
        // set.forEach((cb)=>cb())
        //在触发更新这里先不触发,把这次更新放入tasks中
        set.forEach((cb)=>tasks.add(cb))
        runTasks()
      }
    }
    effect(()=>{
      console.log('effect')
      el.innerText = count.value
    })
    btn.addEventListener('click',()=>{
      count.value++
      count.value++
      console.log(el.innerText)
    })
  </script>
</body>
</html>

点击按钮,effect只执行了一次


image.png

但是打印出来的el.innerText拿不到即时的值,那么就需要nextTick了,nextTick是如何实现的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button id="btn">count++</button>
    <div id="el"></div>
  </div>
  <script>
    const el = document.querySelector("#el")
    const btn = document.querySelector('#btn')

    function effect(fn) {
      //保存当时的effect
      activeEffect = fn
      fn()
      //执行完之后清空
      activeEffect = null
    }

    //当前正在执行的effect
    let activeEffect = null
    //保存依赖
    const set = new Set()
    //
    const tasks = new Set()

    function runTasks() {
      //因为是异步更新
      Promise.resolve().then(()=>{
        tasks.forEach((task)=>task())
        tasks.clear()
      })
    }
    //响应式对象
    const count = {
      _value: 0,
      get value() {
        //收集依赖
        activeEffect && set.add(activeEffect)
        return this._value
      },
      set value(val) {
        this._value = val
        //触发更新
        // set.forEach((cb)=>cb())
        //在触发更新这里先不触发,把这次更新放入tasks中
        set.forEach((cb)=>tasks.add(cb))
        runTasks()
      }
    }
    effect(()=>{
      console.log('effect')
      el.innerText = count.value
    })
    function nextTick(cb) {
      Promise.resolve().then(cb)
    }
    btn.addEventListener('click',()=>{
      count.value++
      count.value++
      nextTick(()=>{
        console.log(el.innerText)
      })
    })
  </script>
</body>
</html>

如图,可以拿到正确的值


image.png

从上面可得,vue为什么要异步更新

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容