[Vue 3] 第二課 Vue組件精通課
Adrian Chen

Vue組件基礎

在Vue中一直有一個論述,叫做:“Everything is components”(一切皆組件)。可見組件在Vue開發中舉足輕重的地位。那麼組件究竟是什麼呢?

我們可以把組件理解爲:同時含有\部分、\部分、\部分,實現從元素、樣式到邏輯處理的一站式完全功能體。 通常,這個功能體被定義在一個.vue 檔案中。也就是說,我們可以認爲,一個.vue檔案就是一個組件。

例如,一個標準的頁面可以被分爲header(頭部)、main(主要部分)、sidebar(側邊欄)、footer(尾部)。其中每一個部分,都有自己的元素(HTML)、樣式(Style)和邏輯(JavaScript),所以一個頁面可以就可以被分爲若干組件。同時,這個頁面在Vue中也是一個.vue檔案,故這個頁面也可以看作是一個大組件。因此,在學習的前期,我們不妨將組件分爲頁面組件功能組件 。頁面組件是Mount在路由中,顯示一整個頁面的組件,而功能組件則是放到頁面組件中,是頁面的某一部分。

定義組件

我們明白了組件的概念,自然明白該怎樣去定義組件。只需要新建一個.vue文件,包含這三個部分,一個組件就被定義好了。

1
2
3
4
5
6
7
8
9
<!-- ButtonConter.vue -->
<script>
</script>

<template>
</template>

<style>
</style>

使用組件

一個組件被定義了之後,必須被放到頁面上,才能夠顯示出來。也就是將某個組件放到更大的組件中去。因此,我們需要在更大的組件中去引入並使用這個組件。

首先,我們要在組件的<script></script>部分引入該組件:

1
2
3
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

然後,我們可以在組件的<template></template>部分使用該組件:

1
2
3
<template>
<ButtonCounter/>
</template>

這樣,這個組件就會被添加到更大的組件中,可以在父組件的相應位置顯示了。

組件Props

組件的一個重要的特性就是將一個功能的集合體抽離出來。抽離出來的東西具有可復現性。

例如,我們有一個組件,會在很多個頁面中都用到,但是每個頁面中,該組件展示的信息不一樣。那我們該怎麼做呢?難道要定義很多個組件嗎?當然不是。因爲該組件的基本結構都是確定的,只是其中微小的部分有差異。那這種情況下,我們就需要父組件告訴子組件,到底要展示什麼內容,也就是說,需要父組件向子組件中傳送props(屬性)。

要實現這個過程,需要執行以下步驟:

  1. 在子組件中定義需要傳入的props
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
import { defineProps } from 'vue';
const props = defineProps({
nihao: { // nihao是需要傳入的props名字
type: String, // props的類型
default: "" // props預設的值
}
})
</script>

<template>
<!-- 渲染到template,直接寫props的名字即可 -->
<div> {{ nihao }} </div>
</template>
  1. 在父組件中傳入這個props的值
1
2
3
4
5
<template>
<Component nihao="你好"/>
<Component nihao="Hello"/>
<Component nihao="こんにちは"/>
</template>

渲染結果如下:

1
2
3
<div>你好</div>
<div>Hello</div>
<div>こんにちは</div>

上面的例子實現了從父組件向子組件傳送一個String類型的props名叫nihao,我們可以看到,當向子組件傳送不同的props值,子組件渲染到頁面上的內容也不同。

如果我們想要將父組件中的一個JS變數作爲props傳入子組件,我們也可以使用模板語法來完成:

1
<Component :nihao="variable"/>

這樣子,當veriable變數發生改變的時候,傳入子組件的props也相應發生改變。如果你想要將數字或者Boolean等類型傳入子組件,就必須使用模板語法纔可以。因爲預設的靜態傳入方式只能向裏面傳送字串。

事件處理

如果說父組件向子組件交流是透過傳送props實現的,那麼子組件如果有內容需要報告給父組件,又該怎麼做呢?是透過一個叫做事件處理的程式來進行的。

完成該過程有兩種辦法,第一種是比較簡單的方法,只能在子組件中某些元素觸發JS事件之後,向父組件傳送一些固定的內容。實現過程如下:

1
<button @click="$emit('event')">Click me</button>

使用$emit()來向父組件傳送資料。event是事件的名字。

在父組件中,可以透過事件監聽來處理子組件傳入的資料:

1
<Component @event="func"/>
1
2
3
const func = () => {
console.log("Hello, world.")
}

以上表示,一旦監聽到了子組件的event事件,就執行func函式。

我們也可以在子組件向上傳送事件的過程中向上傳送資料:

1
<button @click="$emit('event', 1)">Click me</button>

event是事件名稱,1是要向上傳入的內容。該內容將作爲父組件中func函式的引數傳入。

1
<Component @event="func"/>
1
2
3
const func = (e) => {
console.log(e)
}

除了這種簡單的方式,我們還可以將emit寫在JS部分,這樣我們就不必拘泥於按鈕的點擊、輸入框的輸入等等,可以在任何想拋出事件的時候,就將事件拋給父組件。

1
2
3
4
5
6
7
// 定義emits
const emit_event = defineEmits(["event"])

// 拋出事件
onMounted(() => {
emit_event("event", 1)
})

組件v-model

我們上一節課講過,v-model用於模板和資料的雙向綁定。我們也可以在開發組件的時候,爲我們的組件添加上這一屬性。

當我們爲組件添加了這一屬性後,父組件綁定到子組件的這一變數就可以直接在子組件中使用了,就像是在子組件中定義的一樣。在子組件中改變該變數的值,父組件中也會相應改變。

1
2
3
4
5
6
// Child.vue
const model = defineModel()

const update_model = () => {
model ++
}

父組件中可以透過v-model綁定一個JS變數:

1
2
3
4
<!-- Father.vue -->
<template>
<Child v-model="val"/>
</template>

這樣,我們就透過v-model,將父組件中val這個變數綁定到了子組件中model這個變數。也可以說,val和model是同一個變數,只不過在父組件和子組件中分別叫val和model而已。

在子組件中執行的update_model()函式,在改變子組件model變數的同時,由於model變數和父組件val變數互相綁定,故父組件中val變數也同步改變。反之亦然。

關於組件v-model的其餘內容,包括組件多個v-model的實現,請讀者自行查閱Vue官方文檔。模式基本相同,在此不再贅述。

插槽(Slot)

還記得我們遙遠遙遠的第一節課上,我和各位說了一件什麼事情嗎?

單葉標籤和雙葉標籤。

我曾經在課上說過,這兩個詞語是我自己創造出來的,但很應景。所謂的雙葉標籤,兩個標籤葉之間是可以裹挾一些東西的,從而可以渲染到頁面上。而單葉標籤由於只有一個標籤葉,所以沒有辦法在其中夾東西。

不知道大家發現了沒有,在之前我們所定義的所有組件,在父組件中使用的時候,幾乎都是單葉組件,儘管組件也可以被寫成雙葉形式:

1
<Component></Component>

但是在這種情況下,裏面夾任何的東西都是不會被顯示和渲染的。

要實現在裏面放東西,並且還能夠渲染到子組件的相應位置,我們就需要用到“插槽”這個東西。在子組件需要渲染的地方加上一個插槽,就可以引導着雙葉之間的內容渲染到相應的部位了。例如,我們在子組件中可以這樣定義:

1
2
3
4
<!--Child.vue-->
<div id="nihao">
<slot></slot>
</div>

然後在父組件中就可以在雙葉之間添加內容了:

1
2
3
4
<!--Father.vue-->
<Child>
<span>你好!</span>
</Child>

<slot></slot>關鍵字即爲插槽的標誌,代表着父組件雙葉之間的內容將被替換到這裏。上述內容渲染之後是這樣的:

1
2
3
<div id="nihao">
<span>你好!</span>
</div>

可以看到,雙葉之間的內容被替換到了子組件中<slot></slot>的位置。

至於多插槽的配置,還請讀者自行查閱Vue官方文檔,文檔的內容很好理解。

重要的內置組件

在Vue框架中,有幾個組件是Vue官方幫我們寫好的,我們可以直接使用去實現相應的功能。下面筆者將介紹其中兩個使用比較廣泛的組件。

Transition

<Transition> 會在一個元素或元件進入和離開 DOM 時套用動畫。

進入或離開可以由以下的條件之一觸發:

  • 由 v-if 觸發的切換
  • 由 v-show 觸發的切換
  • 由特殊元素 <component> 切換的動態元件
  • 改變特殊的 key 屬性

其中,前兩項是我們比較常用的,第三項將在下一個內置組件中詳細介紹,最後一項不是很常見。

下面是官方文檔中提供給我們的用法案例:

1
2
3
4
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
1
2
3
4
5
6
7
8
9
10
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
opacity: 0;
}

下面我們來解釋一下這些CSS動畫效果的實現。
總共有 6 個應用於進入與離開過渡效果的 CSS class。

image

上面這張圖片很好地展示了這個過程。因此,我們就可以在CSS中,將這些狀態或過程的CSS樣式都定義出來,這樣Transition組件就會幫我們實現這個過渡的過程。

這是官方文檔的解釋:

  1. v-enter-from:進入動畫的起始狀態。 在元素插入之前添加,在元素插入完成後的下一幀移除。

  2. v-enter-active:進入動畫的生效狀態。 應用於整個進入動畫階段。 在元素插入之前添加,在過渡或動畫完成之後移除。 這個 class 可以用來定義進入動畫的持續時間、延遲與速度曲線類型。

  3. v-enter-to:進入動畫的結束狀態。 在元素插入完成後的下一幀被添加 (也就是 v-enter-from 被移除的同時),在過渡或動畫完成之後移除。

  4. v-leave-from:離開動畫的起始狀態。 在離開過渡效果被觸發時立即添加,在一幀後被移除。

  5. v-leave-active:離開動畫的生效狀態。 應用於整個離開動畫階段。 在離開過渡效果被觸發時立即添加,在過渡或動畫完成之後移除。 這個 class 可以用來定義離開動畫的持續時間、延遲與速度曲線類型。

  6. v-leave-to:離開動畫的結束狀態。 在一個離開動畫被觸發後的下一幀被添加 (也就是 v-leave-from 被移除的同時),在過渡或動畫完成之後移除。

爲過渡效果命名

上面我們套用的動畫,包括我們寫的CSS,是沒有命名的。如果我們有好幾個需要套用不同動畫的元素,這顯然是不夠的。因此,我們可以把每一個動畫效果命名,然後選擇每一個需要套用動畫的元素使用哪一個動畫效果。

例如,我們需要名叫“fade”的一套動畫,我們首先要在CSS中,把前面的效果class改成:

1
2
3
4
5
6
7
8
9
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

可以看到,僅僅只是把默認的“v”換成了需要命名的名字而已。

然後,我們可以在<Transition>組件的name屬性去套用這個命名:

1
2
3
<Transition name="fade">
...
</Transition>

KeepAlive

要明白這個組件的用處,我們首先要引入動態組件的概念。有些情況下,我們會需要在兩個組件之間互相切換,比如標籤頁。

在這種情況下,我們可以使用<component>標籤來代替不同的組件:

1
<component :is="activeComponent" />

其中,activeComponent是一個JS變數,其值是已經引入的子組件的名字或者組件實例。

預設情況下,一個元件實例在被替換掉後會被銷毀。 這會導致它遺失其中所有已變更的狀態-當這個元件再一次被顯示時,會建立一個只帶有初始狀態的新實例。

在切換時建立新的元件實例通常是有意義的,但有些時候,我們的確想要元件能在被「切走」的時候保留它們的狀態。 要解決這個問題,我們可以用 <KeepAlive> 內建元件將這些動態元件包裝起來:

1
2
3
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>

組件庫

組件是功能的集合體,而許許多多這樣的組件放到一起,就形成了組件庫。Vue 3有大量的第三方組件庫,包含了許許多多可以使用的組件。這些組件庫極大拓展了Vue 3的能力。

國內目前比較流行的組件庫是Element Plus