組件間通信方式
不管是 vue2 還是 vue3,組件通信方式很重要,以下是常見的幾種通信方式:
- props:可以實現父子組件、子父組件、甚至兄弟組件通信
- 自定義事件:可以實現子父組件通信
- 全局事件總線
$bus
:可以實現任意組件通信 - pubsub:發布訂閱模式實現任意組件通信
- vuex:集中式狀態管理容器,實現任意組件通信
- ref:父組件獲取子組件實例 VC,獲取子組件的響應式數據以及方法
- slot:插槽(默認插槽、具名插槽、作用域插槽)實現父子組件通信…
1. props
props 可以實現父子組件通信,在 vue3 中我們可以通過 defineProps
獲取父組件傳遞的數據。且在組件內部不需要引入 defineProps
方法可以直接使用!
父組件給子組件傳遞數據
<Child info="我愛祖國" :money="money"></Child>
子組件獲取父組件傳遞數據:方式1
let props = defineProps({info: {type: String,//接受的數據類型default: '默認參數',//接受默認數據},money: {type: Number,default: 0}
})
子組件獲取父組件傳遞數據:方式2
let props = defineProps(["info",'money']);
子組件獲取到 props 數據就可以在模板中使用了,但是切記 props 是只讀的(只能讀取,不能修改)
2. 自定義事件
在 vue 框架中事件分為兩種:一種是原生的DOM事件,另外一種自定義事件。
原生 DOM 事件可以讓用戶與網頁進行交互,比如 click、dbclick、change、mouseenter、mouseleave、…
自定義事件可以實現子組件給父組件傳遞數據
2.1 原生DOM事件
代碼如下:
<pre @click="handler">我是祖國的老花骨朵</pre>
當前代碼級給 pre 標簽綁定原生 DOM 事件點擊事件,默認會給事件回調注入 event 事件對象。當然點擊事件想注入多個參數可以按照下圖操作。但是切記注入的事件對象務必叫做 $event
.
<div @click="handler1(1, 2, 3, $event)">我要傳遞多個參數</div>
在 vue3 框架 click、dbclick、change(這類原生DOM事件),不管是在標簽、自定義標簽上(組件標簽)都是原生 DOM 事件。
vue2 中卻不是這樣的,在 vue2 中組件標簽需要通過 native 修飾符才能變為原生 DOM 事件
2.2 自定義事件
自定義事件可以實現子組件給父組件傳遞數據,在項目中是比較常用的。
比如在父組件內部給子組件(Event2)綁定一個自定義事件
<Event2 @xxx="handler3"></Event2>
在 Event2 子組件內部觸發這個自定義事件
<template>
<div><h1>我是子組件2</h1><button @click="handler">點擊我觸發xxx自定義事件</button></div>
</template><script setup lang="ts">let $emit = defineEmits(["xxx"]);const handler = () => {$emit("xxx", "法拉利", "茅臺");};
</script>
<style scoped>
</style>
我們會發現在 script 標簽內部,使用了 defineEmits
方法,此方法是 vue3 提供的方法,不需要引入直接使用。defineEmits
方法執行,傳遞一個數組,數組元素即為將來組件需要觸發的自定義事件類型,此方執行會返回一個 $emit
方法用于觸發自定義事件。
當點擊按鈕的時候,事件回調內部調用 $emit
方法去觸發自定義事件,第一個參數為觸發事件類型,第二個、三個、N個參數即為傳遞給父組件的數據。
需要注意的是:代碼如下
<Event2 @xxx="handler3" @click="handler"></Event2>
正常說組件標簽書寫 @click
應該為原生 DOM 事件,但是如果子組件內部通過 defineEmits
定義就變為自定義事件了
let $emit = defineEmits(["xxx",'click']);
3. 全局事件總線
全局事件總線可以實現任意組件通信,在 vue2 中可以根據 VM 與 VC 關系推出全局事件總線。
但是在 vue3 中沒有 Vue 構造函數,也就沒有 Vue.prototype
,以及組合式API寫法沒有 this,那么在 Vue3 想實現全局事件的總線功能就有點不現實啦,如果想在 Vue3 中使用全局事件總線功能,可以使用插件 mitt 實現。
mitt 官網
4. v-model
v-model 指令可是收集表單數據(數據雙向綁定),除此之外它也可以實現父子組件數據同步。
而 v-model 實指利用 props[modelValue]
與自定義事件 [update: modelValue]
實現的。
下方代碼:相當于給組件 Child 傳遞一個 props(modelValue)
與綁定一個自定義事件 update: modelValue
實現父子組件數據同步
<Child v-model="msg"></Child>
在 vue3 中一個組件可以通過使用多個 v-model,讓父子組件多個數據同步,下方代碼相當于給組件 Child 傳遞兩個 props 分別是 pageNo 與 pageSize,以及綁定兩個自定義事件 update: pageNo
與 update: pageSize
實現父子數據同步
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
5. useAttrs
在 Vue3 中可以利用 useAttrs
方法獲取組件的屬性與事件(包含:原生DOM事件或者自定義事件)),次函數功能類似于 Vue2 框架中 $attrs
屬性與 $listeners
方法。
比如:在父組件內部使用一個子組件 my-button
<my-button type="success" size="small" title='標題' @click="handler"></my-button>
子組件內部可以通過 useAttrs 方法獲取組件屬性與事件。因此你也發現了,它類似于 props,可以接受父組件傳遞過來的屬性與屬性值。需要注意如果 defineProps 接受了某一個屬性,useAttrs 方法返回的對象身上就沒有相應屬性與屬性值。
<script setup lang="ts">import {useAttrs} from 'vue';let $attrs = useAttrs();
</script>
6. ref 與 $parent
提及到 ref 可能會想到它可以獲取元素的 DOM 或者獲取子組件實例的 VC。既然可以在父組件內部通過 ref 獲取子組件實例 VC,那么子組件內部的方法與響應式數據父組件可以使用的。
比如:在父組件掛載完畢獲取組件實例
父組件內部代碼:
<template>
<div><h1>ref與$parent</h1><Son ref="son"></Son></div>
</template><script setup lang="ts">import Son from "./Son.vue";import { onMounted, ref } from "vue";const son = ref();onMounted(() => {console.log(son.value);});
</script>
但是需要注意,如果想讓父組件獲取子組件的數據或者方法需要通過 defineExpose 對外暴露,因為 vue3 中組件內部的數據對外“關閉的”,外部不能訪問
<script setup lang="ts">import { ref } from "vue";//數據let money = ref(1000);//方法const handler = ()=>{ }defineExpose({money,handler})
</script>
$parent
可以獲取某一個組件的父組件實例 VC,因此可以使用父組件內部的數據與方法。必須子組件內部擁有一個按鈕點擊時候獲取父組件實例,當然父組件的數據與方法需要通過 defineExpose 方法對外暴露
<button @click="handler($parent)">點擊我獲取父組件實例</button>
7. provide 與 inject
- provide(提供)
- inject(注入)
vue3 提供兩個方法 provide 與 inject,可以實現隔輩組件傳遞參數
組件組件提供數據:provide 方法用于提供數據,此方法執需要傳遞兩個參數,分別提供數據的 key 與提供數據 value
<script setup lang="ts">import {provide} from 'vue';provide('token','admin_token');
</script>
后代組件可以通過 inject 方法獲取數據,通過 key 獲取存儲的數值
<script setup lang="ts">import {inject} from 'vue'let token = inject('token');
</script>
8. pinia
pinia也是集中式管理狀態容器,類似于 vuex。但是核心概念沒有mutation、modules,使用方式參照官網
9. slot
插槽:默認插槽、具名插槽、作用域插槽可以實現父子組件通信。
9.1 默認插槽
在子組件內部的模板中書寫slot全局組件標簽
<template>
<div><slot></slot></div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
在父組件內部提供結構:Todo即為子組件,在父組件內部使用的時候,在雙標簽內部書寫結構傳遞給子組件
注意開發項目的時候默認插槽一般只有一個
<Todo><h1>我是默認插槽填充的結構</h1>
</Todo>
9.2 具名插槽
顧名思義,此插槽帶有名字在組件內部留多個指定名字的插槽。
下面是一個子組件內部,模板中留兩個插槽
<template>
<div><h1>todo</h1><slot name="a"></slot><slot name="b"></slot></div>
</template>
<script setup lang="ts">
</script><style scoped>
</style>
父組件內部向指定的具名插槽傳遞結構。需要注意 v-slot
:可以替換為 #
<template>
<div><h1>slot</h1><Todo><template v-slot:a> //可以用#a替換<div>填入組件A部分的結構</div>
</template>
<template v-slot:b>//可以用#b替換
<div>填入組件B部分的結構</div>
</template>
</Todo>
</div>
</template><script setup lang="ts">import Todo from "./Todo.vue";
</script>
<style scoped>
</style>
9.3 作用域插槽
作用域插槽:可以理解為,子組件數據由父組件提供,但是子組件內部決定不了自身結構與外觀(樣式)
子組件 Todo 代碼如下:
<template>
<div><h1>todo</h1><ul><!--組件內部遍歷數組--><li v-for="(item,index) in todos" :key="item.id"><!--作用域插槽將數據回傳給父組件--><slot :$row="item" :$index="index"></slot></li></ul></div>
</template>
<script setup lang="ts">defineProps(['todos']);//接受父組件傳遞過來的數據
</script>
<style scoped>
</style>
父組件內部代碼如下:
<template>
<div><h1>slot</h1><Todo :todos="todos"><template v-slot="{$row,$index}"><!--父組件決定子組件的結構與外觀--><span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
</template>
</Todo>
</div>
</template><script setup lang="ts">import Todo from "./Todo.vue";import { ref } from "vue";//父組件內部數據let todos = ref([{ id: 1, title: "吃飯", done: true },{ id: 2, title: "睡覺", done: false },{ id: 3, title: "打豆豆", done: true },]);
</script>
<style scoped>
</style>