com组件的ref有时需要有时不需要?_Vue3组件通信总结
前言
我們知道vue3的Composition Api是它幾個(gè)最大亮點(diǎn)之一,所以下文都是在setup中演示代碼的實(shí)現(xiàn)。后面會(huì)以開發(fā)幾個(gè)簡(jiǎn)單form組件為例子來演示。
基本操作
這里先簡(jiǎn)單開發(fā)一個(gè)VInput的輸入框組件。組件就像一個(gè)函數(shù),主要就是處理輸入和輸出。Vue3在setup函數(shù)上提供了兩個(gè)參數(shù),一個(gè)props,一個(gè)是context下面的emit方法,分別來處理輸入和輸出。
props
現(xiàn)在VInput就是子組件,我需要它能夠接受父級(jí)傳遞一個(gè)值,讓它可以幫我做后續(xù)的邏輯處理在返回給父級(jí)。所以,這里需要最基本的一些父子通信方式v-bind,props。
父級(jí)組件中
// 通過v-bind將數(shù)據(jù)想子組件傳遞:value="valueRef" />const valueRef = ref('')
復(fù)制代碼
VInput中
:value="value" type="text" />復(fù)制代碼
emit
當(dāng)我們?cè)诮M件中接受參數(shù),進(jìn)行一些邏輯處理后,我們就需要將處理好的值,向外部進(jìn)行一個(gè)返回,外部同時(shí)需要實(shí)現(xiàn)一個(gè)事件函數(shù)去接受。此時(shí)我就可以使用emit方法
假設(shè)我們希望VInput組件返回給外部的是一個(gè)限制長(zhǎng)度的字符串。此時(shí)外部就需要實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的事件函數(shù)去接收這個(gè)值,然后VInput內(nèi)部通emit執(zhí)行事件,將內(nèi)部的處理好的值當(dāng)做參數(shù)返回出去。
VInput
:value="value" type="text" @input="onInput" ref="inputRef" />復(fù)制代碼
父級(jí)組件
// 通過v-on向子組件傳遞一個(gè)函數(shù),用戶接受返回值:value="valueRef" :maxLength="10" @onInput="onInput" />復(fù)制代碼
對(duì)于這種input的組件的使用,我猜大家肯定都不想在父級(jí)組件這么麻煩的去接收和改變一個(gè)值,所以vue是提供了v-model來更快捷的實(shí)現(xiàn)輸入和輸出。
v-model
通過Vue3的文檔可以發(fā)現(xiàn),這個(gè)指令的用法發(fā)生了一定的變化。在之前,我們要想實(shí)現(xiàn)一個(gè)自定義的非表單組件的雙向綁定,需要通過xxxx.sync的這種語法來實(shí)現(xiàn),如今這個(gè)指令已經(jīng)被廢除了,而是統(tǒng)一使用v-model這個(gè)指令。
父級(jí)組件
新的v-model?還可以支持多個(gè)數(shù)據(jù)的雙向綁定。
v-model:value="valueRef" v-model:keyword="keywordRef" />復(fù)制代碼
自定義的非表單組件
click="clickHandle">clickexport default defineComponent({
name: 'VBtn',
props: {
value: String,
keyword: String
},
setup(props, { emit }) {
// 省略其他代碼
// 用戶點(diǎn)擊按鈕
const clickHandle = (e: any) => {
// 省略其他代碼
// 修改對(duì)應(yīng)的props的數(shù)據(jù)
emit('update:value', value)
emit('update:keyword', value + '123')
}
return {
// ...
}
}
})
復(fù)制代碼
以上就是在Vue3中一些基本通信方式的API的介紹。在Vue3中一般都是采用Composition Api的形式開發(fā),所以你會(huì)發(fā)現(xiàn)開發(fā)的時(shí)候不能在采用this.$xxx的方式去調(diào)用實(shí)例上的某個(gè)函數(shù)或者是屬性。那些this.$parent,this.$children,this.$on,this.$emit等等都不能在使用了。
那在Vue3中如何解決組件間那些通信的呢?咱們從簡(jiǎn)單到復(fù)雜的場(chǎng)景,一個(gè)個(gè)來分析。
先來看一下,開發(fā)的三個(gè)form組件,組合起來的實(shí)際的用法是怎么樣的:
ref="validateFormRef1" :model="state" :rules="rules">label="用戶名" prop="keyword">placeholder="請(qǐng)輸入"requiredv-model:modelValue="state.keyword"/>label="密碼" prop="password">placeholder="請(qǐng)輸入"requiredtype="password"v-model:modelValue="state.password"
/>class="btn btn-primary" @click="submit(0)">提交
復(fù)制代碼
所有組件的功能,是模仿Element UI去實(shí)現(xiàn)的。
父?jìng)髯?/h2>
父組件向子組件傳遞一個(gè)數(shù)據(jù),可以用這兩種方式:
v-bind
refs獲取子組件內(nèi)部某個(gè)函數(shù),直接調(diào)用傳參(這里簡(jiǎn)稱refs方式)
refs方式
關(guān)于v-bind咱們就不細(xì)說了,在基本操作章節(jié)已經(jīng)講過其對(duì)應(yīng)的使用方式了。這小節(jié)主要在中講Vue3如何通過ref獲取子組件實(shí)例并調(diào)用其身上的函數(shù)來對(duì)子組件進(jìn)行傳值。
子組件
// 渲染從父級(jí)接受到的值Son: {{ valueRef }}復(fù)制代碼
父組件
sonRefclick="sendValue">send// 這里ref接受的字符串,要setup返回的ref類型的變量同名ref="sonRef" />復(fù)制代碼
這里可以看一下流程圖:??其實(shí)這種方式跟Vue2中使用this.$refs,this.$children的方式很相似,都是通過拿到子組件實(shí)例,直接調(diào)用子組件身上的函數(shù)。方法千篇一律,不過在Vue3中沒有了this這個(gè)黑盒。
這里我們可以在控制臺(tái)看一下這個(gè)sonRef.value是一個(gè)怎樣的東西。
可以發(fā)現(xiàn),通過ref獲取到的子組件實(shí)例上面可以拿到setup返回的所有變量和方法,同時(shí)還可以拿到其他的一些內(nèi)部屬性。我們可以看一下官方文檔Vue 組合式 API的描述。
在 Virtual DOM patch 算法中,如果一個(gè) VNode 的 ref 對(duì)應(yīng)一個(gè)渲染上下文中的 ref,則該 VNode 對(duì)應(yīng)的元素或組件實(shí)例將被分配給該 ref。這是在 Virtual DOM 的 mount / patch 過程中執(zhí)行的,因此模板 ref 僅在渲染初始化后才能訪問。
ref方式總結(jié)
優(yōu)點(diǎn):
父組件可以獲取快速向確定存在的子組件傳遞數(shù)據(jù)
傳遞的參數(shù)不受限制,傳遞方式比較靈活
缺點(diǎn):
ref獲取的子組件必須確定存在的(不確定存在的情況:如插槽上子組件,v-if控制的子組件)
子組件還需要實(shí)現(xiàn)接受參數(shù)的方法
父?jìng)鞲畹暮蟠?/h2>
一般往深度層級(jí)傳遞值,有這兩種方式:
provide / inject
vuex
provide / inject
一看到“深”這個(gè)字,大家肯定第一想到的就Vue2中的provide / inject選項(xiàng)。沒錯(cuò),這套邏輯在vue3中同樣適用,這兩個(gè)選項(xiàng)變成了兩個(gè)方法。
provide允許我們向當(dāng)前組件的所有后代組件,傳遞一份數(shù)據(jù),所有后代組件能夠通過inject這個(gè)方法來決定是否接受這份數(shù)據(jù)。
大致的示意圖如下:?
實(shí)際應(yīng)用場(chǎng)景
主要應(yīng)用的場(chǎng)景有兩中,一種深度傳遞一個(gè)參數(shù)或者一個(gè)函數(shù)的時(shí)候,另一種是給插槽上不確定性的組件傳參的時(shí)候。
重點(diǎn)說一下給插槽上的組件傳參。先實(shí)現(xiàn)一個(gè)最外層的ValidateForm組件,它主要負(fù)責(zé)接受一整個(gè)表單數(shù)據(jù)和整個(gè)表單數(shù)據(jù)的校驗(yàn)規(guī)則。其內(nèi)部提供了一個(gè)插槽,用于放置一些不確定性的組件。還有一個(gè)ValidateFormItem組件可以接受一個(gè)字段名,通過這字段名準(zhǔn)確知道需要校驗(yàn)?zāi)膫€(gè)字段(tips:功能其實(shí)和element-ui類似)。
組件化開發(fā),需要將參數(shù)和功能進(jìn)行解耦,所以我們這樣來設(shè)計(jì):
ValidateForm:model,rules,只管接受整份表單的數(shù)據(jù)和校驗(yàn)規(guī)則
ValidateFormItem:prop,只管接受字段名,只需知道自己需要驗(yàn)證哪一個(gè)字段
復(fù)制代碼
如果ValidateFormItem組件需要通過prop去效驗(yàn)?zāi)硞€(gè)字段,那它就需要拿到那份表單的數(shù)據(jù),通過formData[prop]去取到那個(gè)字段的值,那這份formData從哪里來呢?首先不可能每寫一個(gè)ValidateFormItem組件都傳遞一份。因?yàn)?#xff0c;實(shí)際開發(fā)中我們并不能確定在ValidateForm下要寫多少個(gè)ValidateFormItem組件,如果每寫一個(gè)都手動(dòng)傳遞一份表單的數(shù)據(jù),這些寫起來就會(huì)多了很多冗余的代碼而且也很麻煩。所以,就由ValidateForm這個(gè)組件獨(dú)立接受并分發(fā)下來。
ValidateForm
所以我們需要ValidateForm來向下分發(fā)數(shù)據(jù)。
復(fù)制代碼
ValidateFormItem
ValidateFormItem接受上面?zhèn)鬟f的數(shù)據(jù)。
復(fù)制代碼
provide / inject總結(jié)
在這篇文章Vue組件通信方式及其應(yīng)用場(chǎng)景總結(jié)中,大佬對(duì)其的優(yōu)缺點(diǎn)已經(jīng)總結(jié)很好了。這里提一下它的缺點(diǎn),就是不能解決兄弟組件的通信。
vuex
vuex一直以來是vue生態(tài)中一個(gè)解決不同層級(jí)組件數(shù)據(jù)共享的優(yōu)質(zhì)方案。不僅是在父?jìng)髯又锌梢赃m用,在子傳父,或者祖先傳后代,后代傳祖先,兄弟組件間都是一個(gè)非常好的方案。因?yàn)樗且粋€(gè)集中狀態(tài)管理模式。其本質(zhì)實(shí)現(xiàn)也是響應(yīng)式的。這里只簡(jiǎn)單提一下Vue3中是如何使用的。
創(chuàng)建一個(gè)store
import { createStore } from 'vuex'export enum Mutarions {
SET_COUNT = 'SET_COUNT'
}
export default createStore({
state: {
count: 231
},
getters: {
count: state => state.count
},
mutations: {
[Mutarions.SET_COUNT]: (state, num: number) => (state.count = num)
}
})
復(fù)制代碼
父組件
fatherref="sonRef" />復(fù)制代碼
子組件
Son: {{ count }}復(fù)制代碼
子傳父
子級(jí)向父級(jí)傳遞數(shù)據(jù),可以有這三種方式:
v-on
refs方式
事件中心
refs方式
通過ref的方式向父級(jí)傳遞一個(gè)數(shù)據(jù)是同樣適用的。具體思路:子組件內(nèi)部實(shí)現(xiàn)一個(gè)函數(shù),該函數(shù)可以返回一個(gè)值。父級(jí)組件通過ref取到子組件實(shí)例后調(diào)用該方法,得到需要的返回值。
這里來看一下實(shí)際的應(yīng)用場(chǎng)景,我們希望ValidateForm組件去驗(yàn)證下面所有的表單項(xiàng),然后通過一個(gè)函數(shù)將組件內(nèi)部的一個(gè)驗(yàn)證狀態(tài)返回出去。
父組件
ref="validateFormRef" :model="formData" :rules="rules">label="用戶名" prop="keyword">label="密碼" prop="password">復(fù)制代碼
ValidateForm
復(fù)制代碼
這里來看一下大致的流程圖:
通過該種方法還可以拿到子組件內(nèi)部的數(shù)據(jù),這就跟閉包函數(shù)一樣的道理。
事件中心
這種通信方式為什么拿到這里來講呢?因?yàn)槲矣X接下的實(shí)際案例用上事件中心這種方式會(huì)非常的恰當(dāng)。在上一個(gè)小節(jié)中,我們留下來一個(gè)坑,那就是ValidateForm組件要去驗(yàn)證整個(gè)表單是否通過,就必須想辦法讓每個(gè)ValidateFormItem將內(nèi)部的校驗(yàn)結(jié)果返回給它。
首先會(huì)遇到兩個(gè)問題
ValidateForm下面的組件是通過插槽去掛載的,所以無法通過ref的方式去拿到每個(gè)子表單項(xiàng)的實(shí)例,所以就沒辦法拿到每個(gè)ValidateFormItem的驗(yàn)證狀態(tài)了。
上面的章節(jié)中有一個(gè)圖片,展示了通過ref拿到的組件實(shí)例。可以發(fā)現(xiàn),你可以找到$parent屬性,但是沒有$children屬性。這就很尷尬了,我們沒辦法像Vue2一樣在ValidateForm中通過$children拿到每個(gè)子組件的實(shí)例。
解決思路
既然沒有辦法拿到插槽上的組件實(shí)例,那咱們就繞開它,通過一個(gè)事件中心的方式來解決。思路是這樣的:
在ValidateForm實(shí)例初始化的時(shí)候,去創(chuàng)建一個(gè)事件中心Emitter實(shí)例,它可以注冊(cè)一個(gè)事件,當(dāng)這個(gè)事件被執(zhí)行時(shí)可以接受一個(gè)函數(shù),并存在一個(gè)隊(duì)列中。
將這個(gè)Emitter通過provide傳遞給后代,保證這個(gè)事件中心在不同的ValidateForm組件中都是獨(dú)立的。換句話說,就是如果寫了多個(gè)ValidateForm,他們的事件中心不會(huì)相互干擾。
在ValidateFormItem中使用inject接收自己所在表單域的Emitter,在掛載的時(shí)候,執(zhí)行Emitter上的事件,將自己的內(nèi)部的validate函數(shù),傳遞發(fā)送給ValidateForm,并由其將方法緩存在隊(duì)列中。
ValidateForm執(zhí)行校驗(yàn)的時(shí)候,就可以執(zhí)行隊(duì)列中的所有校驗(yàn)函數(shù),并得出校驗(yàn)結(jié)果。
具體代碼實(shí)現(xiàn):
先來實(shí)現(xiàn)一個(gè)Emitter事件中心的類
import { EmitterHandles } from '@/type/utils'export class Emitter {
// 存放事件函數(shù)
private events: EmitterHandles = {}
// 用于注冊(cè)事件
on(eventName: string, eventHandle: Function) {
this.events[eventName] = eventHandle
}
// 刪除事件
off(eventName: string) {
if (this.events[eventName]) {
delete this.events[eventName]
}
}
// 觸發(fā)事件
emit(eventName: string, ...rest: any[]) {
if (this.events[eventName]) {
this.events[eventName](...rest)
}
}
}
復(fù)制代碼
當(dāng)事件中心實(shí)現(xiàn)好了,這里來完善一下ValidateForm的代碼
復(fù)制代碼
ok,現(xiàn)在實(shí)現(xiàn)了validateForm的邏輯,我們?cè)賮韺懸幌聉alidateFormItem的邏輯
class="form-group">v-if="label" class=" col-form-label">{{ label }}v-if="error.isError" class="invalid-feedback">{{ error.errorMessage }}
復(fù)制代碼
為了更詳細(xì)的理解上面的過程,這里來畫一個(gè)示意圖:
注冊(cè)事件,分發(fā)事件中心
執(zhí)行事件,發(fā)送驗(yàn)證函數(shù)
整個(gè)過程的總結(jié)就是,頂層組件創(chuàng)建和分發(fā)事件中心,并注冊(cè)事件監(jiān)聽函數(shù)。后代組件執(zhí)行該事件然后發(fā)送信息,頂層組件回收信息。
Tips
這里再提一點(diǎn),在使用Emitter這個(gè)事件中心的時(shí)候,是在ValidateForm的setup中去創(chuàng)建并且去下發(fā)的,并不是使用一個(gè)全局的事件中心。就像大佬的這篇文章Vue組件通信方式及其應(yīng)用場(chǎng)景總結(jié)中總結(jié)到的,事件總線的形式是有一個(gè)致命缺點(diǎn)的,如果一個(gè)頁面上有多個(gè)公共組件,我們只要向其中的一個(gè)傳遞數(shù)據(jù),但是每個(gè)公共組件都綁定了數(shù)據(jù)接受的方法,那就會(huì)出現(xiàn)混亂的情況。但是,我們的事件總線不是一個(gè)全局的,而是單個(gè)作用域里面的一個(gè)事件中心。
因?yàn)槭录行氖窃诋?dāng)前組件內(nèi)部創(chuàng)建,并使用provide向下發(fā)布的,這樣就只有當(dāng)前組件的后代才能使用這個(gè)事件中心。所以,就算一個(gè)面上寫了多個(gè)ValidateForm,他們的校驗(yàn)都是獨(dú)立的。
ref="validateFormRef1" :model="formData1" :rules="rules">label="用戶名" prop="keyword">label="密碼" prop="password">ref="validateFormRef2" :model="formData2" :rules="rules">label="用戶名" prop="keyword">label="密碼" prop="password">復(fù)制代碼
示意圖:
事件中心總結(jié)
優(yōu)點(diǎn):
可以解決Vue3不能使用this.$children的問題
可以靈活使用,不受組件層級(jí)的限制
這種通信方式不受框架的限制
缺點(diǎn):
需要控制好事件中心的作用范圍
需要控制好事件名的規(guī)范
事件中心進(jìn)階
因?yàn)樵赩ue3的Composition API中,vue的功能api更加的顆粒化。我們可以對(duì)事件中心進(jìn)行一個(gè)自定義需求的改造。
可以通過引入reactive, ref幫助我們的事件中心內(nèi)部維護(hù)一個(gè)響應(yīng)式的數(shù)據(jù),可以實(shí)現(xiàn)當(dāng)事件中心進(jìn)行一定通信行為時(shí),去更新對(duì)應(yīng)的視圖。還可以引入computed實(shí)現(xiàn)計(jì)算屬性的功能。
import { reactive, ref, computed } from 'vue'export class Emitter {
// 響應(yīng)式的數(shù)據(jù)中心
private state = reactive({})
private events: EmitterHandles = ref({})
// 記錄當(dāng)前事件中心 事件的數(shù)量
private eventLength = computed(() => Object.keys(events.value).length)
// 省略部分代碼
}
復(fù)制代碼
加入watch,watchEffect實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽做出一定邏輯行為的功能。我認(rèn)為Composition API和React Hooks Api都是非常強(qiáng)大,因?yàn)樗鼈冊(cè)试S我們將功能函數(shù)當(dāng)成積木一樣去任意組裝成我們希望得到的應(yīng)用程序。
深層后代向頂層通信,兄弟通信
我覺得其實(shí)其他的場(chǎng)景,其通信方式基本都差不多了,所謂千篇一律。后代向祖先傳值,或者兄弟組件傳值,都可以使用vuex或者是事件中心的方式。兄弟層級(jí),或者相鄰層級(jí)的,就可以使用ref,$parent等方式。
總結(jié)
以上是生活随笔為你收集整理的com组件的ref有时需要有时不需要?_Vue3组件通信总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 语音通话框架_教资公告还没出,普通话测试
- 下一篇: c语言笔记照片_C语言学习笔记