組件之間的傳值及通信
一、第一種場(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)贊支持喔~