Vue 3.0 在今年2月7日已經(jīng)正式轉(zhuǎn)正,經(jīng)過這兩年的嘗鮮和測試,已經(jīng)比較穩(wěn)定,個(gè)人建議在支持現(xiàn)代瀏覽器的項(xiàng)目中都可以使用Vue 3.0+來進(jìn)行開發(fā),原生支持TypeScript這點(diǎn)是真的香,Vue 3的好處還是很多的,好了話不多說,這次我們就來聊一聊 <script setup>
語法糖里,究竟該如何自定義組件?
目錄
自定義組件,我們一般需要實(shí)現(xiàn)這幾個(gè)點(diǎn):
- props —— 定義屬性
- events —— 定義事件
- slots —— 插槽
- expose —— 定義組件可供外部訪問的內(nèi)容
- v-model —— 自定義組件實(shí)現(xiàn)雙向數(shù)據(jù)綁定
- provide 與 inject
為什么使用 script setup ?
剛開始嘗試Vue 3的時(shí)候用的組合式API都是這樣的寫法
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
...
return {
...
}
}
}
乍一看感覺不如原來的Options API
啊,什么邏輯都寫到setup里面了,好臃腫的一個(gè)方法。
但事實(shí)上,Vue 3在對響應(yīng)式重新設(shè)計(jì)之后,讓我們可以通過ref
和reactive
方法來創(chuàng)建聲明一個(gè)響應(yīng)式變量,也就意味著我們很多邏輯可以不依賴this.data進(jìn)行開發(fā)和編寫,甚至一些響應(yīng)式邏輯都可以多組件復(fù)用。
在了解了這些點(diǎn)之后,即便我們可以將邏輯拆分獨(dú)立,通過解構(gòu)的方式導(dǎo)入setup中,讓我們代碼更加高內(nèi)聚低耦合,但我們依然避免不了,復(fù)雜組件需要return無數(shù)的方法或者變量提供給模板使用。
但是,在<script setup>
語法糖出現(xiàn)之后,這個(gè)問題得到了極大的改善,不管是import的組件也好,還是聲明的變量也罷,都可以不用一個(gè)個(gè)return了。
編譯器會(huì)幫助我們轉(zhuǎn)換成setup()
函數(shù)的內(nèi)容,這意味著與普通的 <script>
只在組件被首次引入的時(shí)候執(zhí)行一次不同,<script setup>
中的代碼會(huì)在每次組件實(shí)例被創(chuàng)建的時(shí)候執(zhí)行。
所以,還不趕緊學(xué)起來?
自定義組件
props 與 events
在 <script setup>
中必須使用 defineProps
和 defineEmits
API 來聲明 props
和 emits
,它們具備完整的類型推斷并且在 <script setup>
中是直接可用的:
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
-
defineProps
和defineEmits
都是只在<script setup>
中才能使用的編譯器宏。他們不需要導(dǎo)入且會(huì)隨著<script setup>
處理過程一同被編譯掉。 -
defineProps
接收與props
選項(xiàng)相同的值,defineEmits
也接收emits
選項(xiàng)相同的值。 -
defineProps
和defineEmits
在選項(xiàng)傳入后,會(huì)提供恰當(dāng)?shù)念愋屯茢唷?/li> - 傳入到
defineProps
和defineEmits
的選項(xiàng)會(huì)從 setup 中提升到模塊的范圍。因此,傳入的選項(xiàng)不能引用在 setup 范圍中聲明的局部變量。這樣做會(huì)引起編譯錯(cuò)誤。但是,它可以引用導(dǎo)入的綁定,因?yàn)樗鼈円苍谀K范圍內(nèi)。
以上是官方文檔對于定義Props和Emits的相關(guān)介紹,筆者覺得說的還是很清楚的,這里在圈一下重點(diǎn):
- 在
<script setup>
中不需要導(dǎo)入defineProps
和defineEmits
- 定義
props
時(shí)傳入的參數(shù)與options API
中props
選項(xiàng)一致 - 在TS中可以直接純類型聲明
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>();
這里肯定很多小伙伴有疑問了,那如果用TS做純類型的聲明,默認(rèn)值該怎么定義呢?
吶,看這里!
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
還有一個(gè)withDefaults
編譯器宏
上面代碼會(huì)被編譯為等價(jià)的運(yùn)行時(shí) props 的 default
選項(xiàng)。此外,withDefaults
輔助函數(shù)提供了對默認(rèn)值的類型檢查,并確保返回的 props
的類型刪除了已聲明默認(rèn)值的屬性的可選標(biāo)志。
Slots
大部分情況,我們可能需要根據(jù)外部的slots的傳入情況來決定組件內(nèi)部的展示部分,在模板中我們可以通過$slots
來訪問所有的默認(rèn)插槽以及具名插槽
比如:Auth組件校驗(yàn)不通過時(shí),隱藏slots的內(nèi)容
那么我們可以在模板中這樣來做
// page
<Auth auth="commit">
<button>提交<button>
</Auth>
// components
<template>
<slot v-if="condition" />
</template>
這樣既不會(huì)增加dom節(jié)點(diǎn)也可以增加邏輯來處理按鈕權(quán)限的問題
再比如:組件內(nèi)部有多個(gè)插槽及具名插槽的時(shí)候
form表單中的form-item
組件是可以自定義插槽來覆蓋默認(rèn)的input內(nèi)容的,在模板中就可以通過$slots來訪問具體的插槽對象
// page
<form-item>
<template #input>
自定義form input內(nèi)容
</template>
</form-item>
// components
<template>
<slot v-if="$slots.input" name="input" />
<input v-else />
</template>
我們很少情況會(huì)在setup中操作Slots,但是它依然提供了useSlots
方法來幫我們操作組件的Slots
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
</script>
Expose
使用 <script setup>
的組件是默認(rèn)關(guān)閉的,也即通過模板 ref 或者 $parent
鏈獲取到的組件的公開實(shí)例,不會(huì)暴露任何在 <script setup>
中聲明的綁定。
我們組件內(nèi)部的狀態(tài)和方法可能會(huì)很多,比如一些復(fù)雜的組件,但是有些狀態(tài)外部或許需要在適當(dāng)時(shí)候操作或訪問的時(shí)候,我們就需要考慮那些屬性和方法是可以暴露給外部的
這個(gè)時(shí)候我們就可以使用defineExpose
來聲明綁定
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
當(dāng)父組件通過模板 ref 的方式獲取到當(dāng)前組件的實(shí)例,獲取到的實(shí)例會(huì)像這樣 { a: number, b: number }
(ref 會(huì)和在普通實(shí)例中一樣被自動(dòng)解包)
v-model
v-model其實(shí)是一個(gè)語法糖
它代表聲明了一個(gè)modelValue
的屬性以及一個(gè)update:modelValue
的事件
Vue 3 中你可以通過 propsName + update:propsName
來自定義v-model
也就是說:一個(gè)組件里可以定義多個(gè)v-model
// page
<cmp v-model:foo="xxx" v-model:bar="xxxx" />
// components
<script setup>
interface Props {
foo: string
bar: string
}
const props = defineProps<Props>();
const emits = defineEmits(["update:foo", "update:bar"]);
</script>
provide 與 inject
這里需要用到 provide() 與 inject()
父組件:
<script setup>
import { provide } from "vue";
const userObj = ref<User>(...);
provide("user", userObj);
const fn = () => {
...
}
provide("change", fn);
</script>
子組件:
<script setup>
import { inject } from "vue";
const injectUserObj = inject("user");
const injectFn = inject("change");
</script>
總結(jié)
目前筆者整理的就這么多,我自己在開發(fā)組件的過程中常用到的目前也就這些知識點(diǎn)
當(dāng)然還有函數(shù)式組件相關(guān)的寫法,這個(gè)可能大部分人不常會(huì)用到,組件庫考慮到動(dòng)態(tài)性或許會(huì)選擇
不過我們做業(yè)務(wù)組件時(shí)我還是建議大家使用單文件組件
維護(hù)性還是高了不少的
喜歡的就點(diǎn)贊收藏起來吧~