Vue3 - 組件(Component)基礎

tags: Vue
category: Front-End
description: Vue3 - 組件(Component)基礎
created_at: 2022/07/16 18:00:00

cover image


回到 手把手開始寫 Vue3


前言

雖然前面幾篇也有一些有稍微用到組件了,但還是稍微說明一下組件的基礎


一樣先拿官方的圖

cover image

為什麼要把組件抽離出來呢?

簡單說因為畫面上會有可以重複利用的東西可以抽出來,例如像是上面這張圖有 2Article3Item,他們應該都長得差不多,只是內容換一下。

又或者是有多個頁面使用到同一個東西,例如像是 HeaderNavbarFooter...之類的,可能每一頁都長一樣,也可以抽出來,這樣未來改也只需要改一次。


定義組件

定義組件其實前面已經偷偷出現過了(?),就是那個 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=5y-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>

像是上面這樣,只有在 xy 都有值才會正常,不然就會發出警告。

[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" />

這樣的話就可以宣告一個變數,存放要渲染的組件,然後使用這個方式寫。




最後更新時間: 2022年07月16日.