一、動態組件
1、簡介
? 在某些業務場景下,頁面的某模塊具有多個組件但在同一時間只顯示一個,需要在多個組件之間進行頻繁的切換,如:tab切換等場景。除了可以使用v-if
、v-show
根據不同條件顯示不同組件之外,還可以通過動態組件<component>
來實現相同的效果。<component>
雖然被稱為動態組件,但其并非是內置組件,而是屬于模板語法,在模板編譯階段會被編譯。
? 動態組件允許在同一掛載節點動態切換多個組件,可以根據具體條件,動態決定顯示的組件。比起v-if
/v-show
的實現方式來說,無需創建多個掛載節點,且代碼量更少。
? 動態組件默認只保持當前組件存活,其余被切換掉的組件會被卸載,但可以結合<KeepAlive>
組件實現被切換掉的組件保持存活狀態。
2、基礎用法
? 動態組件的核心在于<component>
標簽和is
屬性,Vue會根據is
屬性的值來決定具體渲染在<component>
標簽位置上的是哪個組件。
<component :is="son1"></component>
? 在通過is
屬性指定展示的子組件時,is
屬性的值可以是組件在引入到當前組件時定義的注冊名稱(String
類型,常在選項式API中使用),也可以是組件本身的定義(Component
類型,常在組合式API中使用)。
組合式API中使用組件本身的定義決定渲染組件:
? 在組合式API中,如果我們需要使用變量存儲導入的子組件實例,如果使用ref
,則控制臺會拋出warn
。因為ref
是將組件實例轉換為響應式對象,可能會導致不必要的性能開銷,建議使用markRaw
(對象本身)或shallowRef
(淺層響應式)來避免這種情況。
<template><div><h1>這就是動態組件</h1><!-- 根據按鈕點擊切換組件 --><button v-for="(item, index) in components" :key="index" @click="currentComponent = item">{{ index }}</button><!-- 使用動態組件 --><component :is="currentComponent" /></div>
</template><script setup lang="ts">
import { shallowRef } from 'vue'
// 導入子組件
import son1 from '../components/dtzj-son1.vue'
import son2 from '../components/dtzj-son2.vue'
import son3 from '../components/dtzj-son3.vue'// 定義當前展示的子組件 值為對子組件的引用
const currentComponent = shallowRef(son1)
// 定義子組件對象
const components = {son1,son2,son3
}// 2秒后切換顯示的子組件
setTimeout(() => {currentComponent.value = son3
}, 2000)
</script>
選項式API中使用組件注冊名決定渲染組件:
<template><component :is="view" />
</template><script>
import Foo from './Foo.vue'
import Bar from './Bar.vue'export default {components: { Foo, Bar },data() {return {view: 'Foo'}}
}
</script>
3、渲染普通HTML元素
? is
屬性的值還可以是普通的HTML標簽名(不包含<>
),但是要以字符串的形式設置。Vue會根據字符串的值渲染對應的HTML標簽。<component>
標簽可以寫成雙標簽的形式,內部包含其他內容。
<template><div><!-- 渲染普通HTML --><component v-for="(item, index) in htmlList" :key="index" :is="item">{{ item+'標簽' }}</component></div>
</template><script setup lang="ts">
import { ref } from 'vue'// 定義要渲染的html標簽
const htmlList = ref(['a','span','div','h5','p','i','aside'])
</script>
頁面效果:
4、渲染內置組件
? 動態組件還可以將內置組件(<Transition>
、Teleport
等)作為要渲染的內容,但實際上這種場景并不多見,因此就不展開敘述了。
<template><div><!-- 渲染內置組件 --><component :is="Transition"><div v-show="show">這里是需要過渡的內容</div></component><!-- 等同于 --><Transition><div v-show="show">這里是需要過渡的內容</div></Transition></div>
</template><script setup lang="ts">
// 導入內置組件
import { ref, Transition } from 'vue'
// 定義div的顯/隱
const show = ref(false)// 3秒后切換div的顯隱狀態
setTimeout(() => {show.value = true
}, 3000)</script><style scoped>
/* 定義過渡樣式 */
.v-enter-active,
.v-leave-active {transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {opacity: 0;
}
</style>
5、v-model的特殊性
? 在<component>
標簽上使用 v-model
時,模板編輯器會將其擴展為modelValue
的prop和update:modelValue
的事件監聽器,而并非原始的v-model
雙向綁定功能。
? 如果渲染的普通自定義子組件,內部可以接收prop和使用事件監聽器,或者使用defineModel()
宏方法,進行相應操作。
父組件:
<template><div><!-- 使用的動態組件 并使用v-mode --><component :is="currentComponent" v-model="test" /><h3>{{ test }}</h3></div>
</template><script setup lang="ts">
import { shallowRef } from 'vue'
// 導入子組件
import son1 from '../components/dtzj-son1.vue'// 定義當前展示的子組件 值為對子組件的引用
const currentComponent = shallowRef(son1)
// 定義一個變量
const test = ref('test')
</script>
子組件:
<template><div><h5>{{ modelVar }}</h5></div>
</template><script setup lang="ts">
import { onMounted } from 'vue'const modelVar = defineModel();onMounted(() => {setTimeout(() => {modelVar.value = '子組件更改了modelVar';}, 1000)
})
</script>
? 但如果渲染的是普通HTML元素,且是input
、textarea
、select
等本身可以使用v-model
的元素,則v-model
的雙向綁定功能不會起作用。如果需要實現雙向綁定,則可以手動通過對應的attribute
和事件來實現。
<script setup>
import { ref } from 'vue'const tag = ref('input')
const username = ref('')
</script><template><!-- 由于 'input' 是原生 HTML 元素,因此這個 v-model 不起作用 --><component :is="tag" v-model="username" />
</template>
二、KeepAlive
1、簡介
? 在上面的內容中,講解了動態組件的相關知識,但是在默認情況下,通過動態組件被切走的組件,會被卸載后被銷毀,其內部的所有變動過的狀態會丟失,等再次切換回該組件時,則會重新創建該組件的組件實例。但在某些場景下,我們不希望被切走的組件被銷毀,并且保留其內部狀態,那此時就需要借助KeepAlive
內置組件。
? 將KeepAlive
組件包裹在動態組件的外層,當動態組件發生切換時,默認會將所有被切走的非活躍組件進行緩存,而不是銷毀,并且組件內部的狀態也會被保留。
? 在需要頻繁反復切換動態組件的業務場景中,如:tab切換、路由轉換等,使用KeepAlive
組件可以減少組件實例的銷毀和創建過程,從而優化頁面的性能。
2、基礎用法
? KeepAlive
組件內部可以包裹動態組件,也可以包裹普通組件。但KeepAlive
組件在任何時間節點,只能有一個活躍組件作為其直接子節點,不允許多個組件共存作為直接子節點。
動態組件:
? 當KeepAlive
組件內部包裹動態組件時,如果動態組件發生的切換,那被切走的組件默認會被緩存,當再次切換回該組件時,將從緩存中將組件取出,重新顯示。
<template><div><KeepAlive><component :is="currentComponent" /></KeepAlive></div>
</template><script setup lang="ts">
import { shallowRef, KeepAlive } from 'vue'
import son1 from '../components/keepalive-son1.vue'
import son2 from '../components/keepalive-son2.vue'// 定義當前展示的子組件 值為對子組件的引用
const currentComponent = shallowRef(son1)// 3秒切換顯示的子組件
setTimeout(() => {currentComponent.value = son2
}, 3000)
</script>
普通組件:
? 當KeepAlive
組件內部包裹普通組件時,通常與v-if
/v-else-if
/v-else
指令結合使用,保證組件內部在同一時間節點只能有一個組件作為直接子節點。
<template><div><KeepAlive><son1 v-if="show === 1"></son1><son2 v-else></son2></KeepAlive></div>
</template><script setup lang="ts">
import { ref, KeepAlive } from 'vue'
import son1 from '../components/keepalive-son1.vue'
import son2 from '../components/keepalive-son2.vue'// 決定顯示的組件
const show = ref(1)// 3秒后切換顯示的子組件
setTimeout(() => {// currentComponent.value = son2show.value = 2;
}, 3000)
</script>
? 注意: 不能使用v-show
指令,因為其僅僅是通過設置display
屬性實現的元素顯隱,其節點依舊保留在DOM中,實際上會讓KeepAlive
組件內部同時存在多個直接子節點,從而引發報錯。
3、組件屬性
? KeepAlive
組件有三個可以指定的屬性,分別為:include
、exclude
和max
。
interface KeepAliveProps {/*** 如果指定,則只有與 `include` 名稱* 匹配的組件才會被緩存。*/include?: MatchPattern/*** 任何名稱與 `exclude`* 匹配的組件都不會被緩存。*/exclude?: MatchPattern/*** 最多可以緩存多少組件實例。*/max?: number | string
}type MatchPattern = string | RegExp | (string | RegExp)[]
include:
? KeepAlive
組件默認會緩存內部所有非活躍組件實例,但緩存過多的組件實例也會占用過多的內存資源,因此可以通過include
屬性顯式的指定要被緩存的組件,未被指定的組件則不會被緩存。
? include
屬性的值可以是英文逗號分割的字符串,或者一個正則表達式,以及包含兩種類型的數組。如果屬性值為后兩者,則需要使用v-bind
進行綁定。
<!-- 屬性值為字符串 如果要緩存多個組件 需要以英文逗號分割的 注意分隔符前后不加空格 -->
<KeepAlive include="keepalive-son1,keepalive-son2"><component :is="currentComponent" />
</KeepAlive>
<!--屬性值為數組形式 指定多個組件 -->
<KeepAlive :include="['keepalive-son1', 'keepalive-son2']"><component :is="currentComponent" />
</KeepAlive>
<!-- 屬性值為正則表達式 符合匹配條件的組件會被緩存 -->
<KeepAlive :include="/^keepalive-son/"><component :is="currentComponent" />
</KeepAlive><script setup lang="ts">
import { ref, shallowRef, KeepAlive } from 'vue'
// 注意這里的 son1 是在當前組件內的注冊名稱 組件本身生成的name屬性為 keepalive-son1
import son1 from '../components/keepalive-son1.vue'
import son2 from '../components/keepalive-son2.vue'// 定義當前展示的子組件 值為對子組件的引用
const currentComponent = shallowRef(son1)// 3秒后切換顯示的子組件
setInterval(() => {currentComponent.value === son1? (currentComponent.value = son2): currentComponent.value = son1
}, 3000)
</script>
? 該屬性的屬性值會與子組件的name
選項進行匹配,在選項式API中必須顯示的聲明name
選項,在組合式API中使用<script setup>
的組件會根據文件名稱隱式的生成完全相同的name
選項,無需手動聲明。
使用<script setup>
的組件keepalive-son1.vue
:
<template><div><p>這是子組件1中的count:{{ count }}</p><button @click="count++">add</button></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
const count = ref(1111)// 此時組件沒有顯式指定name屬性 則會隱式的設置name的值為 keepalive-son1
// KeepAlive 會根據 keepalive-son1 進行識別
</script>
非使用<script setup>
的組件keepalive-son2.vue
:
<template><div><p>這是子組件2中的count:{{ count }}</p><button @click="count++">add</button></div>
</template><script>
export default {// 顯式的指定name屬性name: 'KeepaliveSon2',data() {return {count: 2222};},};
</script>
? 如果在使用<script setup>
的單文件組件中,想要顯式的指定組件的name
屬性,可以通過defineOptions()
宏方法或者export default
語法來指定:
<!-- 方法一: 通過新的script + export default 定義組件的name屬性 -->
<!-- 注意兩個 script 的 lang 屬性要一致 -->
<script lang="ts">
export default {name: 'KeepaliveSon1',
}
</script><script setup lang="ts">
import { ref, onMounted } from 'vue'const count = ref(1111)
// 方法二: 通過 defineOptions 定義組件的name屬性
defineOptions({name: 'KeepaliveSon1',
})
</script>
exclude:
? exclude
屬性與include
屬性正好相反,用于指定哪些組件不會被緩存。屬性值類型也相同,可以是英文逗號分割的字符串,或者一個正則表達式,以及包含兩種類型的數組。
<!-- 屬性值為字符串 如果要指定不緩存多個組件 需要以英文逗號分割的 注意分隔符前后不加空格 -->
<KeepAlive exclude="KeepaliveSon1,KeepaliveSon2"><component :is="currentComponent" />
</KeepAlive>
<!--屬性值為數組形式 指定多個組件不緩存 -->
<KeepAlive :exclude="['keepalive-son1', 'keepalive-son2']"><component :is="currentComponent" />
</KeepAlive>
<!-- 屬性值為正則表達式 符合匹配條件的組件不會被緩存 -->
<KeepAlive :exclude="/^keepalive-son/"><component :is="currentComponent" />
</KeepAlive>
? 屬性值會與子組件的name
選項進行匹配,在選項式API中必須顯示的聲明name
選項,在組合式API中使用<script setup>
的組件會根據文件名稱隱式的生成完全相同的name
選項,無需手動聲明。如果想要顯式的指定name
屬性,可以通過defineOptions()
宏方法或者export default
語法來指定。
max:
? 該屬性用于指定KeepAlive
能緩存的組件實例數量,屬性值為一個非負整數。當切換的組件數量大于max
屬性值時,會自動將最先緩存的組件實例銷毀,只保留最近緩存的max
個組件實例,避免過渡占用內存資源。
<!-- 屬性值為2 只會緩存最近切換的兩個組件實例 -->
<KeepAlive max="2"><component :is="currentComponent" />
</KeepAlive>
4、生命周期
? 當<KeepAlive>
內部組件初始掛載時,掛載的組件會進入活躍狀態,如果發生組件切換,組件實例會從DOM上移除,但組件會被<KeepAlive>
緩存,組件狀態變為不活躍狀態,當組件重新被激活,掛載到DOM中時,組件狀態會再次變為活躍狀態。
? 針對被緩存組件的這兩種狀態變化,Vue提供了對應的生命周期鉤子函數供開發者調用:
使用<script setup>
的組件:
<script setup>
import { onActivated, onDeactivated } from 'vue'onActivated(() => {// 調用時機為首次掛載// 以及每次從緩存中被激活時調用
})onDeactivated(() => {// 在及組件卸載時// 以每次進入緩存時調用
})
</script>
未使用<script setup>
的組件:
<script>
export default {name: 'KeepaliveSon2',activated() {// 調用時機為首次掛載// 以及每次從緩存中被激活時調用console.log('子組件2被掛載/激活了');},deactivated() {// 在及組件卸載時// 以每次進入緩存時調用console.log('子組件2被緩存/卸載了');},
};
</script>
? <KeepAlive>
內部發生組件切換時,會先觸發被緩存組件的Deactivated
鉤子函數,再觸發要激活組件的Activated
鉤子函數。但如果項目使用了服務端渲染,則這兩個鉤子函數在服務器端渲染期間不會被觸發。
? 如果<KeepAlive>
緩存的組件內部還嵌套有其他后代組件,則后代組件也可以使用這兩個生命周期鉤子函數。在被激活時,后代組件的Activated
鉤子函數先觸發,再觸發根組件的Activated
鉤子函數;在被卸載時也是一樣,后代組件的Deactivated
鉤子函數先觸發,再觸發根組件的Deactivated
鉤子函數。