Vue3 - 組件(Component)基礎
tags: Vue
category: Front-End
description: Vue3 - 組件(Component)基礎
created_at: 2022/07/16 18:00:00
回到 手把手開始寫 Vue3
前言
雖然前面幾篇也有一些有稍微用到組件了,但還是稍微說明一下組件的基礎
一樣先拿官方的圖
為什麼要把組件抽離出來呢?
簡單說因為畫面上會有可以重複利用的東西可以抽出來,例如像是上面這張圖有 2
個 Article
與 3
個 Item
,他們應該都長得差不多,只是內容換一下。
又或者是有多個頁面使用到同一個東西,例如像是 Header
、Navbar
、Footer
...之類的,可能每一頁都長一樣,也可以抽出來,這樣未來改也只需要改一次。
定義組件
定義組件其實前面已經偷偷出現過了(?),就是那個 src/components/Child.vue
。
這邊先以 src/components/Post.vue
當作範例
傳遞 props
這個其實也有在前面出現過了(?)
基本的用法
src/App.vue
<script setup>
import Post from "./components/Post.vue";
</script>
<template>
<Post title="Post A" />
</template>
src/components/Post.vue
<script setup>
defineProps(["title"]);
</script>
<template>
{{ title }}
</template>
而如果你要在 setup
時就拿到 props
的話,只要宣告變數把 defineProps
接起來就可以,像是這樣
const props = defineProps(["title"]);
增加型別警告
可以設定以下的 type
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
假設我要設定 title
的形態要是字串
defineProps({
title: String,
});
這樣如果使用時傳入 100
會看到這個警告,但是畫面上還是會呈現資料,因為他只是警告
[Vue warn]: Invalid prop: type check failed for prop "title". Expected String with value "100", got Number with value 100.
at <Post title=100 >
at <App>
增加驗證
拿官方的來說明
defineProps({
// 指定一個型別
propA: Number,
// 可以放多個型別
propB: [String, Number],
// 必填欄位
propC: {
type: String,
required: true
},
// 預設值
propD: {
type: Number,
default: 100
},
// 物件的預設值
propE: {
type: Object,
// 物件或陣列的預設值必須從一個函數回傳
// 這個函數會有一個 rawProps 的參數可以用
// 以這次 <Post title="Post A" /> 為例
// rawProps 就是 {title: 'Post A'}
default(rawProps) {
return { message: 'hello' }
}
},
// 自訂驗證函數
propF: {
validator(value) {
// 假設值必須是這三個字串之一
return ['success', 'warning', 'danger'].includes(value)
}
},
// 使用函數生成預設值
propG: {
type: Function,
default() {
return 'Default function'
}
}
})
傳遞事件
組件之間使用事件 event
溝通(雖然你硬要用 props
也不是不行,最後會提到)
可以自訂事件來監聽,然後讓子組件去觸發(emit
),然後父組件在做處理。
舉例來說
src/components/Post.vue
<script setup>
defineProps(["title"]);
</script>
<template>
<div>
{{ title }}
<button
class="bg-red-500 text-white px-3 py-1"
@click="$emit('parent-method')"
>
x
</button>
</div>
</template>
src/App.vue
<script setup>
import Post from "./components/Post.vue";
const parentMethod = () => {
console.log("hi");
};
</script>
<template>
<Post title="Post A" @parent-method="parentMethod" />
</template>
這樣子只要按下子組件的按鈕,上層的 parentMethod
就會被觸發。
defineEmits()
如果要在子組件的 setup
階段使用 emit
,就需要使用這個 defineEmits
。
<script setup>
const emit = defineEmits(["parent-method"]);
defineProps(["title"]);
const hello = () => {
console.log("hello");
emit("parent-method");
};
</script>
<template>
<div>
{{ title }}
<button class="bg-red-500 text-white px-3 py-1" @click="hello">x</button>
</div>
</template>
插入一個需要傳遞參數的範例
直接丟在 emit(, args...)
第二個參數之後就可以
const parentMethod = (x, y) => {
console.log("hi", x, y);
};
對應到
const hello = () => {
emit("parent-method", 5, 10);
};
這樣子 parentMethod
就會收到 x=5
、y-10
。
加入驗證
事件一樣可以加入驗證,但是一樣只是發出警告,所以程式還是會繼續進行,不過可以額外做處理(例如發出錯誤搭配 onErrorCaptured
)。
<script setup>
defineProps(["title"]);
const emit = defineEmits({
"parent-method": (x, y) => {
return x && y;
},
});
const hello = () => {
emit("parent-method");
};
</script>
<template>
<div>
{{ title }}
<button class="bg-red-500 text-white px-3 py-1" @click="hello">x</button>
</div>
</template>
像是上面這樣,只有在 x
與 y
都有值才會正常,不然就會發出警告。
[Vue warn]: Invalid event arguments: event validation failed for event "parent-method".
事件總結
最後來說一下用 props
傳跟用 emit
傳這件事。
props
: 一般應該是希望單純從上往下傳遞資訊
emit
: 是以事件的方式向上觸發事件
因為我自己是沒什麼在用 devtool
,所以不太清楚在 devtool
當中兩者的差異。
就算光字面上看,你用 props
的話,等於是把函數丟給了子元件,在子元件去影響了父元件的狀態。(很髒,扯在一起)
而使用 emit
的話則是讓子組件去通知父組件要執行某個函數,就算他改變了狀態也是改變自己的。(自己顧好自己,很棒)
兩者差異應該就很明顯了。
插槽 slot
目前我們看到的組件都長這樣
<Post title="Post A" />
而那個 slot
就是塞在標籤之間的,只是目前看到的都是簡寫,直接閉合。
<Post title="Post A">Content</Post>
像是這樣寫的話,那個 Content
就是 slot
的值。
而在子組件,只要簡單的放入 <slot></slot>
標籤,就可以渲染出來。
<script setup>
defineProps(["title"]);
</script>
<template>
<div>
{{ title }}
</div>
<div>
<slot>Default Content</slot>
</div>
</template>
另外中間也可以放入預設值,像是上面的 Default Content
,如果父組件沒有包住內容則會輸出預設內容。
而 slot
也可以有多個,使用命名的方式去指定到哪個插槽,詳細可以查看官方
動態組件
在 Vue
裡面有一個滿不錯的功能,之前比賽的時候超愛用(可以少寫滿多code
)
有時候有多個組件,想透過變數快速切換或同時顯示,可以像這樣定義
<script setup>
import Post from "./components/Post.vue";
</script>
<template>
<component :is="Post" title="Post A"></component>
</template>
像是這樣子就等同於
<Post title="Post A" />
這樣的話就可以宣告一個變數,存放要渲染的組件,然後使用這個方式寫。