一、 Composition API的簡(jiǎn)單介紹
Composition API也叫組合式API相恃,是Vue3.x的新特性辜纲。
通過(guò)創(chuàng)建 Vue 組件,我們可以將接口的可重復(fù)部分及其功能提取到可重用的代碼段中。僅此一項(xiàng)就可以使我們的應(yīng)用程序在可維護(hù)性和靈活性方面走得更遠(yuǎn)耕腾。然而见剩,我們的經(jīng)驗(yàn)已經(jīng)證明,光靠這一點(diǎn)可能是不夠的扫俺,尤其是當(dāng)你的應(yīng)用程序變得非常大的時(shí)候——想想幾百個(gè)組件苍苞。在處理如此大的應(yīng)用程序時(shí),共享和重用代碼變得尤為重要牵舵。
通俗的講:
沒(méi)有Composition API之前vue相關(guān)業(yè)務(wù)的代碼需要配置到option的特定的區(qū)域,中小型項(xiàng)目是沒(méi)有問(wèn)題的倦挂,但是在大型項(xiàng)目中會(huì)導(dǎo)致后期的維護(hù)性比較復(fù)雜畸颅,同時(shí)代碼可復(fù)用性不高。Vue3.x中的composition-api就是為了解決這個(gè)問(wèn)題而生的方援。
compositon-api提供了以下幾個(gè)函數(shù):
setup
ref
reactive
watchEffect
watch
computed
toRefs
生命周期的hooks
二没炒、setup
組件選項(xiàng)
新的 setup
組件選項(xiàng)在創(chuàng)建組件之前執(zhí)行,一旦 props
被解析犯戏,并充當(dāng)合成 API 的入口點(diǎn)送火。
提示:
由于在執(zhí)行 setup 時(shí)尚未創(chuàng)建組件實(shí)例,因此在 setup 選項(xiàng)中沒(méi)有 this先匪。這意味著种吸,除了props 之外,你將無(wú)法訪問(wèn)組件中聲明的任何屬性——本地狀態(tài)呀非、計(jì)算屬性或方法坚俗。
使用 setup
函數(shù)時(shí),它將接受兩個(gè)參數(shù):
props
context
讓我們更深入地研究如何使用每個(gè)參數(shù)岸裙。
2.1猖败、Props
setup
函數(shù)中的第一個(gè)參數(shù)是 props
。正如在一個(gè)標(biāo)準(zhǔn)組件中所期望的那樣降允,setup
函數(shù)中的 props
是響應(yīng)式的恩闻,當(dāng)傳入新的 prop 時(shí),它將被更新剧董。
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
注意:
但是幢尚,因?yàn)?props 是響應(yīng)式的,你不能使用 ES6 解構(gòu)翅楼,因?yàn)樗鼤?huì)消除 prop 的響應(yīng)性侠草。
如果需要解構(gòu) prop,可以通過(guò)使用 setup
函數(shù)中的 toRefs
來(lái)安全地完成此操作犁嗅。
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
2.2边涕、上下文
傳遞給 setup
函數(shù)的第二個(gè)參數(shù)是 context
。context
是一個(gè)普通的 JavaScript 對(duì)象,它暴露三個(gè)組件的 property:
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非響應(yīng)式對(duì)象)
console.log(context.attrs)
// 插槽 (非響應(yīng)式對(duì)象)
console.log(context.slots)
// 觸發(fā)事件 (方法)
console.log(context.emit)
}
}
context
是一個(gè)普通的 JavaScript 對(duì)象功蜓,也就是說(shuō)园爷,它不是響應(yīng)式的,這意味著你可以安全地對(duì) context
使用 ES6 解構(gòu)式撼。
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
attrs
和 slots
是有狀態(tài)的對(duì)象童社,它們總是會(huì)隨組件本身的更新而更新。這意味著你應(yīng)該避免對(duì)它們進(jìn)行解構(gòu)著隆,并始終以 attrs.x
或 slots.x
的方式引用 property扰楼。請(qǐng)注意,與 props
不同美浦,attrs
和 slots
是非響應(yīng)式的弦赖。如果你打算根據(jù) attrs
或 slots
更改應(yīng)用副作用,那么應(yīng)該在 onUpdated
生命周期鉤子中執(zhí)行此操作浦辨。
2.3蹬竖、setup組件的 property
執(zhí)行 setup
時(shí),組件實(shí)例尚未被創(chuàng)建流酬。因此币厕,你只能訪問(wèn)以下 property:
props
attrs
slots
emit
換句話說(shuō),你將無(wú)法訪問(wèn)以下組件選項(xiàng):
data
computed
methods
2.4芽腾、ref reactive 以及setup結(jié)合模板使用
在看setup結(jié)合模板使用之前旦装,我們首先得知道ref 和 reactive 方法。
如果 setup
返回一個(gè)對(duì)象則可以在模板中綁定對(duì)象中的屬性和方法摊滔,但是要定義響應(yīng)式數(shù)據(jù)的時(shí)候可以使用ref, reactive方法定義響應(yīng)式的數(shù)據(jù)同辣。
錯(cuò)誤寫(xiě)法:
<template>
{{msg}}
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
</template>
<script>
export default {
data() {
return {
}
},
setup() {
let msg = "這是setup中的msg";
let updateMsg = () => {
alert("觸發(fā)方法")
msg = "改變后的值"
}
return {
msg,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
正確寫(xiě)法一:
ref用來(lái)定義響應(yīng)式的 字符串、 數(shù)值惭载、 數(shù)組旱函、Bool類(lèi)型
import {
ref
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
</template>
<script>
import {
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let list = ref(["馬總", "李總", "劉總"])
let updateMsg = () => {
alert("觸發(fā)方法");
msg.value = "改變后的值"
}
return {
msg,
list,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
正確寫(xiě)法二:
reactive 用來(lái)定義響應(yīng)式的對(duì)象
import {
reactive
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變setup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
{{setupData.title}}
<br>
<button @click="updateTitle">更新setup中的title</button>
<br>
<br>
</template>
<script>
import {
reactive,
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let setupData = reactive({
title: "reactive定義響應(yīng)式數(shù)據(jù)的title",
userinfo: {
username: "張三",
age: 20
}
})
let updateMsg = () => {
alert("觸發(fā)方法");
msg.value = "改變后的值"
}
let updateTitle = () => {
alert("觸發(fā)方法");
setupData.title = "我是改變后的title"
}
return {
msg,
setupData,
updateMsg,
updateTitle
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
說(shuō)明:要改變r(jià)ef定義的屬性名稱(chēng)需要通過(guò) 屬性名稱(chēng).value
來(lái)修改,要改變r(jià)eactive中定義的對(duì)象名稱(chēng)可以直接
2.5描滔、使用 this
在 setup()
內(nèi)部棒妨,this
不會(huì)是該活躍實(shí)例的引用,因?yàn)?setup()
是在解析其它組件選項(xiàng)之前被調(diào)用的含长,所以 setup()
內(nèi)部的 this
的行為與其它選項(xiàng)中的 this
完全不同券腔。這在和其它選項(xiàng)式 API 一起使用 setup()
時(shí)可能會(huì)導(dǎo)致混淆。
二拘泞、 toRefs - 解構(gòu)響應(yīng)式對(duì)象數(shù)據(jù)
把一個(gè)響應(yīng)式對(duì)象轉(zhuǎn)換成普通對(duì)象纷纫,該普通對(duì)象的每個(gè) property 都是一個(gè) ref ,和響應(yīng)式對(duì)象 property 一一對(duì)應(yīng)陪腌。
<template>
<div>
<h1>解構(gòu)響應(yīng)式對(duì)象數(shù)據(jù)</h1>
<p>Username: {{username}}</p>
<p>Age: {{age}}</p>
</div>
</template>
<script>
import {
reactive,
toRefs
} from "vue";
export default {
name: "解構(gòu)響應(yīng)式對(duì)象數(shù)據(jù)",
setup() {
const user = reactive({
username: "張三",
age: 10000,
});
return {
...toRefs(user)
};
},
};
</script>
當(dāng)想要從一個(gè)組合邏輯函數(shù)中返回響應(yīng)式對(duì)象時(shí)辱魁,用 toRefs 是很有效的烟瞧,該 API 讓消費(fèi)組件可以 解構(gòu) / 擴(kuò)展(使用 … 操作符)返回的對(duì)象,并不會(huì)丟失響應(yīng)性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
})
// 對(duì) state 的邏輯操作
// ....
// 返回時(shí)將屬性都轉(zhuǎn)為 ref
return toRefs(state)
}
export default {
setup() {
// 可以解構(gòu)染簇,不會(huì)丟失響應(yīng)性
const { foo, bar } = useFeatureX()
return {
foo,
bar,
}
},
}
三参滴、computed - 計(jì)算屬性
<template>
<div>
<h1>解構(gòu)響應(yīng)式對(duì)象數(shù)據(jù)+computed</h1>
<input type="text" v-model="firstName" placeholder="firstName" />
<br>
<br>
<input type="text" v-model="lastName" placeholder="lastName" />
<br>
{{fullName}}
</div>
</template>
<script>
import {
reactive,
toRefs,
computed
} from "vue";
export default {
name: "解構(gòu)響應(yīng)式對(duì)象數(shù)據(jù)",
setup() {
const user = reactive({
firstName: "",
lastName: "",
});
const fullName = computed(() => {
return user.firstName + " " + user.lastName
})
return {
...toRefs(user),
fullName
};
},
};
</script>
四、readonly “深層”的只讀代理
傳入一個(gè)對(duì)象(響應(yīng)式或普通)或 ref锻弓,返回一個(gè)原始對(duì)象的只讀代理砾赔。一個(gè)只讀的代理是“深層的”,對(duì)象內(nèi)部任何嵌套的屬性也都是只讀的青灼。
<template>
<div>
<h1>readonly - “深層”的只讀代理</h1>
<p>original.count: {{original.count}}</p>
<p>copy.count: {{copy.count}}</p>
</div>
</template>
<script>
import { reactive, readonly } from "vue";
export default {
name: "Readonly",
setup() {
const original = reactive({ count: 0 });
const copy = readonly(original);
setInterval(() => {
original.count++;
copy.count++; // 報(bào)警告暴心,Set operation on key "count" failed: target is readonly. Proxy {count: 1}
}, 1000);
return { original, copy };
},
};
</script>
五、watchEffect
在響應(yīng)式地跟蹤其依賴(lài)項(xiàng)時(shí)立即運(yùn)行一個(gè)函數(shù)杂拨,并在更改依賴(lài)項(xiàng)時(shí)重新運(yùn)行它专普。
<template>
<div>
<h1>watchEffect - 偵聽(tīng)器</h1>
<p>{{data.count}}</p>
<button @click="stop">手動(dòng)關(guān)閉偵聽(tīng)器</button>
</div>
</template>
<script>
import {
reactive,
watchEffect
} from "vue";
export default {
name: "WatchEffect",
setup() {
const data = reactive({
count: 1,
num: 1
});
const stop = watchEffect(() => console.log(`偵聽(tīng)器:${data.count}`));
setInterval(() => {
data.count++;
}, 1000);
return {
data,
stop
};
},
};
</script>
六、watch 扳躬、watch 與watchEffect區(qū)別
對(duì)比watchEffect
脆诉,watch
允許我們:
- 懶執(zhí)行甚亭,也就是說(shuō)僅在偵聽(tīng)的源變更時(shí)才執(zhí)行回調(diào)贷币;
- 更明確哪些狀態(tài)的改變會(huì)觸發(fā)偵聽(tīng)器重新運(yùn)行;
- 訪問(wèn)偵聽(tīng)狀態(tài)變化前后的值
更明確哪些狀態(tài)的改變會(huì)觸發(fā)偵聽(tīng)器重新運(yùn)行亏狰;
<template>
<div>
<h1>watch - 偵聽(tīng)器</h1>
<p>count1: {{data.count1}}</p>
<p>count2: {{data.count2}}</p>
<button @click="stopAll">Stop All</button>
</div>
</template>
<script>
import {
reactive,
watch
} from "vue";
export default {
name: "Watch",
setup() {
const data = reactive({
count1: 0,
count2: 0
});
// 偵聽(tīng)單個(gè)數(shù)據(jù)源
const stop1 = watch(data, () =>
console.log("watch1", data.count1, data.count2)
);
// 偵聽(tīng)多個(gè)數(shù)據(jù)源
const stop2 = watch([data], () => {
console.log("watch2", data.count1, data.count2);
});
setInterval(() => {
data.count1++;
}, 1000);
return {
data,
stopAll: () => {
stop1();
stop2();
},
};
},
};
</script>
訪問(wèn)偵聽(tīng)狀態(tài)變化前后的值
<template>
<div>
<h1>watch - 偵聽(tīng)器</h1>
<input type="text" v-model="keywords" />
</div>
</template>
<script>
import {
ref,
watch
} from "vue";
export default {
name: "Watch",
setup() {
let keywords = ref("111");
// 偵聽(tīng)單個(gè)數(shù)據(jù)源
watch(keywords, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
return {
keywords
};
},
};
</script>
懶執(zhí)行役纹,也就是說(shuō)僅在偵聽(tīng)的源變更時(shí)才執(zhí)行回調(diào)
<template>
<div>
<h1>watch - 偵聽(tīng)器</h1>
<p>num1={{num1}}</p>
<p>num2={{num2}}</p>
</div>
</template>
<script>
import {
ref,
watch,
watchEffect
} from "vue";
export default {
name: "Watch",
setup() {
let num1 = ref(10);
let num2 = ref(10);
// 偵聽(tīng)單個(gè)數(shù)據(jù)源
watch(num1, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
watchEffect(() => console.log(`watchEffect偵聽(tīng)器:${num2.value}`));
return {
num1,
num2
};
},
};
</script>
七、組合式api生命周期鉤子
你可以通過(guò)在生命周期鉤子前面加上 “on” 來(lái)訪問(wèn)組件的生命周期鉤子暇唾。
下表包含如何在 setup () 內(nèi)部調(diào)用生命周期鉤子:
選項(xiàng)式 API | Hook inside setup
|
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
因?yàn)?setup
是圍繞 beforeCreate
和 created
生命周期鉤子運(yùn)行的促脉,所以不需要顯式地定義它們。換句話說(shuō)策州,在這些鉤子中編寫(xiě)的任何代碼都應(yīng)該直接在 setup
函數(shù)中編寫(xiě)瘸味。
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
八、Provider Inject
通常够挂,當(dāng)我們需要將數(shù)據(jù)從父組件傳遞到子組件時(shí)旁仿,我們使用 props。想象一下這樣的結(jié)構(gòu):你有一些深嵌套的組件孽糖,而你只需要來(lái)自深嵌套子組件中父組件的某些內(nèi)容枯冈。在這種情況下,你仍然需要將 prop 傳遞到整個(gè)組件鏈中办悟,這可能會(huì)很煩人尘奏。
對(duì)于這種情況,我們可以使用 provide
和 inject
對(duì)父組件可以作為其所有子組件的依賴(lài)項(xiàng)提供程序病蛉,而不管組件層次結(jié)構(gòu)有多深炫加。這個(gè)特性有兩個(gè)部分:父組件有一個(gè) provide
選項(xiàng)來(lái)提供數(shù)據(jù)瑰煎,子組件有一個(gè) inject
選項(xiàng)來(lái)開(kāi)始使用這個(gè)數(shù)據(jù)。
[圖片上傳失敗...(image-54fd1a-1609758009529)]
8.1 琢感、非組合式api中的寫(xiě)法:
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
8.2丢间、組合式api中的寫(xiě)法:
Provider:
在 setup()
中使用 provide
時(shí),我們首先從 vue
顯式導(dǎo)入 provide
方法驹针。這使我們能夠調(diào)用 provide
時(shí)來(lái)定義每個(gè) property烘挫。
provide
函數(shù)允許你通過(guò)兩個(gè)參數(shù)定義 property:
- property 的 name (
<String>
類(lèi)型) - property 的 value
使用 MyMap
組件,我們提供的值可以按如下方式重構(gòu):
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
Inject:
在 setup()
中使用 inject
時(shí)柬甥,還需要從 vue
顯式導(dǎo)入它饮六。一旦我們這樣做了,我們就可以調(diào)用它來(lái)定義如何將它暴露給我們的組件苛蒲。
inject
函數(shù)有兩個(gè)參數(shù):
- 要注入的 property 的名稱(chēng)
- 一個(gè)默認(rèn)的值 (可選)
使用 MyMarker
組件卤橄,可以使用以下代碼對(duì)其進(jìn)行重構(gòu):
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
Provider Inject 響應(yīng)性
父組件:
import {
provide,
ref,
reactive
} from 'vue'
setup() {
const location = ref('北京')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = '上海'
}
provide('location', location);
provide('geolocation', geolocation);
return {
updateLocation
}
}
<button @click="updateLocation">改變location</button>
子組件:
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>