VUE 3.0 學習探索入門系列 - Vue3.x 生命週期 和 Composition API 核心語法理解(6)

1 Vue2.x 生命週期回顧

  1. beforeCreate,在實例初始化以後,數據觀測 (data observer) 和 event/watcher 事件配置以前被調用。
  2. created,在實例建立完成後被當即調用。在這一步,實例已完成如下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前尚不可用。
  3. beforeMount,在掛載開始以前被調用:相關的 render 函數首次被調用。
  4. mounted,實例被掛載後調用,這時 el 被新建立的 vm.$el 替換了。 若是根實例掛載到了一個文檔內的元素上,當mounted被調用時 vm.$el 也在文檔內。
  5. beforeUpdate,數據更新時調用,發生在虛擬 DOM 打補丁以前。這裏適合在更新以前訪問現有的 DOM,好比手動移除已添加的事件監聽器。
  6. updated,因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。
  7. activated,被 keep-alive 緩存的組件激活時調用。
  8. deactivated,被 keep-alive 緩存的組件停用時調用。
  9. beforeDestroy,實例銷燬以前調用。在這一步,實例仍然徹底可用。
  10. destroyed,實例銷燬後調用。該鉤子被調用後,對應 Vue 實例的全部指令都被解綁,全部的事件監聽器被移除,全部的子實例也都被銷燬。
  11. errorCaptured,當捕獲一個來自子孫組件的錯誤時被調用。

參考:cn.vuejs.org/v2/api/#選項-…javascript

如下是整個生命週期圖示:html

參考:cn.vuejs.org/v2/guide/in…vue

2 Vue3.x 生命週期變化

被替換java

  1. beforeCreate -> setup()
  2. created -> setup()

重命名react

  1. beforeMount -> onBeforeMount
  2. mounted -> onMounted
  3. beforeUpdate -> onBeforeUpdate
  4. updated -> onUpdated
  5. beforeDestroy -> onBeforeUnmount
  6. destroyed -> onUnmounted
  7. errorCaptured -> onErrorCaptured

新增的編程

新增的如下2個方便調試 debug 的回調鉤子:api

  1. onRenderTracked
  2. onRenderTriggered

參考:vue-composition-api-rfc.netlify.com/api.html#li…緩存

特別說明bash

因爲 Vue3.x 是兼容 Vue2.x 的語法的,所以爲了保證 Vue2.x 的語法能正常在 Vue3.x 中運行,大部分 Vue2.x 的回調函數仍是獲得了保留。好比:雖然 beforeCreatecreatedsetup() 函數替代了,也就是說在 Vue3.x 中建議使用 setup(),而不是舊的API,可是若是你要用,代碼也是正常執行的。app

可是,如下2個生命週期鉤子函數被更名後,在 Vue3.x 中將不會再有 beforeDestroydestroyed

  1. beforeDestroy -> onBeforeUnmount
  2. destroyed -> onUnmounted

另外,假如 Vue3.x 在 Q2 如期 Release 的話,你們必定要注意,在混合使用 Vue2.x 和 Vue3.x 語法的時候,特別要注意這2套API的回調函數的執行順序。

3 Vue2.x + Composition API 對比 Vue3.x 生命週期執行順序

若是你們看了我上一篇文章 VUE 3.0 學習探索入門系列 - 糾結要不要升級到Vue3.0?該如何升級?(5),我說過當 Vue3.x 正式 Realease 之後,我可能會先使用 Vue2.x + Composition API,完了再使用 Vue3.x 搭建新的基礎框架。

那我今天主要關心的一個問題,一旦我使用 Vue2.x + Composition API 進入過渡期後,當我再使用 Vue3.x 搭建新的框架,那麼以前的基礎組件還能使用麼?

先測試下生命週期函數的執行順序。

3.1 Vue2.x + Composition API 生命週期執行順序

以下示例,在 Vue2.x 中引入兼容包 Composition API,而後Vue2.x 和 Vue3.x 的生命週期函數混合使用。

<template>
    <div>
        <p> {{ id }} </p>
        <p> {{ name }} </p>
    </div>
</template>
<script> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from '@vue/composition-api'; export default { setup() { const id = ref(1) console.log('setup') onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) // 測試 update 相關鉤子 setTimeout(() => { id.value = 2 }, 2000) return { id } }, data() { console.log('data') return { name: 'lilei' } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') }, beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') setTimeout(() => { this.id = 3; }, 4000) }, beforeUpdate() { console.log('beforeUpdate') }, updated() { console.log('updated') }, beforeUnmount() { }, unmounted() { console.log('unmounted') }, beforeDestroy() { console.log('beforeDestroy') }, destroyed() { console.log('destroyed') } } </script>

複製代碼

執行結果:

1. beforeCreate
2. setup
3. data
4. created
5. beforeMount
6. onBeforeMount
7. mounted
8. onMounted
9. beforeUpdate
10. onBeforeUpdate
11. updated
12. onUpdated
13. beforeDestroy
14. onBeforeUnmount
15. destroyed
16. onUnmounted
複製代碼

結論

Vue2.x 中經過補丁形式引入 Composition API,進行 Vue2.xVue3.x 的回調函數混用時:Vue2.x 的回調函數會相對先執行,好比:mounted 優先於 onMounted

3.2 Vue3.x 生命週期執行順序

如下直接使用 Vue3.x 語法,看看其在兼容 Vue2.x 狀況下,生命週期回調函數混合使用的執行順序。

<template>
    <div>
        <p> {{ id }} </p>
        <p> {{ name }} </p>
    </div>
</template>

<script> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } from 'vue'; export default { setup() { const id = ref(1) console.log('setup') onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) onRenderTracked(() => { console.log('onRenderTracked') }) onRenderTriggered(() => { console.log('onRenderTriggered') }) // 測試 update 相關鉤子 setTimeout(() => { id.value = 2; }, 2000) return { id } }, data() { console.log('data') return { name: 'lilei' } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') }, beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') setTimeout(() => { this.id = 3; }, 4000) }, beforeUpdate() { console.log('beforeUpdate') }, updated() { console.log('updated') }, beforeUnmount() { console.log('beforeUnmount') }, unmounted() { console.log('unmounted') } } </script>

<style scoped> </style>
複製代碼

執行結果:

1. beforeCreate
2. data
3. created
4. onRenderTracked
5. onRenderTracked
6. onBeforeMount
7. beforeMount
8. onMounted
9. mounted
10. onRenderTriggered
11. onRenderTracked
12. onRenderTracked
13. onBeforeUpdate
14. beforeUpdate
15. onUpdated
16. updated
17. onBeforeUnmount
18. beforeUnmount
19. onUnmounted
20. unmounted
複製代碼

結論

Vue3.x 中,爲了兼容 Vue2.x 的語法,全部舊的生命週期函數獲得保留(除了 beforeDestroydestroyed),當生命週期混合使用時:Vue3.x 的生命週期相對優先於 Vue2.x 的執行,好比:onMountedmounted 先執行。

4 Vue2.x + Composition API 過分到 Vue3.x 生命週期總結

綜上所述:

  • Vue2.x 中經過補丁形式引入 Composition API,進行 Vue2.xVue3.x 的回調函數混用時:Vue2.x 的回調函數會相對先執行,好比:mounted 優先於 onMounted
  • Vue3.x 中,爲了兼容 Vue2.x 的語法,全部舊的生命週期函數獲得保留(除了 beforeDestroydestroyed)。當生命週期混合使用時:Vue3.x 的生命週期相對優先於 Vue2.x 的執行,好比:onMountedmounted 先執行。

經過對比能夠得出:當你的主版本是哪一個,當生命週期混用時,誰的回調鉤子就會相對優先執行。

因此,這裏就會有點坑!爲了給減少之後沒必要要的麻煩,若是你們在 Vue2.x 中經過補丁形式引入 Composition API的使用的時候,建議:

  1. 不要混用Vue2.x和Vue3.x的生命週期。要麼你繼續使用 Vue2.x 的鉤子函數,要麼使用 Vue3.x 的鉤子函數,這樣就沒問題。
  2. 在原則1的狀況下,建議源碼從工程或者目錄就區分開新老版本。方便之後升級或者被引入到 Vue3.x 使用的時候,更有針對性兼容測試。

5 Composition API 核心語法

如下內容,大部分參考官方: Vue Composition API

5.1 setup 主執行函數

setup 是 Composition API 的核心,能夠說也是整個 Vue3.x 的核心。

  • setup 就是將 Vue2.x 中的 beforeCreatecreated 代替了,以一個 setup 函數的形式,能夠靈活組織代碼了。
  • setup 還能夠 return 數據或者 template,至關於把 datarender 也一併代替了!

爲何說 setup 靈活了呢?由於在這個函數中,每一個生命週期能夠是一個函數,在裏面執行,以函數的方式編程。

下面看看其幾個核心特色:

1 返回一組數據給template

<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

<script> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // expose to template return { count, object } } } </script>
複製代碼

2 使用jsx語法

<script> import { h, ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) return () => h('div', [ h('p', { style: 'color: red' }, count.value), h('p', object.foo) ]) } } </script>
複製代碼

3 Typescript Type

interface Data {
  [key: string]: unknown
}

interface SetupContext {
  attrs: Data
  slots: Slots
  emit: ((event: string, ...args: unknown[]) => void)
}

function setup( props: Data, context: SetupContext ): Data 複製代碼

4 參數

須要注意的是,在 setup 函數中,取消了 this!兩方面的緣由:

  1. 因爲 setup 是一個入口函數,本質是面向函數編程了,而 this 是面向對象的一種體現!在徹底基於函數的編程世界中,這個 this 就很難在能達到跟 Vue2.x 那種基於 OOP 思想的 Options 機制的實現的效果。

  2. 一樣是基於函數式編程,那麼若是加上 this,對於新手而言,原本 this 就很差理解,這時候就更加懵逼了。好比:

    setup() {
      function onClick() {
        this // 若是有 this,那麼這裏的 this 可能並非你期待的!
      }
    }
    複製代碼

取消了 this,取而代之的是 setup 增長了2個參數:

  • props,組件參數
  • context,上下文信息
setup(props, context) {
    // props
    // context.attrs
    // context.slots
    // context.emit
}
複製代碼

也許你會有疑問,僅有這2個參數就夠了麼?夠了。你在 Vue2.x 的時候,this 沒法就是獲取一些 data、props、computed、methods 等麼?

其實,這2個參數都是外部引入的,這個沒辦法只能帶入初始化函數中。除此以外,你組件上用到的全部 this 能獲取的數據,如今都至關於在 setup 中去定義了,至關於局部變量同樣,你還要 this 幹嗎呢?

好比:

setup(props, context) {
    // data
    const count = ref(1)
    
    // 生命週期鉤子函數
    onMounted(() => {
      console.log('mounted!')
    })
    
    // 計算函數
    const plusOne = computed(() => count.value + 1)
    
    // methods 方法
    const testMethod = () => {
        console.log('methods');
    }
    
    // return to template
    return {
        count,
        testMethod
    }
}
複製代碼

一切在 setup 中,都至關於變成了 局部變量 了,你還要 this 幹嗎?

固然,若是你要講 Vue2.x 和 Vue3.x 混用!那就很彆扭了,之後用 this,之後又不能用,你本身也會懵逼,因此在此建議:雖然Vue3.x兼容Vue2.x語法,可是不建議混合使用各自語法!

5.2 reactive 方法

reactive 方法包裹後的 對象 就變成了一個代理對象,至關於 Vue2.x 中的 Vue.observable()。也就能夠實現頁面和數據之間的雙向綁定了。

這個包裹的方法是 deep 的,對全部嵌套的屬性都生效。

注意: 通常約定 reactive 的參數是一個對象,而下文提到的 ref 的參數是一個基本元素。但若是反過來也是能夠的,reactive 其實能夠是任意值,好比:reactive(123) 也是能夠變成一個代理元素,能夠實現雙向綁定。

好比:

<template>
    <div>
        <p>{{ obj1.cnt }}</p>
        <p>{{ obj2.cnt }}</p>
    </div>
</template>

<script> import { reactive } from 'vue' setup() { // 普通對象 const obj1 = { cnt: 1 } // 代理對象 const obj2 = reactive({ cnt: 1 }) obj1.cnt++ obj2.cnt++ return { obj1, obj2 } } </script>
複製代碼

頁面顯示結果:

1
2
複製代碼

能夠看到,普通對象屬性更新時,頁面是不會同步更新的。只有代理對象,才能夠實現雙向綁定。

5.3 ref 方法

ref 方法包裹後的 元素 就變成了一個代理對象。通常而言,這裏的元素參數指 基本元素 或者稱之爲 inner value,如:number, string, boolean,null,undefied 等,object 通常不使用 ref,而是使用上文的 reactive

也就是說 ref 通常適用於某個元素的;而 reactive 適用於一個對象。

ref 也就至關於把單個元素轉換成一個 reactive 對象了,對象默認的鍵值名是:value

好比:

setup() {
    const count = ref(100)
}
複製代碼

ref 包裹後的元素變成一個代理對象,效果就至關於:

setup() {
    const count = reactive({
        value: 100
    })
}
複製代碼

由於變成了一個代理對象,因此取值的時候須要 .value

setup() {
    const count = ref(100)
    console.log(count.value) // output: 100
}
複製代碼

另外 ref 的結果在 template 上使用時,會自動打開 unwrap,不須要再加 .value

<template>
  <div>{{ count }}</div>
</template>

<script> export default { setup() { return { count: ref(0) } } } </script>
複製代碼

如下是一些基本元素 ref 的結果:

setup() {
    console.log(ref(100).value) // output: 100
    console.log(ref('test').value) // output: test
    console.log(ref(true).value) // output: true
    console.log(ref(null).value) // output: null
    console.log(ref(undefined).value) // output: undefined
    console.log(ref({}).value) // output: {}
}
複製代碼

5.3 isRef 方法

判斷一個對象是否 ref 代理對象。

const unwrapped = isRef(foo) ? foo.value : foo
複製代碼

5.4 toRefs 方法

將一個 reactive 代理對象打平,轉換爲 ref 代理對象,使得對象的屬性能夠直接在 template 上使用。

看看下面的例子你可能就明白它的做用了。

<template>
  <p>{{ obj.count }}</p>
  <p>{{ count }}
  <p>{{ value }}
</template>

<script> export default { setup() { const obj = reactive({ count: 0, value: 100 }) return { obj, // 若是這裏的 obj 來自另外一個文件, // 這裏就能夠不用包裹一層 key,能夠將 obj 的元素直接平鋪到這裏 // template 中能夠直接獲取屬性 ...toRefs(obj) } } } </script>
複製代碼

5.5 computed 函數

與 Vue2.x 中的做用相似,獲取一個計算結果。固然功能有所加強,不只支持取值 get(默認),還支持賦值 set

注意: 結果是一個 ref 代理對象,js中取值須要 .value

正常獲取一個計算結果:

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 報錯,由於未實現 set 函數,沒法賦值操做!
複製代碼

當 computed 參數使用 object 對象書寫時,使用 get 和 set 屬性。set 屬性能夠將這個對象編程一個可寫的對象。

也就是說 computed 不只能夠獲取一個計算結果,它還能夠反過來處理 ref 或者 reactive 對象!

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 100,
  set: val => { count.value = val - 1 }
})

plusOne.value = 1
console.log(count.value) // 0
console.log(plusOne.value) // 100
複製代碼

plusOne.value = 1 至關於給計算對象賦值,會觸發 set 函數,因而 count 值被修改了。

5.5 readonly 函數

使用 readonly 函數,能夠把 普通 object 對象reactive 對象ref 對象 返回一個只讀對象。

返回的 readonly 對象,一旦修改就會在 console 有 warning 警告。程序仍是會照常運行,不會報錯。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 只要有數據變化,這個函數都會執行
  console.log(copy.count)
})

// 這裏會觸發 watchEffect
original.count++

// 這裏不會觸發上方的 watchEffect,由於是 readonly。
copy.count++ // warning!
複製代碼

還有一些 API 諸如 watchwatchEffect 等,這裏就不說了,以上是一些比較重要的的語法,但願能對你們有所幫助。

6 最後

本文也是 VUE 3.0 學習探索入門系列 裏面的最後一篇,但願能對你們入門 Vue3 有所幫助。

接下來就等 Vue3.0 正式 Release 之後,再帶給你們 VUE 3.0 實戰上手篇 系列,歡迎你們關注我,及時瞭解動態。

本系列歷時20天完成,再次感謝你們。

(全劇終)

相關文章
相關標籤/搜索