首頁 > 軟體

vue的狀態更新方式(非同步更新解決)

2022-04-19 10:01:09

狀態更新(非同步更新解決)

在vue中狀態更新是非同步的,這一點和react中的setstate類似。

解決方案

非元件解決方案:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改資料
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在元件內使用 vm.$nextTick() 實體方法特別方便,因為它不需要全域性 Vue ,並且回撥函數中的 this 將自動繫結到當前的 Vue 範例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '沒有更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '更新完成'
      console.log(this.$el.textContent) // => '沒有更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '更新完成'
      })
    }
  }
})

因為 $nextTick() 返回一個 Promise 物件,所以你可以使用新的 ES2016 async/await 語法完成相同的事情:methods: {

  updateMessage: async function () {
    this.message = 'updated'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}

非同步更新及nexttick

為什麼需要非同步更新

vue為了避免頻繁的操作DOM,採用非同步的方式更新DOM。這些非同步操作會通過nextTick函數將這些操作以cb的形式放到任務佇列中(以微任務優先),當每次tick結束之後就會去執行這些cb,更新DOM。

非同步更新內部是最重要的就是nextTick方法,它負責將非同步任務加入佇列和執行非同步任務。VUE 也將它暴露出來提供給使用者使用。在資料修改完成後,立即獲取相關DOM還沒那麼快更新,使用nextTick便可以解決這一問題。

nextTick 原理

在下次DOM更新迴圈結束之後執行的延遲迴撥。在修改資料之後立即使用該方法,獲取更新後的DOM。

/*存放非同步執行的回撥*/
const callbacks = [] 
/*一個標記位,如果已經有timerFunc被推播到任務佇列中去則不需要重複推播*/
let pending = false
/*一個函數指標,指向函數將被推播到任務佇列中,等到主執行緒任務執行完時,任務佇列中的timerFunc被呼叫*/
let timerFunc

/*
  推播到佇列中下一個tick時執行
  cb 回撥函數
  ctx 上下文
*/
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
   // 第一步 傳入的cb會被push進callbacks中存放起來
  callbacks.push(() => {
    if (cb) {      
        try {
            cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })  
  // 檢查上一個非同步任務佇列(即名為callbacks的任務陣列)是否派發和執行完畢了。pending此處相當於一個鎖
  if (!pending) {
  // 若上一個非同步任務佇列已經執行完畢,則將pending設定為true(把鎖鎖上)
    pending = true
    // 呼叫判斷Promise,MutationObserver,setTimeout的優先順序
    timerFunc()
  }
  // 第三步執行返回的狀態
  if (!cb && typeof Promise !== 'undefined') {   
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

Vue 在更新 DOM 時是非同步執行的。只要偵聽到資料變化,Vue 將開啟一個佇列,並緩衝在同一事件迴圈中發生的所有資料變更。

如果同一個 watcher 被多次觸發,只會被推入到佇列中一次。這種在緩衝時去除重複資料對於避免不必要的計算和 DOM 操作是非常重要的。

然後,在下一個的事件迴圈“tick”中,Vue 重新整理佇列並執行實際 (已去重的) 工作。

Vue 在內部對非同步佇列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執行環境不支援,則會採用 setTimeout(fn, 0) 代替。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com