Vue3.2組件間通信

組件之間的傳值及通信

一、第一種場(chǎng)景 兩層組件之間傳值和監(jiān)聽值改變

// 父組件
const name = ref('1')
setTimeout(() => {
   name.value = '2'
}, 1000);

// 子組件
<template>
    <p>接收和監(jiān)聽name {{childName}}</p>
</template>

const childName = ref()
watch(()=>props.name,
  (nv, pv)=>{
    childName.value = nv
},{immediate: true}) // 初始值也要傳過來

二、第二種場(chǎng)景 多層組件間傳值和監(jiān)聽值改變
父組件

<template>
     <Child />
</template>

<script lang="ts" setup>
import { ref, provide } from 'vue';
import Child from './comps/child.vue'; // 引入Child
// 層級(jí)較多時(shí),或者想廣播給所有子孫組件
provide('myProvide', {
    msg: '廣播的內(nèi)容'
})
</script>

子組件

<template>
    <p @click="clickMe">我是子組件</p>
</template>

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

// 接收廣播的內(nèi)容
const provideState = inject('myProvide')
console.log(provideState)
</script>

三、組件間事件傳遞和互相調(diào)用
父組件

<template>
     <Child ref="childRef" :name="name" @clicked="doNext" />
</template>

<script lang="ts" setup>
import { ref, provide, onMounted } from 'vue';
import Child from './comps/child.vue'; // 引入Child

const name = ref('Famous')

function doNext() {
    console.log('監(jiān)聽到子組件傳過來信號(hào),執(zhí)行事件');
}
// 綁上子組件 childRef
const childRef = ref();
onMounted(() => {
    childRef.value.closeMask(); // 需要DOM渲染完, 調(diào)用子組件方法或拿數(shù)據(jù)
});

</script>

子組件

<template>
    <p @click="clickMe">我是子組件:::{{ props.name }}</p>
</template>

<script lang="ts" setup>
import { getCurrentInstance } from 'vue';
// 聲明props
const props = defineProps({
    name: {
        type: String,
        default: ''
    }
});

const {proxy} = getCurrentInstance()
proxy.$parent.doNext() // 直接調(diào)用父組件的方法

// 定義emits
const $emit = defineEmits(['clicked']); // 數(shù)組形式
function clickMe() {
    // 發(fā)出信號(hào)給父組件,調(diào)用父組件的方法
    $emit('clicked', { test: 1 }); // 傳參
}

function closeMask() {
    console.log('關(guān)閉彈窗');
}
defineExpose({
    closeMask // 對(duì)外部暴露方法或數(shù)據(jù),讓其父組件可通過$parent 以及 子組件通過ref 可以拿到,調(diào)用該方法
});
</script>

四、兄弟組件之間呢,vue2我們用EventBus,vue3中我們推薦mitt

(1) 安裝

yarn add mitt  // 或
npm install --save mitt 

(2) 第一種 vue3.x的全局引入,掛載在config.globalProperties

// 在main.js 中
import mitt from 'mitt'
app.config.globalProperties.$Mitt = new mitt()

// 在組件中使用
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()

// 觸發(fā)
proxy.$Mitt.emit('home_newTask', data)

// 在兄弟組件中監(jiān)聽 
proxy.$Mitt.on('home_newTask', data => { // 事件名最好統(tǒng)一加前綴確保唯一
    doNext()
})

(3) 第二種 封裝一個(gè)ES模塊,使用組件都引這一個(gè)

// 創(chuàng)建 Mitt.js
@/utils/Mitt.js
import mitt from 'mitt'
export default new mitt()

// 在組件中使用
import Mitt from '@/utils/Mitt.js'

// 觸發(fā)
Mitt.emit('home_newTask', data)

// 在兄弟組件中監(jiān)聽 
Mitt.on('home_newTask', data => { // 事件名最好統(tǒng)一加前綴確保唯一
    doNext()
})

五、還有一種全局的狀態(tài),項(xiàng)目中很多頁(yè)面共用和隨時(shí)全局更新,vue2我們用Vuex,vue3中我們推薦pinia
ps 存儲(chǔ)狀態(tài)經(jīng)常會(huì)遇到刷新時(shí)狀態(tài)丟失的問題,需要并持久化存儲(chǔ) vuex 中用vuex-persistdstate,pinia 中我們用pinia-persistedstate-plugin

(1) 安裝pinia 和 pinia-plugin-persist

yarn add pinia pinia-plugin-persist // 或
npm i --save pinia pinia-plugin-persist

(2) 引入

main.js 中

import App from './App.vue';
import store from '@/store';

const vm = createApp(App);
vm.use(store)

@/store/index.ts

import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
import type { App } from 'vue';

const pinia = createPinia();
pinia.use(piniaPluginPersist); // 使用持久化插件

export default function install(app: App<Element>) {
    app.use(pinia);
} // 配合main.js中app.use
export { pinia };

@/modules/user.ts

import {pinia} from '@/store';
import { defineStore } from 'pinia';
// import { loginApi } from '@/api/user/index.ts';

interface User {
    nickName?: string;
    cityName?: string;
}

export const useUserStore = defineStore({
    id: 'user-store',
    state: (): User => ({
        nickName: '',
        cityName: ''
    }),
    // getters 和actions 可以通過this訪問當(dāng)前實(shí)例
    getters: {
        // 類似計(jì)算屬性,還可以調(diào)用其它store 的變量,比如 + globalStore.loginStatus,在上面引入和創(chuàng)建 globalStore
        getWelcomeWords(): string {
            return '非常歡迎' + this.nickName + '的到來!!' || '';
            // 還可以return function(params){  } 就可以傳入?yún)?shù)了
        }
    },
    actions: {
        // 類似 methods 還支持異步操作
        // async setUserInfo() {
        //    const res = await loginApi({XXX});
        //    if (res.code == 200) {
        //        this.nickName = '尤雨溪';
        //    }
       // }
       setNickName(){
           this.nickName = '尤雨溪';
       }
    },
    // 開啟數(shù)據(jù)緩存
    persist: {
        enabled: true,
        strategies: [
            {
                storage: localStorage, // 默認(rèn)是sessionStorage
                paths: ['nickName'] // 不寫key: ''  或者path 數(shù)組的話 默認(rèn)是全部緩存,,
            }
        ]
    }
});

// Need to be used outside the setup
export function useUserStoreHook() {
    return useUserStore(pinia);
}

// in setup
export default useUserStore(pinia);

ps: 這套Option Api 的形式和我們Vue3 compositionApi 不符,推薦下面的setup寫法,更簡(jiǎn)潔

import { loginApi } from '@/api/user/index.ts';
import { pinia } from '@/store';
import { defineStore } from 'pinia';

// 使用setup模式定義
const useUserStore = defineStore(
    'userStore',
    () => {
        // ref 和 state 對(duì)應(yīng)
        const nickName = ref<string>('');
        const cityName = ref<string>('');
        // computed 和getter 對(duì)應(yīng)
        const getWelcomeWords = computed(() => {
            return '非常歡迎' + nickName.value + '的到來!!' || '';
        });
        // function 和 action 對(duì)應(yīng)
        async function setUserInfo() {
            const res = await loginApi({ location: '蘇州', key: 'e76a0884c20d49aba7bf1f9cebc0f0bc' });
            console.log(res);
            if (res?.code == 200) {
                nickName.value = '尤雨溪';
            }
        }
        function setCityName(name: string) {
            cityName.value = name;
        }
        return { nickName, cityName, getWelcomeWords, setUserInfo, setCityName };
    },
    {
        persist: {
            enabled: true,
            strategies: [
                {
                    storage: localStorage, // 默認(rèn)是sessionStorage
                    // key: 'nickName'
                    paths: ['nickName', 'cityName'] // 不寫key: ''  或者path 數(shù)組的話 默認(rèn)是全部緩存,,
                }
            ]
        }
    }
);

/** 在 setup 外使用 */
export function useUserStoreHook() {
    return useUserStore(pinia);
}

export default useUserStore(pinia);

(3) 頁(yè)面中使用

<template>
    <div class="pinia-container">
        你好: {{ nickName }}
        <p v-if="nickName">{{ userStore.getWelcomeWords }}</p>
        <div @click="login">登錄</div>
    </div>
</template>

<script setup lang="ts">
import userStore from '@/store/modules/user';
import { storeToRefs } from 'pinia';

// 1、userStore.$reset() 可以重置到初始值 
// 2、修改某個(gè)屬性,可通過userStore.xx = ,或如下解構(gòu)修改 const { nickName } = storeToRefs(userStore); 
// 但解構(gòu)后會(huì)丟失響應(yīng)式,pinia提供了保持響應(yīng)式的方法storeToRefs
// 這樣nickName.value 在頁(yè)面修改時(shí)會(huì)更新到全局
const { nickName } = storeToRefs(userStore);

function login() {
    // userStore.setUserInfo(); // 走異步請(qǐng)求
    userStore.setNickName(); // 雖然可userStore.nickName = XXX 直接修改,但是大項(xiàng)目多人合作時(shí)出問題,極難排查,store的值修改建議都走action,后期也容易維護(hù)。
}

</script>

打開調(diào)試中的application 點(diǎn)擊登錄,可以看到locationStorage 中會(huì)存儲(chǔ)userStore 內(nèi)容,實(shí)現(xiàn)了持久化存儲(chǔ)的效果,若部分key 需特殊管理,在persist中單配即可

總結(jié)下,pinia 體積小,省去mutations,十分方便,一旦涉及到全局狀態(tài)管理時(shí)必備。Mitt 也很優(yōu)秀,兄弟組件間可以用總線通信,但別到處寫。而provide inject 更適合的場(chǎng)景是從頂層丟出一個(gè)底下組件都用到的狀態(tài),如果想要響應(yīng)式,那就傳遞響應(yīng)式對(duì)象,子組件中watch 即可。父子組件通信,props + emit + watch 足夠

六、雙向綁定 v-model

父組件中

<template>
 <p>父組件的年齡:{{ age }}</p>
 <p>資產(chǎn): {{ asset.money }}</p>
 <Child v-model:age="age" v-model:asset="asset"/>
 <!-- 只寫v-model === v-model:modelValue -->
 <!-- 原理等于: <Child :modelValue="age"  @update:modelValue="age = $event" /> -->
</template> 

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const age = ref(0)
const asset = reactive({
    house: 99,
    money: '200億'
});
</script>

子組件中

<template>
   <!-- props.age 可以省略props -->
    <p>子組件的值:{{ age }} <span @click="addAge">過去一年</span></p>
    <p>資產(chǎn): {{ asset.money }}</p>
</template>

<script lang="ts" setup>
const props = defineProps({
    age: {
        type: Number,
        default: 0
    },
    asset: {
        type: Object,
        default: null
    }
});
const $emit = defineEmits([ 'update:age']);
function addAge() {
    $emit('update:age', props.age + 1);  
   // 這里有個(gè)隱藏的知識(shí)點(diǎn)
   // props.age = props.age + 1 無效 // 如果是基本類型,必須用 emit 修改
   // 但如果是引用類型,可以直接修改,切記 不可把a(bǔ)sset 解構(gòu)出來,會(huì)丟響應(yīng)式,前面已提到reactive 丟失響應(yīng)式的原因。一旦在修改,你一定會(huì)用let,所以多用const可以避免自己犯錯(cuò)!!
  const asset = props.asset;
  asset.money = asset.money.length > 1 ? 
  asset.money.substring(0, asset.money.length - 1) : 0;
}
</script>

ps:
1、和vue2 差不多,只是prop、emit 的寫法有所改變,講道理v-model 是一個(gè)語(yǔ)法糖,只是在父組件可以省略 @update:modelValue="age = $event" 特殊性在于父子組件都用的是同一個(gè)值
2、vue3 中引用類型或者嵌套引用類型的是可以直接修改props 來進(jìn)行同步的

相信你對(duì)Vue3 組件通信有了比較全面的認(rèn)識(shí)了,感謝支持,會(huì)繼續(xù)更新,點(diǎn)贊支持喔~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容