05 Vue3元件間的通訊

songxia777發表於2024-04-16

Vue3元件通訊和Vue2的區別:

  • 移出事件匯流排,使用mitt代替。
  • vuex換成了pinia

  • .sync最佳化到了v-model裡面了

  • $listeners所有的東西,合併到$attrs中了

  • $children被砍掉了

常見搭配形式
image

props - 【父傳子 子傳父】

父傳子:屬性值是非函式
子傳父:屬性值是函式
一般都用於 父傳子

父傳子

父元件

<template>
  <Child :car="car" :obj="obj" :list="list" />
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Child from '@/components/Child.vue';

const car = ref('賓士');
// 定義資料型別-物件
type personsType = {
  id: string;
  name: string;
  age?: number;
};

// 定義資料型別-陣列
// type ArrayPer = Array<persons>;
type ArrayPer = personsType[];

let obj = reactive<personsType>({ id: 'asdfj01', name: '張三' });

let list = reactive<ArrayPer>([
  { id: 'asdfj01', name: '張三', age: 10 },
  { id: 'asdfj02', name: '李四' },
  { id: 'asdfj03', name: '王五' },
]);
</script>

子元件-接收資料

<template>
  <h1>接收來自父元件的值:</h1>
  數值: {{ car }}
  <hr />
  物件:{{ obj }}
  <hr />
  陣列:{{ list }}
  <hr />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 定義資料型別-物件
type personsType = {
  id: string;
  name: string;
  age?: number;
};

// 方式一:直接接收
// defineProps(['car','obj','list']);

// 方式一:設定資料型別
defineProps<{
  car: string;
  obj: personsType;
  list: personsType[];
}>();
</script>

子傳父

父元件

<template>
 # 子傳父:屬性值是 函式
  <Child :getMsg="getMsg" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';

function getMsg(val: string) {
  console.log('收到了來自子元件的資料:' + val);
}
</script>

子元件

<template>
  <h1>點選按鈕向父元件傳送資料:</h1>
  <button @click="sentMsg">傳送資料</button>
  <hr />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';

// 方式一:直接接收
let props = defineProps(['getMsg']);

// // 方式一:設定資料型別
// let props = defineProps<{
//   getMsg: Function;
// }>();

function sentMsg() {
  props.getMsg('哈哈哈');
}
</script>

自定義事件--【子傳父】 defineEmits

父元件--接收資料

<template>
  // customerEventName 子元件裡面的 自定義事件
  <Child @customerEventName="getMsg" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';

function getMsg(val: string) {
  console.log('收到了來自子元件的資料:' + val);
}
</script>

子元件-傳送資料

<template>
  <h1>點選按鈕向父元件傳送資料:</h1>
  <button @click="sentMsg">傳送資料</button>
  <hr />
</template>

<script lang="ts" setup>
// customerEventName 是自定義事件的名稱
const emit = defineEmits(['customerEventName']);
function sentMsg() {
  const msg = '哈哈哈';
  // 向父元件傳遞引數
  emit('customerEventName', msg);
}
</script>

mitt-- 任意元件間通訊

與訊息訂閱與釋出(pubsub)功能類似,可以實現任意元件間通訊

1. 安裝

npm i mitt

2. 構建 src\utils\emitter.ts

// 引入mitt
import mitt from 'mitt';

// 建立emitter
const emitter = mitt();

// 建立並暴露mitt
export default emitter;

3. mitt 繫結on-呼叫-釋出-接收資料觸發emit-定義-訂閱-發資料 事件

// emitter表示的是 emitter物件


// 繫結事件--呼叫事件
emitter.on('abc', (value) => {
  console.log('abc事件被觸發', value);
});
emitter.on('xyz', (value) => {
  console.log('xyz事件被觸發', value);
});

setInterval(() => {
  // 觸發事件--定義事件
  emitter.emit('abc', 666);
  emitter.emit('xyz', 777);
}, 1000);


setTimeout(() => {
  // 清理事件  
  emitter.all.clear();
  
  // 移除事件
  emitter.off('abc')
  
}, 3000);

4. 一個案例

父元件--監聽事件--接收資料

<template>
  <Child />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { onMounted, onUnmounted } from 'vue';
import mittBus from '@/utils/emitter';
onMounted(() => {
  getMsg();
});

onUnmounted(() => {
  mittBus.off('customerEventName');
});

const getMsg = () => {
  mittBus.on('customerEventName', (val) => {
    console.log('收到了來自子元件的資料:' + val);
  });
};
</script>

子元件--觸發事件--發資料

<template>
  <h1>點選按鈕向父元件傳送資料:</h1>
  <button @click="sentMsg">傳送資料</button>
  <hr />
</template>

<script lang="ts" setup>
import mittBus from '@/utils/emitter';

// customerEventName 是自定義事件的名稱
const sentMsg = () => {
  const msg = '哈哈哈';
  // 向父元件傳遞引數msg
  mittBus.emit('customerEventName', msg);
};
</script>

v-model -- 父傳子 子傳父

v-model的雙向資料繫結

v-model的雙向資料繫結本質就是 input 繫結一個value值,同時監聽input的value變化,重新賦值

<template>
  <h2>父元件</h2>
  <input type="text" v-model="username" />

  <!-- v-model的雙向資料繫結本質就是 input 繫結一個value值,同時監聽input的value變化,重新賦值 -->
  <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value" />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let username = ref('zhangsan');
</script>

元件標籤上的v-model的本質

:moldeValueupdate:modelValue事件

  <!-- 透過 v-model 將username 傳遞給 Child 子元件 -->
 <Child v-model="username" />

 <!-- 元件標籤上v-model的本質 -->
 <!-- 傳遞資料modelValue,同時繫結事件 update:modelValue -->
 <Child :modelValue="username" @update:modelValue="username = $event" />

父子互相通訊

父元件--傳送資料

<template>
  <h2>父元件</h2>
  <input type="text" v-model="username" />
  <!-- 透過 v-model 將username 傳遞給 Child 子元件 -->
  <Child v-model="username" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
</script>


子元件-接收資料
透過 defineProps 接收 名為 modelValue 的資料,就是父元件傳遞過來的 username

<template>
  <hr />
  <h2>子元件</h2>
  <h3>透過v-model接收父元件的資料:</h3>
  {{ modelValue }}
  <input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)" />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收資料 modelValue
defineProps(['modelValue']);

// 自定義觸發事件  pdate:modelValue
const emit = defineEmits(['update:modelValue']);
</script>

更換 modelValue 的命名

父元件

<template>
  <h2>父元件</h2>
  <input type="text" v-model="username" />
  <input type="text" v-model="pwd" />
  <!-- 透過 v-model 將username 傳遞給 Child 子元件 -->
  <Child v-model:abc="username" v-model:xyz="pwd" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
let pwd = ref('12356');
</script>

子元件

<template>
  <hr />
  <h2>子元件</h2>
  <h3>透過v-model接收父元件的資料:</h3>
  {{ abc }}=={{ xyz }}
  <input type="text" :value="abc" @input="emit('update:abc', (<HTMLInputElement>$event.target).value)" />
  <input type="text" :value="xyz" @input="emit('update:xyz', (<HTMLInputElement>$event.target).value)" />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收資料 modelValue
defineProps(['abc', 'xyz']);

// 自定義觸發事件  pdate:modelValue
const emit = defineEmits(['update:abc', 'update:xyz']);
</script>

$attrs -- 祖孫間傳遞資料

$attrs 用於實現當前元件的父元件,向當前元件的子元件通訊(祖→孫

$attrs是一個物件,包含所有父元件傳入的標籤屬性

注意:$attrs會自動排除props中宣告的屬性(可以認為宣告過的 props 被子元件自己“消費”了)

父元件

<template>
  <h2>父元件</h2>
  <h3>a={{ a }}</h3>
  <h3>b={{ b }}</h3>
  <h3>c={{ c }}</h3>
  <h3>num={{ num }}</h3>

  <Child :a="a" :b="b" :c="c" v-bind="{ x: 100, y: 200, z: 300 }" :updateSum="updateSum" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let num = ref(1);
let a = ref('a');
let b = ref('b');
let c = ref('c');

const updateSum = (val: number) => {
  num.value = val;
};
</script>

子元件 : 子元件不做任何事情,直接將 $attrs 傳遞給孫子元件

<template>
  <hr />
  <h2>子元件不做任何事情,直接將 $attrs 傳遞給孫子元件</h2>
  <GrandChild v-bind="$attrs" />
</template>

<script lang="ts" setup>
import GrandChild from '@/components/GrandChild.vue';
</script>

孫元件

<template>
  <hr />
  <h2>孫子元件</h2>
  <h3>a={{ a }}</h3>
  <h3>b={{ b }}</h3>
  <h3>c={{ c }}</h3>
  <h3>x={{ x }}</h3>
  <h3>y={{ y }}</h3>
  <h3>z={{ z }}</h3>
  <button @click="updateSum(666)">點選將 num 的值 改為 666</button>
</template>

<script lang="ts" setup>
defineProps(['a', 'b', 'c', 'x', 'y', 'z', 'updateSum']);
</script>

$refs 父傳子 $parent 子傳父

$refs 父傳子,結合ref元件標籤

$refs 值為物件,包含所有被ref屬性標識的DOM元素或元件例項

父元件:只可以修改 子元件向外暴露的資料

<template>
  <h2>父元件</h2>
  <!--  $refs 子元件所有的 例項物件  -->
  <button @click="getAllChilds($refs)">獲取所有子元件的例項物件,並修改資料</button>
  <Child1 ref="c1" />
  <Child2 ref="c2" />
</template>

<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import Child2 from '@/components/Child2.vue';
import { ref } from 'vue';
let c1 = ref();
let c2 = ref();

function getAllChilds(refs: any) {
  refs.c1.name = '張三三';
  refs.c2.name = '李四四';
}
</script>

子元件:必須向外暴露資料,父元件才可以修改和獲取

子元件1

<template>
  <hr />
  <h2>子元件-Child1-{{ name }}</h2>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('張三');
// 將name向外暴露出去
defineExpose({ name });
</script>

子元件2

<template>
  <hr />
  <h2>子元件-Child2-{{ name }}</h2>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('李四');
// 將name向外暴露出去
defineExpose({ name });
</script>

$parent 子傳父

$parent 值為物件,當前元件的父元件例項物件

父元件

<template>
  <h2>父元件</h2>
  數值num為=={{ num }}
  <Child1 />
</template>

<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import { ref } from 'vue';
let num = ref(6);
// 必須將父元件的值暴露給子元件,子元件才可以修改父元件的值
defineExpose({ num });
</script>

子元件

<template>
  <hr />
  <h2>子元件-Child1</h2>
  <button @click="minus($parent)">將父元件num值減1</button>
</template>

<script lang="ts" setup>
function minus(p: any) {
  console.log(p.num);
  p.num -= 1;
}
</script>

provide、inject 祖孫間傳遞資料

Pinia

參考:
Pinia狀態管理

插槽

參考:

相關文章