vue3.0組合式API知識點整理

為什么要使用Composition API?

根據官方的說法,vue3.0的變化包括性能上的改進、更小的 bundle 體積、對 TypeScript 更好的支持、用于處理大規模用例的全新 API,全新的api指的就是本文主要要說的組合式api。

在 vue3 版本之前,我們復用組件(或者提取和重用多個組件之間的邏輯),通常有以下幾種方式:

  • Mixin:命名空間沖突 & 渲染上下文中暴露的 property 來源不清晰。例如在閱讀一個運用了多個 mixin 的模板時,很難看出某個 property 是從哪一個 mixin 中注入的。
  • Renderless Component:無渲染組件需要額外的有狀態的組件實例,從而使得性能有所損耗
  • Vuex:就會變得更加復雜,需要去定義 Mutations 也需要去定義 Actions

上述提到的幾種方式,也是我們項目中正在使用的方式。對于提取和重用多個組件之間的邏輯似乎并不簡單。我們甚至采用了 extend 來做到最大化利用已有組件邏輯,因此使得代碼邏輯依賴嚴重,難以閱讀和理解。
Vue3 中的 Composition API 便是解決這一問題;且完美支持類型推導,不再是依靠一個簡單的 this 上下文來暴露 property(比如 methods 選項下的函數的 this 是指向組件實例的,而不是這個 methods 對象)。其是一組低侵入式的、函數式的 API,使得我們能夠更靈活地「組合」組件的邏輯。

業務實踐

組合式api的出現就能解決以上兩個問題,此外,它也對TypeScript類型推導更加友好。
在具體使用上,對vue單文件來說,模板部分和樣式部分基本和以前沒有區別,組合式api主要影響的是邏輯部分。下面是一個經典的vue2的計數器案例.:

vue2 實現

//Counter.vue
export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      this.count++;
    }
  },
  computed: {
    double () {
      return this.count * 2;
    }
  }
}

vue3 composition api

當我們在組件間提取并復用邏輯時,組合式API 是十分靈活的。一個組合函數僅依賴它的參數和 Vue 全局導出的 API,而不是依賴其微妙的 this 上下文。你可以將組件內的任何一段邏輯導出為函數以復用它。

  • 基于響應式
  • 提供 vue 的生命周期鉤子
  • 組件銷毀時自動銷毀依賴監聽
  • 可復用的邏輯
// Counter.vue
import { ref, computed } from "vue";

export default {
  setup() {
    const count = ref(0);
    const double = computed(() => count * 2)
    function increment() {
      count.value++;
    }
    return {
      count,
      double,
      increment
    }
  }
}

代碼提取

Composition API的第一個明顯優點是提取邏輯很容易。使用Composition提取上面Counter.vue組件代碼。

//useCounter.js 組合函數
import { ref, computed } from "vue";

export default function () {
  const count = ref(0);
  const double = computed(() => count * 2)
  function increment() {
    count.value++;
  }
  return {
    count,
    double,
    increment
  }
}

代碼重用

要在組件中使用該函數,我們只需將模塊導入組件文件并調用它(注意導入是一個函數)。這將返回我們定義的變量,隨后我們可以從 setup 函數中返回它們。

// MyComponent.js
import useCounter from "./useCounter.js";

export default {
  setup() {
    const { count, double, increment } = useCounter();
    return {
      count,
      double,
      increment
    }
  }
} 

相比而言,組合式 API:

  • 暴露給模板的 property 來源十分清晰,因為它們都是被組合邏輯函數返回的值
  • 不存在命名空間沖突,可以通過解構任意命名
  • 不再需要僅為邏輯復用而創建新的組件實例
image

3107124351-5f184be9e39f2_fix732.png
3107124351-5f184be9e39f2_fix732.png

常用api介紹

setup

export default {
  setup(props, context) {
    console.log(context); // { attrs, slots, emit }
    //context.emit('emitFun', {emit: true})
    return { privateMsg: props.msg };
  }
}

setup函數是組件內使用 component API 的入口。是在組件實例被創建時, 初始化了 props 之后調用,處于 created 前。還有以下特點:
1.可以返回一個對象或函數,對象的屬性會合并到模板渲染的上下文中;
2.第一個參數是響應式的props對象,注意不能解構 props 對象,會使其失去響應性。 **
也不可直接修改 props,會觸發警告
3.第二個參數是一個上下文對象,暴露了 attrs,slots,emit 對象
4.
this 在 setup 函數中不可用。**因為它不會找到組件實例。setup 的調用發生在 data、computed 和 methods 被解析之前,所以它們無法在 setup 中被獲取。

props與上下文對象attrs的區別:
1、props 要先聲明才能取值,attrs 不用先聲明
2、props 聲明過的屬性,attrs 里不會再出現
3、props 不包含事件,attrs 包含。vue2中的listeners 被整合到attrs


reactive

<template>
  <div>
    <p>{{data.msg}}</p>
    <button @click="updateData">更新數據</button>
  </div>
</template>

<script>
import { reactive } from "vue";

export default {
  name: "ReactiveObject",
  setup() {
    const data = reactive({ msg: "hello world" });
    const updateData = () => {
      data.msg= "hello world " + new Date().getTime();
    };
    return { data, updateData };
  },
};
</script>

reactive函數接收一個普通對象然后返回對象的響應式代理,同 Vue.observable。
原理:通過proxy對數據進行封裝,當數據變化時,觸發模板等內容的更新。

ref

<template>
  <div>
    <p>{{msg}}</p>
    <button @click="updateMessage">更新數據</button>
  </div>
</template>

<script>
import { ref } from "vue";

export default {
  name: "ReactiveSingleValue",
  setup() {
    const msg= ref("hello world");
    const updateMessage = () => {
      msg.value = "hello world " + new Date().getTime();
    };
    return { msg, updateMessage };
  },
};
</script>

ref和reactive存在一定的相似性,所以需要完全理解它們才能高效的在各種場景下選擇不同的方式,它們之間最明顯的區別是ref使用的時候需要通過.value來取值,reactive不用。ref是property而reactive是proxy,reactive能夠深度監聽各種類型對象的變化,ref是處理諸如number,string之類的基本數據類型。
它們的區別也可以這么理解,ref是使某一個數據提供響應能力,而reactive是為包含該數據的一整個對象提供響應能力。
在模板里使用ref和嵌套在響應式對象里時不需要通過.value,會自己解開:

除了響應式ref還有一個引用DOM元素的ref,2.x里面是通過this.$refs.xxx來引用,但是在setup里面沒有this,所以也是通過創建一個ref來使用:

<template>
    <div ref="node"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
    setup() {
        const node = ref(null)
        onMounted(() => {
            console.log(node.value)  // 此處就是dom元素 <div ref="node"></div>
        })
        return {
            node
        }
    }
}
</script>

computed

傳入一個 getter 函數,返回一個默認不可修改的 ref 對象,同 vue 2.x 中的計算屬性 computed

const count = ref(0)
const sum = computed(() => count.value + 1)
console.log(sum.value) // 1
sum.value = 3 // 錯誤

也可傳入一個 get 和 set 函數對象,創建一個可修改的計算狀態

const count = ref(0)

const sum = computed({
  get: () => count.value + 1,
  set: (value) => {
    count.value = value - 1
  }
})

sum.value = 55
console.log(sum, count) // 1, 54

watchEffect

import { reactive, watchEffect } from "vue";
export default {
  name: "WatchEffect",
  setup() {
    const data = reactive({ count: 1 });
    watchEffect(() => console.log(`偵聽器:${data.count}`));
    setInterval(() => {
      data.count++;
    }, 1000);
    return { data };
  },
};

watchEffect用來監聽數據的變化,它會立即執行一次,之后會追蹤函數里面用到的所有響應式狀態,當變化后會重新執行該回調函數。

watch

完全等效于 2.x 中 watch 選項,對比 watchEffect,watch 允許我們:

  • 懶執行副作用;
  • 更明確哪些狀態的改變會觸發偵聽器重新運行副作用;
  • 訪問偵聽狀態變化前后的值。
// 監聽一個 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    console.log(count, prevCount)
  }
)

// 直接監聽一個 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  console.log(count, prevCount)
}, {
  deep: true, // 深度監聽
  immediate: true // 初始化執行一次
})

// 監聽多個數據
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  console.log([foo, bar], [prevFoo, prevBar])
})

toRefs

把一個響應式對象轉換成普通對象,該普通對象的每個 property 都是一個 ref,和響應式對象 property 一一對應。可以被解構且保持響應性

<template>
  <div>
    <h1>解構響應式對象數據</h1>
    <p>Username: {{username}}</p>
    <p>Age: {{age}}</p>
  </div>
</template>

<script>
import { reactive, toRefs } from "vue";
export default {
  name: "DestructReactiveObject",
  setup() {
    const user = reactive({
      username: "haihong",
      age: 10000,
    });
    return { ...toRefs(user) };
  },
};
</script>

toRef

toRef 可以用來為一個 reactive 對象的屬性創建一個 ref。這個 ref 可以被傳遞并且能夠保持響應性。

setup() {
  const user = reactive({ age: 1 });
  const age = toRef(user, "age");

  age.value++;
  console.log(user.age); // 2

  user.age++;
  console.log(age.value); // 3
}

Provide/Inject

為了增加 provide 值和 inject 值之間的響應性,我們可以在 provide 值時使用 refreactive
當使用響應式 provide / inject 值時,建議盡可能將對響應式 property 的所有修改限制在定義 provide 的組件內部。然而,有時我們需要在注入數據的組件內部更新 inject 的數據。在這種情況下,我們建議 provide 一個方法來負責改變響應式 property。
最后,如果要確保通過 provide 傳遞的數據不會被 inject 的組件更改,我們建議對提供者的 property 使用 readonly。

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', readonly(location))
    provide('geolocation', readonly(geolocation))
    provide('updateLocation', updateLocation)
  }
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    const updateUserLocation = inject('updateLocation')

    return {
      userLocation,
      userGeolocation,
      updateUserLocation
    }
  }
}
</script>

生命周期函數

與 2.x 版本生命周期相對應的組合式 API
~~beforeCreate~~ -> 使用 setup()
~~created~~ -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

只需要將之前的生命周期改成onXXX的形式即可,需要注意的是created、beforeCreate兩個鉤子被刪除了,生命周期函數只能在setup函數里使用。

總結

使用組合式api還是需要一點時間來適應的,首先需要能區分ref和reactive,不要在基本類型和引用類型、響應式和非響應式對象之間搞混,其次就是如何拆分好每一個use函數,組合式api帶來了更好的代碼組織方式,但也更容易把代碼寫的更難以維護,比如setup函數巨長。

簡單總結一下升級思路,data選項里的數據通過reactive進行聲明,通過...toRefs()返回;computed、mounted等選項通過對應的computed、onMounted等函數來進行替換;methods里的函數隨便在哪聲明,只要在setup函數里返回即可。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容