Vue3用法總結(jié)


highlight: a11y-dark

模板語法

1.屬性綁定的值是 nullundefined時(shí)酿愧,則該屬性會(huì)從元素上被移除:

<div :id="dynamicId"></div>

2.布爾型屬性的值為 false時(shí),該屬性會(huì)從元素上被移除:

<button :disabled="isButtonDisabled">Button</button>

3.指令是帶有 v- 前綴的特殊屬性敛熬,如:v-html黑低;

v-bindv-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-ifv-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)用 watchwatchEffect 返回的函數(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 氮趋、emitsv-on 事件的屬性 ;最常見的就是 class弟塞、style 和 id凭峡。

當(dāng)子組件只有一個(gè)根元素時(shí),透傳的 attribute 會(huì)自動(dòng)被添加到子組件的根元素上决记。例如:在父組件中為子組件添加的classstyle摧冀,會(huì)直接被透傳到子組件的根元素上,并與子組件根元素上的classstyle合并系宫。

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ì)變得很繁瑣新思;而provideinject 可以將父組件作為依賴提供者窖梁,為所有后代組件注入由父組件提供的依賴。

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>

官方文檔:https://v3.cn.vuejs.org/guide/teleport.html#%E4%B8%8E-vue-components-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8

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)性含。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洲赵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胶滋,更是在濱河造成了極大的恐慌板鬓,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件究恤,死亡現(xiàn)場離奇詭異俭令,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)部宿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門抄腔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓢湃,“玉大人,你說我怎么就攤上這事赫蛇∶嗷迹” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵悟耘,是天一觀的道長落蝙。 經(jīng)常有香客問我,道長暂幼,這世上最難降的妖魔是什么筏勒? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮旺嬉,結(jié)果婚禮上管行,老公的妹妹穿的比我還像新娘。我一直安慰自己邪媳,他們只是感情好捐顷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雨效,像睡著了一般迅涮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上设易,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天逗柴,我揣著相機(jī)與錄音,去河邊找鬼顿肺。 笑死戏溺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屠尊。 我是一名探鬼主播旷祸,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讼昆!你這毒婦竟也來了托享?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤浸赫,失蹤者是張志新(化名)和其女友劉穎闰围,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體既峡,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羡榴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了运敢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片校仑。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忠售,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迄沫,到底是詐尸還是另有隱情稻扬,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布羊瘩,位于F島的核電站泰佳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尘吗。R本人自食惡果不足惜乐纸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摇予。 院中可真熱鬧,春花似錦吗跋、人聲如沸侧戴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酗宋。三九已至,卻和暖如春疆拘,著一層夾襖步出監(jiān)牢的瞬間蜕猫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工哎迄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留回右,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓漱挚,卻偏偏與公主長得像翔烁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子旨涝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容