上次將 Composition API
大致梳理了一遍 ,這次主要是想記錄一些 vue3
相較 vue2
新增出來的一些特性和一些方法上使用的變動,話不多說,直接開擼愤炸。
Teleport
我們?nèi)粘i_發(fā)中經(jīng)常會遇到這樣一個場景,比如我們封裝一個彈層 msk
組件掉奄,但是它包裹在一個 position: relative
定位的父元素中规个,此時它將被局限在父元素的大小中,我們很難將彈層鋪滿整個窗口姓建。而 Teleport
提供了一種干凈的方法诞仓,允許我們控制在 DOM
中哪個父節(jié)點下渲染了 HTML
,而不必求助于全局狀態(tài)或?qū)⑵洳鸱譃閮蓚€組件速兔。舉個栗子:
<body>
<div id="root"></div>
<div id="center"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div class="mask-wrapper">
<msk></msk>
</div>
`
})
app.component('msk', {
template: `
<div class="msk">111</div>
`
})
app.mount('#root')
</script>
瀏覽器渲染結(jié)果如下:
這肯定不是我們想要實現(xiàn)的效果墅拭,我們希望蒙層是充滿整個窗口的,此時我們可以直接將蒙層組件通過 teleport
渲染到 body
下面或者我們指定的 dom
節(jié)點下面憨栽,teleport
上面有一個 to
的屬性帜矾,它接受一個 css query selector
作為參數(shù)翼虫。如下栗子:
<script>
const app = Vue.createApp({
template: `
<div class="mask-wrapper">
// 使用 to 屬性將其掛載到 id = center 的 dom 節(jié)點下
// 我們也可以直接使用 to = body 將其直接掛載到 body 中
<teleport to="#center">
<msk></msk>
</teleport>
</div>
`
})
</script>
emits
我們知道在 vue2
中父子組件傳值會用到 props
和 $emit
屑柔,但是在 vue3 中新增了 emits
,它的主要作用是匯總該組件有哪些自定義事件珍剑,可以是一個數(shù)組寫法掸宛,也可以是一個對象寫法,同時在對象寫法中還支持自定義函數(shù)招拙,可以在運行時驗證參數(shù)是否正確唧瘾。
了解它的基礎用法之后我們將 teleport
中寫入的小栗子重寫,讓其組件通信完成最基本的顯示和隱藏的動態(tài)交互功能别凤。當然我們在子組件通過 $emit
觸發(fā)的事件要統(tǒng)一寫入 emits
數(shù)組中進行管理饰序。
const app = Vue.createApp({
template: `
<div class="mask-wrapper">
<button @click="openMsk">打開彈層</button>
<teleport to="#center">
<msk :isOpen="isOpen" @closeMsk="closeMsk"></msk>
</teleport>
</div>
`,
setup() {
const { ref } = Vue
const isOpen = ref(false)
const openMsk = () => {
isOpen.value = true
}
const closeMsk = () => {
isOpen.value = false
}
return { openMsk, isOpen, closeMsk }
}
})
app.component('msk', {
props: ['isOpen'],
// 子組件中我們會向父組件觸發(fā) `closeMsk` 事件,所以將其統(tǒng)一寫入 `emits` 中方便管理維護
emits: ['closeMsk'],
template: `
<div class="msk" v-show="isOpen">
<button @click="closeMsk">關閉彈層</button>
</div>
`,
setup(props, context) {
const closeMsk = () => {
context.emit('closeMsk')
}
return { closeMsk }
}
})
app.mount('#root')
當然我們也可以在 emits
中使用對象寫法规哪,并且傳入驗證的自定義函數(shù):
app.component('msk', {
props: ['isOpen'],
emits: {
// 'closeMsk': null, 無需驗證
'closeMsk': (payload) => {
return payload === 111 // 事件觸發(fā)時驗證傳入的值是否為 111
// 驗證失敗求豫,因為我傳入的是 222
// 無效的事件參數(shù):事件“closeMsk”的事件驗證失敗。
}
}诉稍,
setup(props, context) {
const closeMsk = () => {
context.emit('closeMsk', 222)
}
return { closeMsk }
}
}
小伙伴們可以試一試蝠嘉,當然即使我傳入的值和驗證時的值不匹配但是并不會影響這個事件的正常執(zhí)行,只是會在瀏覽器中給出警告提示杯巨。
Suspense
和 teleport
組件一樣蚤告,這也是 vue3.0
新推出來的一個全新的組件,它的主要作用是和異步組件一起使用服爷,我們可以現(xiàn)在這里回憶一下 vue2.0
中我們是如何使用動態(tài)組件和異步組件的杜恰。
動態(tài)組件
vue 2.0
和 vue3.0
動態(tài)組件的使用方式基本差不多获诈,都是根據(jù)數(shù)據(jù)的變化,結(jié)合 component
這個標簽箫章,來隨時動態(tài)切換組件的實現(xiàn)烙荷。這里簡單做個小回顧:
const app = Vue.createApp({
setup() {
const { ref, keepAlive } = Vue
const currentItem = ref('first-item')
const handleClick = () => {
if (currentItem.value === 'first-item') {
currentItem.value = 'second-item'
} else {
currentItem.value = 'first-item'
}
}
return { currentItem, handleClick }
},
template: `
<keep-alive>
<component :is="currentItem"></component>
</keep-alive>
<button @click="handleClick">組件切換</button>
`
})
app.component('first-item', {
template: `
<div>hello world</div>
`
})
app.component('second-item', {
template: `
<input type="text" />
`
})
app.mount('#root')
異步組件
以前,異步組件是通過將組件定義為返回 Promise 的函數(shù)來創(chuàng)建的檬寂,這里可以直接查看 vue2.0 中如何定義異步組件终抽。但是在 vue3.0
中現(xiàn)在,由于函數(shù)式組件被定義為純函數(shù)桶至,因此異步組件的定義需要通過將其包裝在新的 defineAsyncComponent 助手方法中來顯式地定義昼伴,其實也很簡單,看栗子就知道了:
const { defineAsyncComponent } = Vue
const app = Vue.createApp({
template: `
<div>
<async-show></async-show>
<async-common-item></async-common-item>
</div>
`
})
app.component('asyncShow', defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
template: `<div>我將在 1s 之后被渲染出來</div>`
})
}, 1000)
})
}))
app.component('async-common-item', defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
template: `<div>我將在 3s 之后被渲染出來</div>`
})
}, 3000)
})
}))
defineAsyncComponent
接受返回 Promise
的工廠函數(shù)镣屹。從服務器檢索組件定義后圃郊,應調(diào)用 Promise
的 resolve
回調(diào)。你也可以調(diào)用 reject(reason)
女蜈,來表示加載失敗持舆。
接下來就可以引入我們的主角 Suspense
組件,他可以用來接收一個或多個異步組件伪窖,它本身支持兩個具名插槽逸寓,一個承載異步插件返回等待狀態(tài)的插槽,一個承載異步插件返回成功狀態(tài)的插槽覆山。舉個小栗子:
const { defineAsyncComponent } = Vue
const app = Vue.createApp({
template: `
<Suspense >
<template #default> // 異步組件成功內(nèi)容包裹在 default 插槽中
<async-show></async-show>
</template>
<template #fallback> // 異步組件未加載時顯示 fallback里的內(nèi)容
<h1>loading !!!!</h1>
</template>
</Suspense>
`
})
當然 Suspense
組件也支持多個異步組件的插入竹伸,并且它會等待所有異步組件都返回才將其顯示出來,不過此時我們需要在其根上包一層簇宽,如下栗子:
const app = Vue.createApp({
template: `
<Suspense >
<template #default>
<div>
<async-show></async-show>
<async-common-item></async-common-item>
</div>
</template>
<template #fallback>
<h1>loading !!!!</h1>
</template>
</Suspense>
`
})
Provide / Inject
通常勋篓,當我們需要從父組件向子組件傳遞數(shù)據(jù)時,我們使用 props魏割。想象一下這樣的結(jié)構:有一些深度嵌套的組件譬嚣,而深層的子組件只需要父組件的部分內(nèi)容。在這種情況下钞它,如果仍然將 prop
沿著組件鏈逐級傳遞下去拜银,可能會很麻煩。
對于這種情況须揣,我們可以使用一對 provide
和 inject
盐股。無論組件層次結(jié)構有多深,父組件都可以作為其所有子組件的依賴提供者耻卡。這個特性有兩個部分:父組件有一個 provide
選項來提供數(shù)據(jù)疯汁,子組件有一個 inject
選項來開始使用這些數(shù)據(jù)。
上面兩段話摘自官網(wǎng)卵酪,說的很明白幌蚊,基礎的用法其實和 vue2
中差不多谤碳,但是我們知道 vue2
中無法實現(xiàn)數(shù)據(jù)的響應式監(jiān)聽,但是 vue3
中我們使用 composition API
就可以完成對應響應式變化的監(jiān)聽溢豆。我們先來回顧一下 vue2
中的基礎用法:
父組件像孫子組件傳遞固定值
const app = Vue.createApp({
provide: {
count: 1
},
template: `
<child />
`
})
app.component('child', {
template: `
<child-child></child-child>
`
})
app.component('child-child', {
inject: ['count'],
template: `
<div>{{count}}</div>
`
})
如果我們想使用 provide
傳遞數(shù)據(jù) data
中的值時蜒简,我們就不能用上面這種寫法,我們需要將 provide
轉(zhuǎn)換為返回對象的函數(shù)漩仙。栗子如下:
const app = Vue.createApp({
data() {
return {
count: 1
}
},
provide() {
return {
count: this.count
}
}
})
父組件像孫子組件動態(tài)傳值
如果此時我們新增一個按鈕改變父組件中 count
的值搓茬,子組件是無法繼續(xù)監(jiān)聽到改變后的值的。此時如果我們想對祖先組件中的更改做出響應队他,我們需要為 provide
的 count
分配一個組合式API computed
const app = Vue.createApp({
data() {
return {
count: 1
}
},
methods: {
handleClick() {
this.count++
}
},
provide() {
return {
count: Vue.computed(() => this.count)
}
},
template: `
<child />
<button @click="handleClick">增加</button>
`
})
app.component('child', {
template: `
<child-child></child-child>
`
})
app.component('child-child', {
inject: ['count'],
template: `
<div>{{count.value}}</div>
`
})
當然此時我們還沒有用到 Composition API
來實現(xiàn)卷仑,我們在使用 Composiiton API
來重構下上面的代碼:
const { ref, provide, inject } = Vue
const app = Vue.createApp({
setup() {
let count = ref(1)
const handleClick = () => {
count.value++
}
provide('count', count)
return { handleClick }
},
template: `
<child />
<button @click="handleClick">增加</button>
`
})
app.component('child', {
template: `
<child-child></child-child>
`
})
app.component('child-child', {
setup() {
let count = inject('count')
return { count }
},
template: `
<div>{{count}}</div>
`
})
是不是感覺非常簡單,那么問題來了麸折,剛剛我們使用了 provide / inject
實現(xiàn)了父組件和子孫組件中的數(shù)據(jù)傳遞锡凝,如果兩個毫無關聯(lián)的組件,那么我們應該如何建立數(shù)據(jù)通訊呢垢啼?除了 vuex
你最先能想到什么窜锯,在 2.x
中,Vue
實例可用于觸發(fā)通過事件觸發(fā) API
強制附加的處理程序 ($on
芭析,$off
和 $once
)锚扎,這用于創(chuàng)建 event hub
,以創(chuàng)建在整個應用程序中使用的全局事件偵聽器放刨,因為我前面寫過相關文章工秩,關于 vue2
的知識就不在這里過多贅述了尸饺,詳情可以點擊 Vue 常見 API 及問題进统。
但是在 vue3
中廢棄了 $on, $off
,為什么會廢棄呢浪听,可以參考文章解讀Vue3中廢棄組件事件進行解讀螟碎。官方推薦我們使用第三方庫 mitt
進行全局事件的綁定和監(jiān)聽。大體用法其實和原來差不多迹栓,這里就不過多贅述了掉分。
Mixin
Mixin
應該算 vue2
中用的比較多的,用法其實和以前大體相差不大克伊,我在 Vue 常見 API 及問題 中記錄過 Mixin
的基礎用法酥郭,官網(wǎng)寫的也挺詳細的,當然現(xiàn)在關于組件公共邏輯的抽離其實更推薦使用 組合式 API 愿吹。這里還是簡單總結(jié)下 Mixin
的幾個特點:
1不从、混入過程中,組件
data
犁跪、methods
椿息、優(yōu)先級高于mixin data
歹袁,methods
優(yōu)先級。
2寝优、生命周期函數(shù)条舔,先執(zhí)行mixin
里面的,在執(zhí)行組件里面的乏矾。
3孟抗、自定義的屬性,組件中的屬性優(yōu)先級高于mixin
屬性優(yōu)先級钻心。
什么叫自定義屬性呢夸浅?我們來看個小栗子:
const app = Vue.createApp({
number: 3,
template: `
<div>{{number}}</div>
`
})
我們直接定義了一個屬性 number
,它既不在 data
中扔役,也不在 setup
中帆喇,而是直接掛載在 app
上,那么它就是 app
上的自定義屬性亿胸。此時我們無法在模板中直接使用 this
訪問到這個 number
屬性坯钦,必須要通過 this.$options
才能訪問到它:
const app = Vue.createApp({
number: 3,
template: `
<div>{{this.$options.number}}</div>
`
})
如果我們此時在 mixin
中也定義一個 number
屬性:
const myMixin = {
number: 1
}
const app = Vue.createApp({
mixins: [myMixin],
number: 3,
template: `
<div>{{this.$options.number}}</div>
`
})
前面我們說過,mixin
的優(yōu)先級低于組件優(yōu)先級侈玄,所以此時肯定輸出的是 3
婉刀,但是如果我們希望 mixin
的優(yōu)先級高于組件優(yōu)先級,我們就可以使用 app.config.optionMergeStrategies
自定義選項合并策略:
const myMixin = {
number: 1
}
const app = Vue.createApp({
mixins: [myMixin],
number: 3,
template: `
<div>{{this.$options.number}}</div>
`
})
// 接收兩個參數(shù)序仙,配置優(yōu)先返回第一個參數(shù)突颊,如找不到在返回第二個參數(shù)
app.config.optionMergeStrategies.number = (mixinValue, appValue) => {
return mixinValue || appValue
}
自定義指令
我們先假想一個使用場景,如果我們希望在頁面加載的時候自動獲取 input
框的焦點事件潘悼,我們一般會這樣寫:
const app = Vue.createApp({
template: `
<input ref="input" />
`,
mounted() {
this.$refs.input.focus()
}
})
假如另一個組件中又有一個 input
律秃,那么我們就又需要在那個組件的 dom
元素節(jié)點處定義 ref
,然后在組件的生命周期中調(diào)用一遍 this.$refs.input.focus()
治唤。如果我們可以定義一個全局的 autofocus
事件棒动,只要遇到 input
我們就通過給定的指令直接觸發(fā)那應該怎么辦呢?此時我們就可以用到自定義指令了:
const app = Vue.createApp({
template: `
<input ref="input" v-focus />
`
})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
通過過 app.directive
我們定義了一個全局的 focus
指令宾添,指令的使用只需要在前面加上 v-
即可船惨;當然指令也和組件一樣,有著生命周期缕陕,我們在 mounted
的時候可以拿到使用指令的 dom
元素節(jié)點粱锐,然后操作這個節(jié)點完成對應的功能。當然上面我們使用 app.directive
將指令定義到了全局扛邑,日常開發(fā)中我們可能更多的是使用局部指令:
// 定義指令集合怜浅,因為可以是多個指令,所以是復數(shù)
const directives = {
focus: {
mounted(el) {
el.focus()
}
}
}
const app = Vue.createApp({
directives,
template: `
<input ref="input" v-focus />
`
})
動態(tài)指令參數(shù)
例如我們想通過一個指令實時改變 input
框的位置鹿榜,那么此時我們寫下代碼:
const app = Vue.createApp({
template: `
<input class="input" ref="input" v-pos />
`
})
app.directive('pos', {
mounted(el) {
el.style.top = '200px'
}
})
上面這個栗子雖然我們每次使用 v-pos
都會改變輸入框的 top
值海雪,但是如果我們希望這個值不是固定的 200
锦爵,而是指令中傳給我們的數(shù)字,那該如何進行改造呢奥裸?
官網(wǎng)的文檔資料告訴了我們险掀,指令中的參數(shù)可以是動態(tài)的,例如湾宙,在 v-mydirective:[argument]="value"
中樟氢,argument
參數(shù)可以根據(jù)組件實例數(shù)據(jù)進行更新!這使得自定義指令可以在應用中被靈活使用侠鳄。光看文字可能有點糊埠啃,我們來使用實際的栗子:
// 場景一:指令接收傳值
const app = Vue.createApp({
template: `
<input class="input" ref="input" v-pos="100" />
`
})
app.directive('pos', {
mounted(el, binding) {
// binding.value 是我們傳遞給指令的值——在這里是 200
el.style.top = binding.value + 'px'
}
})
其實上面的小栗子還有個缺點,就是我們將 v-pos
的值定義在 data
中伟恶,但是我們實時改變 data
中的值碴开,頁面并不會產(chǎn)生對應的響應式變化。那是因為我們指令注冊的過程中 mounted
生命周期只會執(zhí)行一遍博秫,所以如果我們希望對應變化的產(chǎn)生就可以使用 updated
生命周期:
const app = Vue.createApp({
data() {
return {
top: 100
}
},
template: `
<input class="input" v-pos="top" />
`
})
app.directive('pos', {
mounted(el, binding) {
el.style.top = binding.value + 'px'
},
// 通過 updated 監(jiān)聽指令值的實時變化
updated(el, binding) {
el.style.top = binding.value + 'px'
}
})
const vm = app.mount('#root')
當然如果我們在 mounted
和 updated
時觸發(fā)相同行為潦牛,而不關心其他的鉤子函數(shù)。那么你可以通過將這個回調(diào)函數(shù)傳遞給指令來實現(xiàn):
app.directive('pos', (el, binding) => {
el.style.top = binding.value + 'px'
})
如果應用場景升級挡育,我們不僅希望它只是是在 top
上的偏移巴碗,而是通過我們指定傳入的方向值進行偏移,那么應該如何實現(xiàn)呢即寒?這時使用動態(tài)參數(shù)就可以非常方便地根據(jù)每個組件實例來進行更新橡淆。
// 場景二:動態(tài)指令參數(shù)
const app = Vue.createApp({
template: `
<input class="input" ref="input" v-pos:[direction]="100" />
`,
data() {
return {
direction: 'bottom'
}
}
})
app.directive('pos', {
mounted(el, binding) {
// binding.arg 是我們傳遞給指令的參數(shù)
const direction = binding.arg || 'top'
el.style[direction] = binding.value + 'px'
}
})
你可以試著使用自定義組件完成一個這樣的功能?
插件
我們在 vue
項目中經(jīng)常會使用別人寫好的插件母赵,例如 vue-router
逸爵、vue-touch
等,那么我們?nèi)绾巫约壕帉懸粋€插件呢市咽?看官網(wǎng)的介紹:插件是自包含的代碼痊银,通常向 Vue
添加全局級功能抵蚊。它可以是公開 install()
方法的 object
施绎,也可以是 function
。光看這句話可能有點懵贞绳,其實就傳達給了我們兩點訊息:
1谷醉、編寫插件可以是一個對象寫法,也可以是一個函數(shù)寫法
2冈闭、插件有一個公開的install()
默認方法俱尼,它接收 vue 實例和你自定義的屬性兩個形參。
舉個栗子:
// 對象寫法:
const myPlugin = {
install(app, options) {
console.log(app, options) // vue 實例萎攒,{name: "cc"}
}
}
app.use(myPlugin, { name: 'cc' })
//函數(shù)寫法:
const myPlugin = (app, options) => {
console.log(app, options)
}
app.use(myPlugin, { name: 'cc' })
插件一般怎么寫呢遇八?我們使用插件的時候額外的參數(shù)會放到 options
中矛绘,而 app
是使用這個插件的時候 vue
對應的實例。我們既然能得到實例刃永,我們就可以對其做很多拓展货矮,例如:
const app = Vue.createApp({
template: `
<child></child>
`
})
// 子組件就可以通過 inject 接收到我們寫的插件里的 `name`
app.component('child', {
inject: ['name'],
template: `<div>{{name}}</div>`
})
// 自己寫插件,在上面通過 `provide` 拓展一個 name 屬性
const myPlugin = (app, options) => {
app.provide('name', 'cc')
}
app.use(myPlugin, { name: 'cc' })
官網(wǎng)栗子中給出了 app.config.globalProperties
這個語法斯够,其實就是對 vue
全局的屬性做一些拓展囚玫,比如我們想在全局上加入 sayHello
這樣一個屬性,我們一般會使用 app.config.globalProperties.$sayHello
這樣去寫读规,在屬性名前加入 $
符號代表這是我們自己在 vue
全局添加的一個私有屬性抓督,更方便我們管理。此時我們就可以在組件中直接訪問到這個全局私有屬性:
app.config.globalProperties.$sayHello = 'hello cc'
// 子組件直接通過 this 使用 $sayHello 屬性
app.component('child', {
inject: ['name'],
template: `<div>{{name}}</div>`,
mounted() {
console.log(this.$sayHello)
}
})
結(jié)合官網(wǎng)束亏,我們是否可以簡單的寫一個小插件铃在,例如表單中的 input
框輸入檢測,對輸入的值進行一些基礎的校驗碍遍,如下栗子:
當然涌穆,這個簡單的小栗子肯定難不倒聰明的我們,其實我們可以使用一個全局 mixin
就可以完成這個功能:
const app = Vue.createApp({
data() {
return {
name: 'cc',
age: '18'
}
},
template: `
<div>
姓名: <input type="text" v-model="name" />
<span class="hint" v-if="this.$options.rules.name.error">
{{this.$options.rules.name.message}}
</span>
</div>
<div>
年齡: <input type="number" v-model="age" />
<span class="hint" v-if="this.$options.rules.age.error">
{{this.$options.rules.age.message}}
</span>
</div>
`,
rules: {
name: {
validate: name => name.length > 3,
error: false,
message: '用戶名最少為4個字符'
},
age: {
validate: age => age > 20,
error: false,
message: '年齡不能小于 20 歲'
}
}
})
// 校驗插件
const validatorPlugin = (app, options) => {
app.mixin({
created() {
const rules = this.$options.rules
for (let key in rules) {
let item = rules[key]
this.$watch(key, (value) => {
if (!item.validate(value)) {
item.error = true
} else {
item.error = false
}
}, {
immediate: true
})
}
}
})
}
app.use(validatorPlugin)
我們在組件中定義了 rules
屬性雀久,所以我們可以通過 this.$options.rules
直接訪問到這個屬性宿稀,然后我們通過 watch
監(jiān)聽 name
和 age
的變化,通過回調(diào)函數(shù)來校驗值是否滿足條件赖捌,當然判斷的過程中我們知道 watch
是有惰性的祝沸,所以我們在 watch
的配置中要加上 immediate: true
,這樣就可以在頁面加載完成時立即執(zhí)行越庇。這樣我們就完成了一個迷你版的 input
校驗功能罩锐。
自定義 v-model
vue2
中自定義 v-model
的實現(xiàn)及雙向綁定的原理我已經(jīng)寫過對應的文章了,Vue 中如何自定義 v-model 及雙向綁定的實現(xiàn)原理 卤唉,老版本的 v-model
有幾個痛點:
1蟀悦、比較繁瑣,要添加一個 model 屬性
2芽丹、組件上只能有一個 v-model褥赊,如果組件上出現(xiàn)多個 v-model,實現(xiàn)就比較困難
3熬的、對初學者比較不友好痊硕,看的云里霧里
所以在 vue3
中對 v-model
也是進行了大刀闊斧的改革,在 vue3
中實現(xiàn) v-model
不需要再給組件添加一個 model
屬性押框,只需要:
1岔绸、在組件的
props
中添加一個modelValue
的屬性
2、更新值的時候組件中的emit
時有一個update:modelValue
的方法
我們直接通過一個栗子來認識 vue3
中的自定義 v-model
:
const app = Vue.createApp({
data() {
return {
count: 1
}
},
template: `
<div>{{count}}</div>
<child v-model="count"></child>
`
})
app.component('child', {
// props 中默認的 modelValue 接收父組件中 count 的值
props: {
modelValue: String
},
template: `
<div @click="handleClick">{{modelValue}}</div>
`,
methods: {
handleClick() {
// 組件更新值的時候使用規(guī)定的 `update: modelValue` 方法
this.$emit('update:modelValue', this.modelValue + 3)
}
}
})
前面說過,vue3
中可以使用多個 v-model
盒揉,那么我們在來看看多個 v-model
的應用:
const app = Vue.createApp({
data() {
return {
count: 1,
age: 18
}
},
template: `
<div>父組件的值</div>
<div>{{count}}</div>
<div>{{age}}</div>
<div>子組件 v-model 綁定的值</div>
<child v-model="count" v-model:age="age"></child>
`
})
app.component('child', {
props: {
modelValue: Number,
age: Number
},
template: `
<div @click="handleClick">{{modelValue}}</div>
<input :value="age" @input="handleInput" type="number" />
`,
methods: {
handleClick() {
this.$emit('update:modelValue', this.modelValue + 3)
},
handleInput(event) {
this.$emit('update:age', +(event.target.value))
}
}
})
當我們在 v-model
后面不接入任何參數(shù)時晋被,就可以直接在子組件中使用默認的 modelValue
與父組件中 v-model
的值進行綁定,而當我們在 v-model:age
傳入 age
參數(shù)之后刚盈,對應的子組件的 props
中也需要改成 age
墨微,而更新值的時候組件中的 emit
中的方法也要改成對應的 update: age
。其實新版本中的 v-model
使用更簡單更方便扁掸,同時可以綁定多個互不干擾翘县。
非 Prop 的 Attribute
官網(wǎng)給出的定義為一個非 prop
的 attribute
是指傳向一個組件,但是該組件并沒有相應 props 或 emits 定義的 attribute
谴分。常見的示例包括 class
锈麸、style
和 id
屬性。咋看這段解釋可能有點懵牺蹄,其實我們可以通過一些栗子來看問題
Attribute 繼承
當組件返回單個根節(jié)點時忘伞,非 prop attribute
將自動添加到根節(jié)點的 attribute
中。例如下列栗子:
const app = Vue.createApp({
template: `
<child type="number" class="parent"></child>
`
})
app.component('child', {
template: `
<div class="child">
<input />
</div>
`
})
被渲染的 child
組件實際代碼結(jié)構如下:
// class 和 type 都被渲染到根節(jié)點上去了
<div class="child parent" type="number">
<input>
</div>
禁用 Attribute 繼承
如果你不希望組件的根元素繼承 attribute
沙兰,你可以在組件的選項中設置 inheritAttrs: false
氓奈。例如:禁用 attribute
繼承的常見情況是需要將 attribute
應用于根節(jié)點之外的其他元素。
通過將 inheritAttrs
選項設置為 false
鼎天,你可以訪問組件的 $attrs property
舀奶,該 property
包括組件 props
和 emits property
中未包含的所有屬性 (例如,class
斋射、style
育勺、v-on
監(jiān)聽器等)。還是上面的栗子罗岖,我們需要 child
組件中的 input
去渲染對應的 class
和 type
涧至,我們就可以將代碼改寫一下:
app.component('child', {
inheritAttrs: false,
template: `
<div class="child">
<input v-bind="$attrs" />
</div>
`
})
此時我們再從瀏覽器中查看 DOM
元素節(jié)點就可以看到如下結(jié)構:
<div class="child">
<input type="number" class="parent">
</div>
多個根節(jié)點上的 Attribute 繼承
如果我們的子組件存在多個根節(jié)點怎么辦,例如:
const app = Vue.createApp({
template: `
<child class="child"></child>
`
})
app.component('child', {
template: `
<div class="header" >
<input />
</div>
<div class="main" v-bind="$attrs">main</div>
<div class="footer">footer</div>
`
})
如果我們不在其中一個根組件使用 v-bind = "$attrs"
控制臺就會給我們報錯桑包,我們在其中一個根節(jié)點上使用之后父組件上對應的 attribute
就會被繼承到這個根組件上南蓬。
當然我們也可以禁止掉根節(jié)點上的繼承,直接在 header
結(jié)構下的 input
框加入 v-bind = "$attrs"
即可哑了。如下栗子:
app.component('child', {
inheritAttrs: false,
template: `
<div class="header" >
<input v-bind="$attrs" />
</div>
<div class="main">main</div>
<div class="footer">footer</div>
`
})
查漏補缺
我們知道在 vue2
模板中可以通過在 DOM
結(jié)構中指定 ref
屬性赘方,然后在邏輯代碼中通過 this.$refs.
去操作 DOM
,那么在 vue3
中我們應該如何操作 DOM
元素呢垒手?
我們先來一個簡單的場景蒜焊,判斷點擊的 dom
元素是否在 id = 'index'
的 dom
結(jié)構中,場景代碼如下:
// 判斷點擊的 dom 元素節(jié)點是不是在 index 中
const app = Vue.createApp({
template: `
<div id="index">
<div id="index-list">555</div>
</div>
<div id="demo">666</div>
`,
})
此時我們就要進行 DOM
元素節(jié)點判斷科贬,結(jié)合 setup
我們應該如何去使用 ref
來獲取 dom
元素節(jié)點呢?代碼比較簡單就直接上結(jié)果了:
const app = Vue.createApp({
template: `
<div id="index" ref="index">
<div id="index-list">555</div>
</div>
<div id="demo">666</div>
`,
setup() {
const { ref, onMounted } = Vue
const index = ref(null)
onMounted(() => {
document.addEventListener('click', function (e) {
if (index.value.contains(e.target)) {
console.log('點擊的是 index 里面的元素')
} else {
console.log('點擊的不是 index 里面的元素')
}
})
})
return { index }
}
})
因為 setup
的執(zhí)行是在 beforeCreate
和 created
之間,所以我們?nèi)绻肽玫綄?dom
元素節(jié)點榜掌,最好在其內(nèi)部的生命周期中進行獲取优妙。
vue3
中相較 vue2
大體的改動和日常開發(fā)中經(jīng)常會遇到的問題基本都已經(jīng)整理的差不多了,由于 vue3
代碼基本都是 ts
寫的憎账,所以學習 ts
其實已經(jīng)迫在眉睫的套硼。結(jié)尾綜合做個小栗子吧:
其實很簡單,就是一個 form 表單提交驗證胞皱,不過封裝基本用的是 vue3 + ts
邪意,小伙伴可以自己獨立實現(xiàn)一個類似 element-ui
中的表單效驗插件,其實組件開發(fā)更多的是學習思路以及代碼的擴展性反砌,優(yōu)雅性雾鬼。最近 github
經(jīng)常打不開,源代碼就放在 gitee 上了宴树。本文多為自己學習筆記記錄策菜,如有錯誤,歡迎指正>票帷S趾!