Vue 3 資料傳遞

Vue 3 為前端開發者提供了強大的功能來建構互動式網頁應用程序。在組件化的架構中,資料的傳遞是重要的一環。本文將介紹在 Vue 3 中,如何實現父元件與子元件之間的資料傳遞,包括父元件傳資料給子元件、子元件傳資料給父元件,以及子元件間的資料傳遞。

  • Props: 用於父元件向子元件傳遞資料。
  • Emit: 子元件可以通過發射事件來向父元件傳遞資料。
  • Provide/Inject: 這是一種跨越多個層級的元件間通信方法,特別適用於那些不是直接父子關係的元件間的資料傳輸。

1. 父元件傳資料到子元件

在 Vue 3 中,父元件可以透過 props 向子元件傳遞資料。props 是子元件用來接收來自父元件資料的一種機制。

1.1 父元件

假設我們有一個父元件,想要傳遞一個名為 message 的資料到子元件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<ChildComponent :message="messageFromParent" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: {
ChildComponent
},
data() {
return {
messageFromParent: 'Hello from Parent'
}
}
}
</script>

1.2 子元件

子元件需要定義一個 props 來接收來自父元件的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>{{ message }}</div>
</template>

<script>
export default {
props: {
message: {
type: String,
required: true
}
}
}
</script>

2. 子元件傳資料到父元件

子元件可以透過發射事件 (emit) 的方式將資料傳回父元件。

2.1 子元件

在子元件中,我們可以使用 $emit 方法發射一個自定義事件,並附帶需要傳遞的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<button @click="sendMessageToParent">Send to Parent</button>
</template>

<script>
export default {
methods: {
sendMessageToParent() {
this.$emit('message-from-child', 'Hello from Child');
}
}
}
</script>

2.2 父元件

父元件需要在使用子元件的標記上綁定一個事件監聽器來接收這個資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<ChildComponent @message-from-child="handleMessageFromChild" />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: {
ChildComponent
},
methods: {
handleMessageFromChild(message) {
console.log('Received message from child:', message);
}
}
}
</script>

3. 跨組件讀寫資料

3.1 使用 Provide/Inject 進行資料傳輸

provide 和 inject 的概念是,父元件可以 provide 一些資料(可以是任何東西:物件、數據、函數等),而任何子元件(不論是直接或間接的子元件)都可以選擇性地 inject 這些資料。這種方式適合於那些不是直接父子關係的組件傳遞數據的場景。

需要注意的是,由於 provide/inject 導致了組件實例的高度耦合,所以它們僅適合在較小型的項目中使用。對於大型項目來說,我們更推薦使用 Vuex 或 Pinia 等全局狀態管理工具。

3.1.1 父元件

首先,在父元件中使用 provide 來定義你想要共享的資料。在 Vue 3 中,provide 通常在 setup() 函數中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import { provide, ref } from 'vue';

export default {
setup() {
const sharedData = ref('這是要共享的資料');

provide('sharedDataKey', sharedData);

return {};
}
};
</script>

3.1.2 子元件

然後,在任何子元件中,你可以使用 inject 來接收這些資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import { inject } from 'vue';

export default {
setup() {
const sharedData = inject('sharedDataKey');

return {
sharedData
};
}
};
</script>

這樣,子元件就可以訪問由父元件提供的資料了。需要注意的是,provide 和 inject 的鍵(在這個例子中是 sharedDataKey)必須匹配,才能正確地傳遞和接收資料。

3.2 Vuex 與 Pinia

當面對一個父元件底下有多個子元件,且需要在子元件之間進行資料傳輸的情況,我們可以採用 Vuex 或 Pinia 這樣的全局狀態管理解決方案。

3.2.1 Vuex

Vuex 提供了一個集中管理應用的所有組件的狀態的機制。使用 Vuex,你可以在一個集中的地方定義全局狀態,然後讓任何組件根據需要讀取或修改這些狀態。

步驟1: 安裝 Vuex

1
2
npm install vuex@next --save
步驟2: 創建 Vuex Store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// store/index.js
import { createStore } from 'vuex';

export default createStore({
state() {
return {
sharedData: ''
};
},
mutations: {
updateSharedData(state, payload) {
state.sharedData = payload;
}
},
getters: {
sharedData: state => state.sharedData
}
});

步驟3: 在 Vue 應用中使用 Store

1
2
3
4
5
6
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

createApp(App).use(store).mount('#app');

步驟4: 在組件中讀取和修改狀態
子元件可以通過 this.$store 訪問 store 中的狀態和 mutations。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 子元件
export default {
computed: {
sharedData() {
return this.$store.getters.sharedData;
}
},
methods: {
updateData() {
this.$store.commit('updateSharedData', '新的共享資料');
}
}
};

3.2.2 Pinia

Pinia 是 Vue 官方推薦的一種更現代化的狀態管理解決方案,相比 Vuex 而言,它具有更簡潔的 API 和更好的 TypeScript 支持。

步驟1: 安裝 Pinia

1
npm install pinia

步驟2: 創建 Pinia Store

1
2
3
4
5
6
7
8
9
10
11
12
13
// stores/counterStore.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
}
}
});

步驟3: 在 Vue 應用中使用 Pinia Store

1
2
3
4
5
6
7
8
9
10
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount('#app');

步驟4: 在組件中使用 Pinia Store

1
2
3
4
5
6
7
8
9
10
11
// 任何子元件
<script setup>
import { useCounterStore } from './stores/counterStore';

const counterStore = useCounterStore();
</script>

<template>
<div>{{ counterStore.count }}</div>
<button @click="counterStore.increment">Increment</button>
</template>

使用 Vuex 或 Pinia,子元件之間可以很容易地共享和修改狀態,因為所有的狀態都被集中管理了。當一個子元件修改了狀態,其他引用了這個狀態的子元件會自動更新,從而實現了跨元件的資料共享和通信。

4. 最佳實踐建議

保持單向數據流: 儘量使用 props 向下傳遞資料,避免直接修改 props,這樣可以防止子組件意外改變了父組件的狀態。
適當使用 provide/inject: 雖然 provide/inject 很方便,但它會增加組件實例之間的耦合性,使重構變得更加困難。對於大型項目,建議使用 Vuex 或 Pinia 進行狀態管理。
模塊化管理狀態: 當使用 Vuex 或 Pinia 時,建議按功能將應用程序狀態劃分為多個模塊,每個模塊管理與該功能相關的狀態。這樣可以更好地解耦和重用代碼。
在適當的層級傳遞數據:對於父子組件通信,請使用 props 和 events; 對於更深層級的通信,請考慮使用 provide/inject 或者全局狀態管理。避免在應用程序的多個位置直接導入和修改狀態。
編寫單元測試: 不論你選擇使用哪種通信方式,都應該為這些功能編寫單元測試,以確保代碼的正確性和維護性。

總結

選擇適合的組件間通信方式取決於你的具體需求:

對於簡單的父子組件通信,props 和 emit 是最直接且常用的方法。
當需要在祖先和後代組件間共享資料時,provide 和 inject 提供了一個更為靈活的解決方案。
對於複雜的應用,需要全局狀態管理或跨多個組件共享狀態時,Vuex 或 Pinia 是更好的選擇,它們提供了集中管理和高度靈活的狀態管理方案。

合理利用這些工具和概念,可以幫助你建立一個結構清晰、易於維護的 Vue 應用。