前端 | Vue nextTick 獲取更新後的 DOM

Skuld_yi發表於2022-01-24

前兩天在開發時遇到一個需求:開啟對話方塊的時候自動聚焦其中的輸入框。由於原生的 autofocus 屬性不起作用,需要使用元件庫提供的 focus 方法手動手動獲取焦點。於是有如下程式碼:

<el-button @click="openDialog">點選開啟 Dialog</el-button>

<el-dialog :visible.sync="dialogVisible">
  <el-input v-model="input" ref="input"></el-input>
</el-dialog>
methods: {
  openDialog() {
    this.dialogVisible = true;
    const input = this.$refs.input;
    input.focus();
  },
},

結果報錯了,原因是沒有獲取到 input 元件;通過 log,也驗證了 this.$refs.input 的值確實是 undefined。但是經過測試,如果對話方塊預設狀態是開啟的,就不會報錯;明明元件就在那,為什麼獲取不到呢?

生命週期 update

經過分析,這種現象是由於 Vue 例項的更新機制造成的。從下方的生命週期圖(區域性)中可以看出,元件裝載好之後,遇到資料變化時將重新渲染虛擬 DOM(可以理解為 HTML 中的元件節點)。在本例中,隱藏的 Dialog 元件(以及其中的 input 元件)本來並沒有渲染在 DOM 中,是在觀察到 dialogVisible 屬性變為 true 後再進行更新渲染的。

Vue 生命週期圖(區域性)

而網頁渲染通常是一個非同步任務,因此在 visible 屬性剛剛更改時(一個函式中是同步過程),DOM 渲染還沒有進行,因此自然獲取不到此時還不存在的 input 元件了。

關於非同步、JS任務佇列、巨集任務與微任務等概念的更多介紹,可參考博文JS多執行緒:任務佇列

為了更直觀地展示這個過程,可以在更新前後的鉤子函式中試圖獲取元件並進行列印:

beforeUpdate() {
  console.log("beforeUpdate");
  const input = this.$refs.input;
  console.log(input);
},
updated() {
  console.log("updated");
  const input = this.$refs.input;
  console.log(input);
},
methods: {
  openDialog() {
    this.dialogVisible = true;
    console.log("click open");
  },
},

結果如下,可以驗證之前的分析和猜想:

click open
beforeUpdate
undefined
updated
VueComponent {...}

Vue.nextTick

為了解決這個問題,Vue 提供了全域性 api Vue.nextTick(),它的作用是提供下次 DOM 更新之後的回撥。也就是說,在更新資料後呼叫 api,就能夠獲取到重新渲染後的 DOM 並進行相關操作。

nextTick 方法可以廣泛適用於各種需要在資料更新後對相關 DOM 進行操作的情景,例如 v-ifwatch 等。

在上文的例子中再加入 nextTick:

openDialog() {
  this.dialogVisible = true;
  console.log("click open");
  this.$nextTick(function () {
    console.log("next tick");
    const input = this.$refs.input;
    console.log(input);
    input.focus();
  });
},

可以看到,回撥確實是在 DOM 更新之後,也就是 updated 執行之後才執行的。獲取元件與手動獲得焦點的操作也能夠正確執行了。

click open
beforeUpdate
undefined
updated
VueComponent {...}
next tick
VueComponent {...}

Promise

如果沒有提供回撥引數,並且瀏覽器支援 Promise,呼叫 nextTick 將返回一個 Promise。也就是說下面幾種寫法是等價的(環境支援的情況下):

Vue.nextTick(function () {...})
Vue.nextTick(() => {...})

Vue.nextTick().then(function () {...})
Vue.nextTick().then(() => {...})

關於 Promise 的介紹和用法,可以參考博文 JS Promise

結語&參考資料

以上是個人對 Vue 中 nextTick api 的一些理解與思考,希望能給你提供幫助。如果有問題或疏漏之處,歡迎在評論中討論與指正。

我將繼續在個人部落格中更新自己的學習筆記,以前端技術(Vue框架)為主,感興趣的話歡迎關注!

參考資料:

Vue 文件 - api

Vue 文件 - 例項

相關文章