動態

詳情 返回 返回

Vue組件異步請求一定要在 mounted 回調執行? - 動態 詳情

背景與總結

先説結論:

有很多所謂的最佳實踐告訴你這個觀點,但其實這個觀點是非常片面的,甚至在大部分場景下他是錯的。

聲明一下:

本文通過幾個案例説明部分同行觀點的片面性,分析了這種觀點出現原因,涉及一點vue的渲染原理。最後給出作為個人為了避免困擾,可以採取的相對最近實踐(因人而異)。

分析

很多文章給出的理由是:mounted回調函數被調用的時候,組件已經被掛載到了DOM上,而比mounted更早的生命週期還沒有掛載。

這句話本身沒有毛病,但它和mounted裏執行異步請求沒有必然的因果關係。

關鍵就在於,mounted回調執行時,組件已經掛載到了DOM上,可我們的異步請求與DOM有什麼關係嗎?

並沒有直接的關係,雖然我們請求的結果需要通過DOM呈現在界面上,用户才能看到。

但是,別忘了,通常我們的請求結果是經過處理然後複製給data的一個屬性,就像這樣:

getData(){
  axios.get(url).then(response => {
    // 這裏只是一個複製操作
    this.dataset = response.data
  })
}

我們只是做了一個賦值操作,並沒有操作DOM
之後這個賦值操作會被vue攔截到,然後去調用render函數生成新的vnode,進一步去做diff找出需要更新的屬性、節點、樣式等,最後會有真正的讀寫DOM的操作。

這也正是為什麼都説要放在mounted回調中執行異步請求的原因,因為賦值操作會導致DOM的操作。

但這個理由根本不成立,關鍵就在於異步這倆字。

如果你要做一個同步的請求,那麼我同意上述的觀點,但不好意思,你不是同步的。

案例

來看兩個例子對比下:

第一個,我們在created中有個異步操作,修改了requested屬性。

<template>
  <h1>{{ requested }}</h1>
</template>

<script>
export default {
  data() {
    return {
      requested: '',
    }
  },
  created() {
    console.log('created')
    setTimeout(() => {
      this.requested = 'done!'
      console.log('setTimeout!')
    })
  },
  mounted() {
    console.log('mounted')
  },
}
</script>

圖2

結果:代碼正常運行,界面正常展示,並沒有發生你所擔心的created中讀寫DOM出錯的問題。而且從輸出的日誌可以看出來,異步回調是在mounted之後才執行的,這個很關鍵。

如果換成同步操作呢?

<template>
  <h1>{{ requested }}</h1>
</template>

<script>
export default {
  data() {
    return {
      requested: '',
    }
  },
  created() {
    console.log('created')
    this.requested = 'requested finished!'
    console.log('requested finished!')
  },
  mounted() {
    console.log('mounted')
  },
}
</script>

image.png

結果:代碼正常運行,界面正常展示,也沒有發生你所擔心的created中讀寫DOM出錯的問題。
而從輸出的日誌可以看出來,created中的同步操作是在mounted之前執行的。
這裏很奇怪,儘管我們修改了data中的一個狀態會導致vue去讀寫DOM,但此時並沒有發生錯誤。

Vue渲染原理(部分)

實際上,這裏需要簡單瞭解下vue的渲染流程-diffdiff會對oldVnodenewVnode進行對比,在開始的時候有個很簡單的邏輯,以下是偽代碼:

if (!oldVnode){
  // 沒有上一次的 vnode,説明是首次渲染
  // 直接執行掛載方法,不需要再進行其它的比較
  mountComponent()
} else {
  // 正常的屬性、子節點等的對比
}

所以,created中同步的修改狀態不用擔心讀寫DOM出錯的問題。

異步請求放在哪裏

業內同行給出mounted中執行異步代碼的建議是相對安全的一種選擇,對於部分初學者來説,避免了業務開發的難度,避免了疑難問題的出現頻率。

由於前端各種開發人員對vue的原理認識不足,憑着自身的經驗給出這個最佳實踐是可以理解的。

實際上,通過上面的案例可以看到,將異步/同步請求放在created裏執行是可以的,當然有個前提哈:請求的回調中不能直接讀寫DOM
甚至,你異步(同步不行)請求放在beforeCreate中執行都是可行的,請求的回調中不能直接讀寫DOM依然是前提。

<template>
  <h1>{{ requested }}</h1>
</template>

<script>
export default {
  data() {
    return {
      requested: '',
    }
  },
  beforeCreate() {
    console.log('beforeCreate')
    setTimeout(() => {
      this.requested = 'done!'
      console.log('setTimeout!')
    })
  },
  created() {
    console.log('created')
  },
  mounted() {
    console.log('mounted')
  },
}
</script>

結果:
image.png

代碼執行運行,界面正常展示,很匪夷所思吧。

哈哈,有個重要的點,vue中的渲染流程initbeforeCreatecreatedbeforeMountmounted等生命週期鈎子的調用是同步的,所以異步的請求回調都會在這些同步鈎子執行完後才可能執行(可能的意思是,還得等其它同步任務執行完成)。

這裏的原理涉及到js的事件循環,不過多展開了,有興趣可以看看這篇文章【JS】深入理解事件循環,這一篇就夠了!。

另外,以上分析案例都是基於vue2.x的版本,實際上,使用vue3.x,結論依然成立。

綜上所述

  • 如果你是初學者或者只是臨時使用vue實現簡單的業務,不想過多的深入,那麼直接在mounted中執行異步代碼一般沒有明顯的問題;
  • 如果你是追求更好的性能,以及更深入的理解vue,那麼不妨更深入的思考下最佳實踐原因,它是否適合你以及你的項目呢。

Add a new 評論

Some HTML is okay.