在 Vue 應用中,父子組件之間的數據傳遞和事件通訊是開發中最常見的操作。Vue 提供了 props
和 emit
機制,用於解決父組件向子組件傳遞數據,以及子組件向父組件反饋操作的需求。結合 TypeScript,這些機制可以進一步提高數據的安全性和事件的精確性。本文將介紹如何使用 props
和 emit
進行數據與事件的傳遞,並展示如何結合 TypeScript 進行型別檢查。
props
和 emit
?props
:從父組件向子組件傳遞數據,是單向數據流的一部分。
emit
:用於子組件向父組件發送自定義事件,以便父組件響應子組件的操作。
這兩者的組合,使得 Vue 組件之間的通訊更加靈活和高效。
props
傳遞數據讓我們先來看一個使用 props
傳遞數據的範例。(檔案: src/components/ChildComponent.vue
)
<script lang="ts" setup>
defineProps<{ message: string }>();
</script>
<template>
<div>
<p>來自父組件的數據:{{ message }}</p>
</div>
</template>
在這個範例中,ChildComponent
使用 props
接收來自父組件的 message
。defineProps
用於聲明 props
並與 TypeScript 結合,確保 message
的類型是 string
。
接下來,在父組件中傳遞這個數據:(檔案: src/components/ParentComponent.vue
)
<script lang="ts" setup>
import ChildComponent from './ChildComponent.vue';
</script>
<template>
<ChildComponent message="Hello from Parent" />
</template>
父組件通過 message
屬性將數據傳遞給子組件 ChildComponent
,這是 props
的基本用法。
emit
傳遞事件當子組件需要向父組件傳遞信息或觸發事件時,我們可以使用 emit
。
<!-- src/components/ChildComponent.vue -->
<script lang="ts" setup>
const emit = defineEmits<{
'update:message': [message: string];
}>();
const sendMessage = () => {
emit('update:message', 'Hello from Child');
};
</script>
<template>
<button @click="sendMessage">點擊我</button>
</template>
這裡,ChildComponent
使用 emit
發送了一個 update:message
事件,並傳遞了一個字符串作為參數。defineEmits
的語法讓 TypeScript 能夠檢查事件的名稱和參數類型。
父組件如何接收這個事件呢? (檔案: src/components/ParentComponent.vue
)
<script lang="ts" setup>
import ChildComponent from './ChildComponent.vue';
const handleMessage = (message: string) => {
console.log('收到來自子組件的消息:', message);
};
</script>
<template>
<ChildComponent @update:message="handleMessage" />
</template>
在 ParentComponent
中,我們使用 @update:message
來監聽來自子組件的事件,並在 handleMessage
函數中處理這個事件。
使用 props
和 emit
時,TypeScript 能夠幫助我們進行靜態類型檢查,這大大減少了潛在的錯誤。
在子組件中,我們可以定義 props
和 emit
的類型,確保數據和事件的正確性:(檔案: src/components/ChildComponent.vue
)
<script lang="ts" setup>
const props = defineProps<{
message: string
}>();
const emit = defineEmits<{
'update:message': [message: string];
}>();
const sendMessage = () => {
emit('update:message', 'Hello from Child');
};
</script>
<template>
<button @click="sendMessage">點擊我</button>
</template>
這樣,props
和 emit
都會有嚴格的類型檢查,確保數據傳遞和事件發送都是安全的。
在 vue 3.4 之後提供了 defineModel
的方法,可以把上述 props
, emit
化簡為 defineModel
去實現雙向綁定的功能。以下是我們以往使用雙向綁定的方法。
(檔案: src/components/MySampleInput.vue
)
<script lang="ts" setup>
const props = defineProps<{
message: string
}>();
const emit = defineEmits<{
'update:message': [message: string];
}>();
const sendMessage = () => {
emit('update:sendMessage', 'Hello from Child');
};
</script>
<template>
<input type="text" @input="sendMessage" :value="message" />
</template>
這時候我們的父元件會長這樣
(檔案: src/components/Demo.vue
)
<script lang="ts" setup>
import { shallowRef } from 'vue';
import './MySampleInput.vue';
const mySampleMessage = shallowRef('inpu');
</script>
<template>
<MySampleInput v-model:message="mySampleMessage" />
</template>
這時我們可以用 defineModel
簡化 MySampleInput.vue
(檔案: src/components/MySampleInput.vue
)
<script lang="ts" setup>
const message = defineModel<string>('message', { default: '' });
</script>
<template>
<input type="text" v-model="message />
</template>
withDefaults
眾所皆知,對與使用 typescript 會面臨到 optional type 處理預設值的問題,這時候我們會用 withDefaults
如果是 undefined 或是帶入子元件的 props 未帶入值時會有個預設值。
withDefaults(
defineProps<{
msg: string;
content?: string;
}>(),
{
content: '我是預設值,打我啊笨蛋!'
}
);
由上述範例可知 content 如果父元件上為帶入 content 時,會在子元件預設 content 為 "我是預設值,打我啊笨蛋!"
在 vue 3.5 之後,這個寫法可以再簡化,可以利用解構的方式簡化整個過程
const { msg, content = '我是預設值,打我啊笨蛋!' } = defineProps<{
msg: string;
content?: string;
}>();
provide
和 inject
進行數據共享除了 props
和 emit
,Vue 也提供了 provide
和 inject
來在組件樹中更深層次共享數據。
(檔案: src/components/ParentComponent.vue
)
<script lang="ts" setup>
import { provide } from 'vue';
const sharedData = '共享數據';
provide('sharedData', sharedData);
</script>
<!-- src/components/ChildComponent.vue -->
<script lang="ts" setup>
import { inject } from 'vue';
const sharedData = inject<string>('sharedData');
</script>
provide
和 inject
在組件間進行跨層級的數據共享非常有效,可以作為 props
和 emit
的補充方式。
提醒
: provide
, inject
,在一般專案並不是一個非常常用的做法,通常有 pinia 就用 pinia 進行共享狀態處理,本人不建議在專案裡直接使用 provide, inject 的方式處理。不過 provide, inject 有幾個地方可以作為使用它的方式,
不需要 pinia 的狀態 : 如果專案規模小到屬於可拋式的專案,不會再,那用 provide, inject 是比較清亮和簡單的處理方式
** 插件開發 ** : 一般來說,我們開發 vue 相關插件,我們不會依賴 pinia 整個狀態管理工具,這時候有多層組件需要合併,我建議用 provide/inject 的方式進行調用處理。
defineExpose
: 個人不建議使用這個方法,原因是 vue 每個組件內部方法都是私有且封裝的,defineExpose 基本上本人認為是 anti-pattern 違反組件封閉性的原則,但有鑒於坊間有太多前端工程師為了為解決問題而解決而使用這方法,本人還是特別提及,但不實作。
基本上有使用這方法去解決問題的公司和企業蠻多的,從你我開始,看到有人這樣寫建議暴打惡鬼。
我發張暴打惡鬼圖,鎮一下!如果自己這樣寫,請失主醒醒變回人類吧!
通過 props
和 emit
,Vue 提供了靈活且高效的父子組件通訊機制。結合 TypeScript,這些操作變得更加安全和可維護,有效減少了開發過程中的潛在錯誤。provide
和 inject
則作為補充,在處理更深層數據共享時發揮作用。
希望這篇文章幫助你更深入理解 Vue 組件通訊的最佳實踐。