為什么要使用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 來源十分清晰,因為它們都是被組合邏輯函式回傳的值
- 不存在命名空間沖突,可以通過解構任意命名
- 不再需要僅為邏輯復用而創建新的組件實體


常用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 = https://www.cnblogs.com/huangchengpei/archive/2023/04/22/reactive({ msg:"hello world" });
const updateData = https://www.cnblogs.com/huangchengpei/archive/2023/04/22/() => {
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 = "https://www.cnblogs.com/huangchengpei/archive/2023/04/22/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 = https://www.cnblogs.com/huangchengpei/archive/2023/04/22/3 // 錯誤
也可傳入一個 get 和 set 函式物件,創建一個可修改的計算狀態
const count = ref(0)
const sum = computed({
get: () => count.value + 1,
set: (value) => {
count.value = https://www.cnblogs.com/huangchengpei/archive/2023/04/22/value - 1
}
})
sum.value = 55
console.log(sum, count) // 1, 54
watchEffect
import { reactive, watchEffect } from "vue";
export default {
name: "WatchEffect",
setup() {
const data = https://www.cnblogs.com/huangchengpei/archive/2023/04/22/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 值時使用 ref 或 reactive,
當使用回應式 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 = 'https://www.cnblogs.com/huangchengpei/archive/2023/04/22/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 -> one rrorCaptured
只需要將之前的生命周期改成onXXX的形式即可,需要注意的是created、beforeCreate兩個鉤子被洗掉了,生命周期函式只能在setup函式里使用,
總結
使用組合式api還是需要一點時間來適應的,首先需要能區分ref和reactive,不要在基本型別和參考型別、回應式和非回應式物件之間搞混,其次就是如何拆分好每一個use函式,組合式api帶來了更好的代碼組織方式,但也更容易把代碼寫的更難以維護,比如setup函式巨長,
簡單總結一下升級思路,data選項里的資料通過reactive進行宣告,通過...toRefs()回傳;computed、mounted等選項通過對應的computed、onMounted等函式來進行替換;methods里的函式隨便在哪宣告,只要在setup函式里回傳即可,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/550895.html
標籤:其他
上一篇:線上多域名實戰
下一篇:返回列表
