手把手教你Vue3自定義組件(setup語法糖版)

Vue3 setup Cmp.png

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ì)之后,讓我們可以通過refreactive方法來創(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> 中必須使用 definePropsdefineEmits API 來聲明 propsemits ,它們具備完整的類型推斷并且在 <script setup> 中是直接可用的:

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>
  • definePropsdefineEmits 都是只在 <script setup> 中才能使用的編譯器宏。他們不需要導(dǎo)入且會(huì)隨著 <script setup> 處理過程一同被編譯掉。
  • defineProps 接收與 props 選項(xiàng)相同的值,defineEmits 也接收 emits 選項(xiàng)相同的值。
  • definePropsdefineEmits 在選項(xiàng)傳入后,會(huì)提供恰當(dāng)?shù)念愋屯茢唷?/li>
  • 傳入到 definePropsdefineEmits 的選項(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)入definePropsdefineEmits
  • 定義props時(shí)傳入的參數(shù)與options APIprops選項(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)贊收藏起來吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容