highlight: a11y-dark
模板語法
1.屬性綁定的值是 null
或 undefined
時(shí)酿愧,則該屬性會(huì)從元素上被移除:
<div :id="dynamicId"></div>
2.布爾型屬性的值為 false
時(shí),該屬性會(huì)從元素上被移除:
<button :disabled="isButtonDisabled">Button</button>
3.指令是帶有 v-
前綴的特殊屬性敛熬,如:v-html
黑低;
v-bind
和v-on
是特殊的內(nèi)置指令,分別可以以簡寫方式書寫挽拔,冒號后是指令參數(shù):
<a v-bind:[attributeName]="url"> ... </a>
<!-- 簡寫 -->
<a :[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 簡寫 -->
<a @[eventName]="doSomething">
邏輯渲染
1.如果要使用v-if
切換多個(gè)元素辆脸,就可以用 <template>
包裹:
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
注意事項(xiàng)
v-show
不支持在 <template>
元素上使用
不推薦同時(shí)使用 v-if 和 v-for,因?yàn)槎叩膬?yōu)先級不明顯螃诅;
當(dāng) v-if
和 v-for
同時(shí)存在于一個(gè)元素上時(shí)啡氢,v-if
的優(yōu)先級高,這意味著 v-if 會(huì)首先被執(zhí)行,且無法訪問到 v-for
作用域內(nèi)的變量:
v-for
// 通過參數(shù)二獲取索引
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
// 直接解構(gòu)對象元素
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
// 可使用 `of` 替代 `in`枯跑,作用相同:
<div v-for="item of items"></div>
//遍歷對象屬性旷坦,參數(shù)二為屬性名,參數(shù)三為索引:
<li v-for="(value, key,index) in myObject">
{{ key }}: {{ value }}
</li>
輸入綁定
修飾符
.lazy
默認(rèn)情況下搀崭,v-model
在每次 input
事件后更新數(shù)據(jù) (IME 拼字階段除外)叨粘,但可以通過添加 lazy
修飾符來改為在每次 change
事件后更新數(shù)據(jù):
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
.number
將輸入自動(dòng)轉(zhuǎn)換為數(shù)字:(如果輸入值無法被 parseFloat()
處理,則返回原始值)
<input v-model.number="age" />
.trim
自動(dòng)去除輸入內(nèi)容中兩端的空格:
<input v-model.trim="msg" />
偵聽器
深層偵聽器
watch
淺層偵聽時(shí)瘤睹,嵌套屬性變化并不會(huì)觸發(fā)偵聽器升敲;
watch
深偵聽時(shí),會(huì)遍歷被偵聽對象的所有嵌套屬性默蚌,當(dāng)被偵聽對象過于復(fù)雜時(shí)冻晤,性能開銷會(huì)很大;
1.0> 直接給 watch()
傳入一個(gè)響應(yīng)式對象绸吸,會(huì)隱式地創(chuàng)建一個(gè)深層偵聽器:
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的屬性變更時(shí)觸發(fā)
// 注意:`newValue` 此處和 `oldValue` 是相等的
// 因?yàn)樗鼈兪峭粋€(gè)對象鼻弧!
})
2.0> 如下淺層偵聽,一個(gè)返回響應(yīng)式對象的 getter
函數(shù)锦茁,只有在返回不同對象時(shí)攘轩,才會(huì)觸發(fā)偵聽回調(diào):
watch(
() => state.someObject,
() => {
// 僅當(dāng) state.someObject 被替換時(shí)觸發(fā)
}
)
watchEffect()
watch
只追蹤明確偵聽的數(shù)據(jù)源,并且僅在數(shù)據(jù)源改變時(shí)才會(huì)觸發(fā)回調(diào)码俩;
watchEffect
會(huì)在回調(diào)同步執(zhí)行過程中自動(dòng)追蹤所有能訪問到的響應(yīng)式屬性度帮,并在創(chuàng)建后會(huì)立即執(zhí)行一次;
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
注意:watchEffect
僅在同步執(zhí)行期間才追蹤依賴稿存;在使用異步回調(diào)時(shí)笨篷,只有在第一個(gè) await
正常工作前能夠訪問到的屬性才會(huì)被追蹤;
回調(diào)的觸發(fā)時(shí)機(jī)
如果在響應(yīng)式狀態(tài)更新后瓣履,同時(shí)觸發(fā)了DOM
更新和偵聽器回調(diào)率翅,則側(cè)偵聽器回調(diào)會(huì)在 Vue 組件 DOM 更新之前被調(diào)用,這意味著在偵聽器回調(diào)中只能訪問到 DOM 更新之前的狀態(tài)袖迎。如果想在偵聽器回調(diào)中能訪問更新之后的DOM冕臭,就需要指明 flush: 'post'
選項(xiàng):
watch(source, callback, {
flush: 'post'
})
// 或
watchEffect(callback, {
flush: 'post'
})
后置刷新的 watchEffect()
有個(gè)更方便的別名 watchPostEffect()
:
watchPostEffect(() => {
/* 在 Vue 組件 DOM 更新后執(zhí)行 */
})
停止偵聽器
在 setup()
或 <script setup>
中用同步語句創(chuàng)建的偵聽器,會(huì)自動(dòng)綁定到宿主組件實(shí)例上燕锥,并且在宿主組件卸載時(shí)自動(dòng)停止辜贵。因此,大多數(shù)情況下無需關(guān)心如何停止偵聽器归形。
一個(gè)關(guān)鍵點(diǎn)是托慨,偵聽器必須用同步語句創(chuàng)建:如果在異步回調(diào)中創(chuàng)建偵聽器,那么它不會(huì)綁定到當(dāng)前組件上连霉,就必須手動(dòng)停止榴芳,以防內(nèi)存泄漏:
<script setup>
import { watchEffect } from 'vue'
// 它會(huì)自動(dòng)停止
watchEffect(() => {})
// ...這個(gè)則不會(huì)嗡靡!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
要手動(dòng)停止一個(gè)偵聽器,要調(diào)用 watch
或 watchEffect
返回的函數(shù):
const unwatch = watchEffect(() => {})
// ...當(dāng)該偵聽器不再需要時(shí)
unwatch()
注意窟感,需要異步創(chuàng)建偵聽器的情況很少讨彼,請盡可能選擇同步創(chuàng)建;如果需要等待一些異步數(shù)據(jù)柿祈,可以使用條件式的偵聽邏輯:
// 需要異步請求得到的數(shù)據(jù)
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 數(shù)據(jù)加載后執(zhí)行某些操作...
}
組件基礎(chǔ)
子組件
子組件通過 defineEmits
宏來聲明需要拋出的事件哈误,然后調(diào)用 $emit
方法出事件:
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
動(dòng)態(tài)組件
參數(shù) is
是被注冊的組件名或?qū)氲慕M件對象
<component :is="tabs[currentTab]"></component>
組件傳值
Props 聲明
子組件使用 defineProps()
宏顯式聲明所接受的 props
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
// Props聲明
defineProps({
greetingMessage: String
})
// 屬性傳值
<MyComponent greeting-message="hello" />
靜態(tài) vs 動(dòng)態(tài) Prop
1.0> 靜態(tài)傳值:參數(shù)值是字符時(shí),則無須綁定躏嚎,直接賦值即可蜜自;
<BlogPost title="My journey with Vue" />
2.0> 動(dòng)態(tài)傳值:參數(shù)值是非字符時(shí),則需要?jiǎng)討B(tài)綁定(當(dāng)參數(shù)類型為Boolean時(shí)卢佣,可以不傳值重荠,默認(rèn)會(huì)轉(zhuǎn)為true);
<!-- 變量 -->
<BlogPost :title="post.title" />
<!-- 表達(dá)式 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
<!-- Number -->
<BlogPost :likes="42" />
<!-- Boolean(僅寫上 prop 但不傳值虚茶,會(huì)隱式轉(zhuǎn)換為 `true`) -->
<BlogPost is-published />
<!-- Boolean -->
<BlogPost :is-published="false" />
<!-- 數(shù)組 -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- 對象 -->
<BlogPost :author="{ name: 'Veronica', company: 'Veridian Dynamics'}" />
使用一個(gè)對象綁定多個(gè) prop
props 對象的屬性可以分別當(dāng)作 props 傳入:
const form = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="form" />
## 或
<BlogPost :id="id" :title="title" />
單向數(shù)據(jù)流
props 遵循單向綁定原則戈鲁,數(shù)據(jù)狀態(tài)向下流往子組件,而不會(huì)逆向傳遞嘹叫;這避免了子組件修改父組件的情況婆殿,否則數(shù)據(jù)流將很容易變得混亂而難以理解。一般要在子組件中修改 prop 的需求無非以下兩種場景:
1.0>prop 被用于傳入初始值罩扇;這種情況下婆芦,最好是新定義一個(gè)局部數(shù)據(jù)屬性,從 props 上獲取初始值即可喂饥,如下:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
2.0> 需要對傳入的 prop 值做進(jìn)一步轉(zhuǎn)換消约;這種情況中,最好是基于該 prop 值定義一個(gè)計(jì)算屬性:
const props = defineProps(['size'])
const normalizedSize = computed(() => props.size.trim().toLowerCase())
Prop 校驗(yàn)
props聲明時(shí)可以約束數(shù)據(jù)類型员帮、是否必填荆陆;
defineProps({
// 1.0> 基礎(chǔ)類型檢查
// (給出 `null` 和 `undefined` 值則會(huì)跳過任何類型檢查)
propA: Number,
// 2.0> 多種可能的類型
propB: [String, Number],
// 3.0> 必傳,且為 String 類型
propC: {
type: String,
required: true
},
// 4.0> Number 類型的默認(rèn)值
propD: {
type: Number,
default: 100
},
// 5.0> 對象類型的默認(rèn)值
propE: {
type: Object,
// 對象或數(shù)組的默認(rèn)值必須從一個(gè)工廠函數(shù)返回
default(rawProps) {
return { message: 'hello' }
}
},
// 6.0> 自定義類型校驗(yàn)函數(shù)
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 7.0> 函數(shù)類型的默認(rèn)值
propG: {
type: Function,
// 不像對象或數(shù)組的默認(rèn)集侯,這不是一個(gè)工廠函數(shù),這會(huì)是一個(gè)用來作為默認(rèn)值的函數(shù)
default() {
return 'Default function'
}
}
})
注意:
defineProps()
宏中的參數(shù)不可以訪問 <script setup>
中定義的其他變量,因?yàn)榫幾g時(shí)整個(gè)表達(dá)式都會(huì)被移到外部的函數(shù)中帜消。
所有 prop 默認(rèn)都是可選的棠枉,除非聲明了 required: true
。
除 Boolean
外的未傳遞可選 prop 都會(huì)有一個(gè)默認(rèn)值 undefined
泡挺。
事件傳遞
觸發(fā)與監(jiān)聽事件
## 子組件中
<button @click="$emit('someEvent')">click me</button>
## 父組件中
<MyComponent @some-event.once="callback" />
注:和原生 DOM 事件不一樣辈讶,組件觸發(fā)的事件沒有冒泡機(jī)制;只能監(jiān)聽直接子組件觸發(fā)的事件娄猫;平級或多層嵌套的組件間通信贱除,應(yīng)使用Vuex或Pinia生闲;
聲明事件
在 <template>
中可以使用$emit
直接拋出事件,但在 <script setup>
中要先通過 defineEmits()
宏聲明事件月幌;
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
emit('submit')
</script>
defineEmits()
宏不能在子函數(shù)中使用碍讯,必須放在 <script setup>
的頂級作用域下。
emits
選項(xiàng)還支持對象語法扯躺,它允許我們對觸發(fā)事件的參數(shù)進(jìn)行驗(yàn)證:
<script setup>
const emit = defineEmits({
submit(payload) {
// 通過返回值為 `true` 還是為 `false` 來判斷
// 驗(yàn)證是否通過
}
})
</script>
在 TypeScript 中捉兴,還可以使用純類型標(biāo)注來聲明觸發(fā)的事件:
<script setup lang="ts">
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
注:如果原生事件的名字 (如 click
) 被定義在 emits
選項(xiàng)中,則監(jiān)聽器只會(huì)監(jiān)聽組件觸發(fā)的 click
事件而不再響應(yīng)原生 click
事件录语。
v-model
參數(shù)
默認(rèn)情況下倍啥,v-model
在組件上使用 modelValue
作為 prop
,并以 update:modelValue
作為對應(yīng)事件澎埠;也可通過給 v-model
指定一個(gè)參數(shù)來更改默認(rèn) prop
的名稱:
<MyComponent v-model:title="bookTitle" />
如下虽缕,在子組件中應(yīng)聲明一個(gè) title prop
,并通過觸發(fā) update:title
事件更新父組件值:
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
透傳 Attributes
Attributes 繼承
“透傳 attribute”是指傳給一個(gè)組件蒲稳,卻沒有被該組件聲明為 props
氮趋、emits
或 v-on 事件
的屬性 ;最常見的就是 class弟塞、style 和 id凭峡。
當(dāng)子組件只有一個(gè)根元素時(shí),透傳的 attribute 會(huì)自動(dòng)被添加到子組件的根元素上决记。例如:在父組件中為子組件添加的class
或style
摧冀,會(huì)直接被透傳到子組件的根元素上,并與子組件根元素上的class
和style
合并系宫。
v-on
監(jiān)聽器繼承
v-on
事件監(jiān)聽器同樣也會(huì)被透傳索昂;如果子組件根元素自身也通過 v-on
綁定了事件監(jiān)聽器,則這個(gè)監(jiān)聽器和從父組件繼承的監(jiān)聽器都會(huì)被觸發(fā)扩借。
深層組件繼承
如果子組件的根元素又是一個(gè)組件椒惨,則透傳屬性會(huì)繼續(xù)向下傳遞;但透傳不包括聲明過的 props
或針對 emits
聲明事件的v-on
偵聽潮罪;也就是說康谆,聲明過的 props
和偵聽函數(shù)已經(jīng)被當(dāng)前組件消費(fèi)了,因此不會(huì)再往下傳遞嫉到。
禁用 Attributes 繼承
如果不想要組件自動(dòng)地繼承 attribute沃暗,可以在組件選項(xiàng)中設(shè)置 inheritAttrs: false``;
如果使用了 <script setup>
何恶,就需要一個(gè)額外的 <script>
塊來書寫這個(gè)選項(xiàng)聲明:
<script>
// 使用普通的 <script> 來聲明選項(xiàng)
export default {
inheritAttrs: false
}
</script>
<script setup>
// ...setup 部分邏輯
</script>
最常見的需要禁用 attribute 繼承的場景就是 attribute 需要應(yīng)用在根節(jié)點(diǎn)以外的其他元素上孽锥;將inheritAttrs
設(shè)為false
,就可以完全控制透傳進(jìn)來的 attribute 被如何使用。
透傳進(jìn)來的 attribute 可以在模板的表達(dá)式中直接用 $attrs
訪問:
<span>Fallthrough attribute: {{ $attrs }}</span>
也可以通過 v-bind 將透傳的屬性應(yīng)用到任意元素上惜辑,如下:
<button class="btn" v-bind="$attrs">click me</button>
注意:a>. 和 props 不同唬涧,透傳 attributes 在 JavaScript 中保留了它們原始的大小寫,所以像 foo-bar
這樣 attribute 需要通過 $attrs['foo-bar']
來訪問盛撑。
b>. 像 @click
這樣的一個(gè) v-on
事件監(jiān)聽器將在此對象下被暴露為一個(gè)函數(shù) $attrs.onClick
碎节。
多根節(jié)點(diǎn)的 Attributes 繼承
多根節(jié)點(diǎn)的組件沒有自動(dòng)透傳的行為,因?yàn)閂ue并不知道要將 attribute 透傳到哪個(gè)根元素上撵彻;因此需要通過 v-bind
顯式綁定 $attrs
钓株,以明確需要將 attribute 透傳到哪個(gè)根元素,如下:
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
在 JavaScript 中訪問透傳 Attributes
在<script setup>
中可以使用 useAttrs()
API 來訪問所有透傳 attribute:
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
注意:attrs
對象是最新的透傳 attribute陌僵,但它并不是響應(yīng)式的 (考慮到性能因素)轴合;因此,不能通過偵聽器去監(jiān)聽其變化碗短;如果需要響應(yīng)性受葛,可以使用 prop。
插槽
渲染作用域
因?yàn)椴宀蹆?nèi)容在父組件模板中定義偎谁,因此可以訪問父組件作用域总滩,而無法訪問子組件的數(shù)據(jù);
總結(jié):Vue 模板中的表達(dá)式只能訪問其定義時(shí)所處的作用域巡雨,這和 JavaScript 的詞法作用域規(guī)則是一致的闰渔。
默認(rèn)內(nèi)容
在外部沒有提供任何內(nèi)容的情況下,可以在<slot>
標(biāo)簽之間為插槽指定默認(rèn)內(nèi)容:
<button type="submit">
<slot>
Submit <!-- 默認(rèn)內(nèi)容 -->
</slot>
</button>
具名插槽
<slot>
元素有一個(gè)特殊的name 屬性
铐望,用來給插槽分配唯一 ID冈涧,以確定每一處要渲染的內(nèi)容:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
這種帶 name
的插槽被稱為具名插槽,沒有提供 name
的插槽會(huì)隱式命名為“default”正蛙;
要為具名插槽傳入內(nèi)容督弓,需要使用含 v-slot
指令的 <template>
元素,并將目標(biāo)插槽的名字傳給該指令:
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的內(nèi)容放這里 -->
</template>
</BaseLayout>
// `v-slot` 有對應(yīng)的簡寫 `#`
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
</BaseLayout>
當(dāng)組件同時(shí)接收默認(rèn)插槽和具名插槽時(shí)乒验,所有位于頂級的非 <template>
節(jié)點(diǎn)都被隱式地視為默認(rèn)插槽內(nèi)容:
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<!-- 隱式的默認(rèn)插槽 -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
動(dòng)態(tài)插槽名
動(dòng)態(tài)指令參數(shù)在 v-slot
上也是有效的愚隧,即可以定義下面這樣的動(dòng)態(tài)插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 縮寫為 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
默認(rèn)情況下,插槽內(nèi)容無法訪問到子組件的數(shù)據(jù)锻全;如果要在插槽中訪問子組件的數(shù)據(jù)狂塘,就需要子組件主動(dòng)傳遞 prop 數(shù)據(jù)對象傳給插槽:
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
然后,在父組件中通過在子組件標(biāo)簽上定義 v-slot
指令鳄厌,來接收插槽 props 對象:
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
也可以在 v-slot
中使用解構(gòu):
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽
具名插槽也是類似的睹耐,插槽 props 可以作為 v-slot
指令的值被訪問到v-slot:name="slotProps",如下
:
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
向具名插槽中傳入 props:
<slot name="header" message="hello"></slot>
注意:插槽上的 name
是一個(gè) Vue 特別保留的屬性部翘,不會(huì)作為 props 傳遞給插槽;headerProps的結(jié)果是{ message="hello"}
依賴注入
Prop 逐級透傳問題
當(dāng)組件嵌套太深時(shí)响委,通過 Prop 逐級透傳數(shù)據(jù)會(huì)變得很繁瑣新思;而provide
和 inject
可以將父組件作為依賴提供者窖梁,為所有后代組件注入由父組件提供的依賴。
Provide (提供)
為后代組件提供數(shù)據(jù)夹囚,需要用到 provide()
函數(shù):
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
provide()
函數(shù)接收兩個(gè)參數(shù)纵刘;參數(shù)一是注入名,參數(shù)二注入值荸哟,注入值可以是任意類型假哎,包括響應(yīng)式狀態(tài);后代組件使用注入名來查找期望的值鞍历;可以多次調(diào)用 provide()
舵抹,使用不同的注入名,注入不同的依賴值劣砍;當(dāng)注入值為響應(yīng)式狀態(tài)時(shí)(如:ref)惧蛹,后代組件就可以由此和提供者建立響應(yīng)式聯(lián)系。
應(yīng)用層 Provide
可以在整個(gè)應(yīng)用層面提供依賴:
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
在應(yīng)用級別提供的數(shù)據(jù)在該應(yīng)用內(nèi)的所有組件中都可注入刑枝;
Inject (注入)
要注入上層組件提供的數(shù)據(jù)香嗓,需使用 inject()
函數(shù):
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
如果提供的值是一個(gè) ref,注入進(jìn)來的會(huì)是該 ref 對象装畅,而不會(huì)自動(dòng)解包為其內(nèi)部的值靠娱;
注入默認(rèn)值
默認(rèn)情況下,如果注入名不存在掠兄,則會(huì)拋出一個(gè)運(yùn)行時(shí)警告像云;但可為 inject 聲明一個(gè)默認(rèn)值,和 props 類似:
const value = inject('message', '這是默認(rèn)值')
默認(rèn)值也可通過函數(shù)或初始化類來獲得:
const value = inject('key', () => new ExpensiveClass())
和響應(yīng)式數(shù)據(jù)配合使用
盡量將任何對響應(yīng)式狀態(tài)的變更都保持在供給方組件中徽千,這樣可以確保所提供狀態(tài)的聲明和變更操作都內(nèi)聚在同一個(gè)組件內(nèi)苫费,使其更容易維護(hù)。
作為依賴供給方双抽,如果想確保提供的數(shù)據(jù)不能被注入方組件更改百框,可以使用 readonly()
來包裝提供的值:
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
使用 Symbol 作注入名
大型應(yīng)用中,可能會(huì)包含非常多的依賴提供牍汹,或者你正在編寫提供給其他開發(fā)者使用的組件庫铐维,建議最好使用 Symbol 來作為注入名以避免潛在的沖突。
推薦在一個(gè)單獨(dú)的文件中導(dǎo)出這些注入名 Symbol:
// keys.js
export const myInjectionKey = Symbol()
// 在供給方組件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, { /*
要提供的數(shù)據(jù)
*/ });
// 注入方組件
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
異步組件
基本用法
在大型項(xiàng)目中慎菲,可能需要拆分應(yīng)用為更小的塊嫁蛇,并僅在需要時(shí)從服務(wù)器加載組件;Vue 提供了 defineAsyncComponent
方法來實(shí)現(xiàn)此功能露该, 該方法接收一個(gè)返回 Promise 的加載函數(shù)睬棚,這個(gè) Promise 的 resolve
方法從服務(wù)器獲取組件,也可以調(diào)用 reject(reason)
表明加載失敗,如下:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...從服務(wù)器獲取組件
resolve(/* 獲取到的組件 */)
})
})
// ... 像使用其他一般組件一樣使用 `AsyncComp`
因?yàn)?ES 模塊動(dòng)態(tài)導(dǎo)入也會(huì)返回一個(gè) Promise抑党,所以多數(shù)情況下推薦將 defineAsyncComponent 和 ES動(dòng)態(tài)導(dǎo)入搭配使用包警;Vite 和 Webpack 都支持此語法 ,并且會(huì)將其作為打包時(shí)的代碼分割點(diǎn)底靠,如下:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
最后得到的 AsyncComp
是一個(gè)外層包裝過的組件害晦,僅在頁面需要渲染時(shí)才會(huì)調(diào)用加載函數(shù);AsyncComp 會(huì)將收到的 props 和插槽傳給內(nèi)部組件暑中,所以可以使用這個(gè)異步的包裝組件無縫地替換原始組件壹瘟,同時(shí)實(shí)現(xiàn)延遲加載。
與普通組件一樣鳄逾,異步組件可以使用 app.component()
全局注冊:
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
也可以直接在父組件中直接定義它們:
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AdminPage />
</template>
加載與錯(cuò)誤狀態(tài)
異步操作一般會(huì)涉及到加載中稻轨、加載錯(cuò)誤等狀態(tài),因此 defineAsyncComponent()
也支持在高級選項(xiàng)中處理這些狀態(tài):
const AsyncComp = defineAsyncComponent({
// 加載函數(shù)
loader: () => import('./Foo.vue'),
// 加載異步組件時(shí)使用的組件
loadingComponent: LoadingComponent,
// 展示加載組件前的延遲時(shí)間严衬,默認(rèn)為 200ms
delay: 200,
// 加載失敗后展示的組件
errorComponent: ErrorComponent,
// 如果提供了一個(gè) timeout 時(shí)間限制澄者,并超時(shí)了
// 也會(huì)顯示這里配置的報(bào)錯(cuò)組件,默認(rèn)值是:Infinity
timeout: 3000
})
加載組件會(huì)在內(nèi)部組件加載前(時(shí))先行顯示请琳,但在加載組件顯示之前默認(rèn)有 200ms 延遲粱挡,這是因?yàn)樵诰W(wǎng)絡(luò)狀況較好時(shí),加載完成得很快俄精,加載組件和最終組件之間的替換太快可能產(chǎn)生閃爍询筏,反而影響用戶感受。報(bào)錯(cuò)組件會(huì)在加載器函數(shù)返回的 Promise 拋錯(cuò)時(shí)被渲染竖慧;最后嫌套,還可以指定一個(gè)超時(shí)時(shí)間,在請求耗時(shí)超過指定時(shí)間時(shí)也會(huì)渲染報(bào)錯(cuò)組件圾旨。
組合式函數(shù)
組合式函數(shù)
“組合式函數(shù)”是利用 Vue 組合式 API 來封裝和復(fù)用有狀態(tài)邏輯的函數(shù)踱讨;與普通函數(shù)抽取不同的是在組合式函數(shù)中可以保留響應(yīng)性、訪問模板頁面砍的,就相當(dāng)于在 Vue 頁面中編寫代碼一樣痹筛。
通過抽取組合式函數(shù)改善代碼結(jié)構(gòu)
<script setup>
import { useFeatureA } from './featureA.js'
import { useFeatureB } from './featureB.js'
import { useFeatureC } from './featureC.js'
const { foo, bar } = useFeatureA()
const { baz } = useFeatureB(foo)
const { qux } = useFeatureC(baz)
</script>
和無渲染組件的對比
相比無渲染組件,組合式函數(shù)不會(huì)產(chǎn)生額外的組件實(shí)例開銷廓鞠,因此推薦在純邏輯復(fù)用時(shí)使用組合式函數(shù)帚稠,在需要同時(shí)復(fù)用邏輯和視圖布局時(shí)使用無渲染組件。
自定義指令
基本介紹
自定義指令則是為了復(fù)用涉及普通元素 DOM 訪問的邏輯床佳。
<script setup>
// 在模板中啟用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
在 <script setup>
中滋早,任何以 v
開頭的駝峰式命名的變量都可以被用作一個(gè)自定義指令,上述示例中vFocus
即可以在模板中以 v-focus
的形式使用砌们。
自定義指令全局注冊:
const app = createApp({})
// 使 v-focus 在所有組件中都可用
app.directive('focus', {
/* ... */
})
注意:只有當(dāng)所需功能只能通過直接 DOM 操作來實(shí)現(xiàn)時(shí)杆麸,才應(yīng)該使用自定義指令搁进。其他情況下應(yīng)該盡可能地使用 v-bind
這樣的內(nèi)置指令來聲明式地使用模板,這樣更高效角溃,也對服務(wù)端渲染更友好拷获。
注意:只有當(dāng)所需功能只能通過直接 DOM 操作來實(shí)現(xiàn)時(shí),才應(yīng)該使用自定義指令减细。其他情況下應(yīng)該盡可能地使用 v-bind
這樣的內(nèi)置指令來聲明式地使用模板,這樣更高效赢笨,也對服務(wù)端渲染更友好未蝌。
指令鉤子
自定義指令有很多鉤子函數(shù)(都是可選的),分別在不階段被調(diào)用茧妒,常用的是mounted
萧吠,如下:
const myDirective = {
// 在綁定元素的 attribute 前
// 或事件監(jiān)聽器應(yīng)用前調(diào)用
created(el, binding, vnode, prevVnode) {
// 下面會(huì)介紹各個(gè)參數(shù)的細(xì)節(jié)
},
// 在元素被插入到 DOM 前調(diào)用
beforeMount(el, binding, vnode, prevVnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點(diǎn)都掛載完成后調(diào)用
mounted(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件更新前調(diào)用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點(diǎn)都更新后調(diào)用
updated(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件卸載前調(diào)用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件卸載后調(diào)用
unmounted(el, binding, vnode, prevVnode) {}
}
內(nèi)置組件
Teleport
用于將組件內(nèi)部的一部分模板“傳送”到其它 DOM 結(jié)構(gòu)中去;
<Teleport> 接受兩個(gè)參數(shù)桐筏,參數(shù)一是傳送的目的地纸型, 是一個(gè) CSS 選擇器字符串;參數(shù)二是可用于動(dòng)態(tài)的控制傳送是否生效或開始梅忌;
最典型的應(yīng)用場景是狰腌,將代碼片段傳送到 dialog 模態(tài)框中去;
<Teleport to="body" :disabled="isMobile">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
Transition
<Transition>
會(huì)在一個(gè)元素或組件進(jìn)入和離開 DOM 時(shí)應(yīng)用動(dòng)畫牧氮。
TransitionGroup
<TransitionGroup>
會(huì)在 v-for
列表中的元素或組件被插入琼腔、移動(dòng)、移除時(shí)應(yīng)用動(dòng)畫踱葛。
KeepAlive
<KeepAlive>
是一個(gè)內(nèi)置組件丹莲,用于在多個(gè)組件間動(dòng)態(tài)切換時(shí)緩存被移除的組件實(shí)例。
Suspense
<Suspense>
是一個(gè)內(nèi)置組件尸诽,用于在組件樹中協(xié)調(diào)對異步依賴的處理甥材,它讓可以在組件樹上層等待下層的多個(gè)嵌套異步依賴項(xiàng)解析完成,并可以在等待時(shí)渲染一個(gè)加載狀態(tài)性含。