Vue3問(wèn)題:如何實(shí)現(xiàn)級(jí)聯(lián)菜單的數(shù)據(jù)懶加載?
序言
大家好,我是大澈!
本文約3100+字,整篇閱讀大約需要5分鐘。
本文主要內(nèi)容分三部分,第一部分是需求分析,第二部分是實(shí)現(xiàn)步驟,第三部分是問(wèn)題詳解。
如果您只需要解決問(wèn)題,請(qǐng)閱讀第一、二部分即可。
如果您有更多時(shí)間,進(jìn)一步學(xué)習(xí)問(wèn)題相關(guān)知識(shí)點(diǎn),請(qǐng)閱讀至第三部分。
一、需求分析
實(shí)現(xiàn)級(jí)聯(lián)菜單效果,點(diǎn)擊一級(jí)菜單某一項(xiàng),就會(huì)加載出對(duì)應(yīng)的二級(jí)菜單內(nèi)容,點(diǎn)擊二級(jí)菜單某一項(xiàng),就會(huì)加載出對(duì)應(yīng)的三級(jí)菜單內(nèi)容,以此類(lèi)推,最后一級(jí)為五級(jí)菜單。
對(duì)于加載而言,必須是懶加載效果,即點(diǎn)擊菜單某一項(xiàng)時(shí),才會(huì)真正去請(qǐng)求加載對(duì)應(yīng)的下一級(jí)菜單內(nèi)容。
對(duì)于菜單最后一級(jí)內(nèi)容,要插入一個(gè)輸入框,在輸入框內(nèi),用戶(hù)可以任意輸入版本號(hào),并且要求輸入框不可被選中。
當(dāng)然,我們的正題是數(shù)據(jù)的懶加載,級(jí)聯(lián)菜單中插入不可被選中的輸入框?qū)崿F(xiàn)算是附加內(nèi)容了。
二、實(shí)現(xiàn)步驟
1、為什么要使用數(shù)據(jù)懶加載
在項(xiàng)目中,我們使用數(shù)據(jù)的懶加載,會(huì)有如下好處:
- 提升用戶(hù)體驗(yàn)
- 減少初始加載時(shí)間
- 減輕網(wǎng)絡(luò)資源的消耗
了解了這些好處之后,再就是要了解數(shù)據(jù)懶加載的應(yīng)用場(chǎng)景。
這部分比較有用,所以多敘述一些。
數(shù)據(jù)懶加載在以下場(chǎng)景中特別有用:
- 長(zhǎng)列表或分頁(yè)數(shù)據(jù):當(dāng)你有一個(gè)包含大量數(shù)據(jù)的列表或分頁(yè)功能時(shí),可以使用數(shù)據(jù)懶加載來(lái)延遲加載列表項(xiàng)或分頁(yè)數(shù)據(jù)。只有當(dāng)用戶(hù)滾動(dòng)到可見(jiàn)范圍時(shí)才加載新的數(shù)據(jù),從而提高初始加載速度和響應(yīng)性能。
- 圖片或媒體資源:對(duì)于包含大量圖片或媒體資源的頁(yè)面,可以使用數(shù)據(jù)懶加載來(lái)延遲加載這些資源。只有當(dāng)圖片或媒體元素進(jìn)入視口時(shí)才進(jìn)行加載,從而減少初始加載時(shí)間和網(wǎng)絡(luò)資源的消耗。
- 條件性加載:當(dāng)一些數(shù)據(jù)或組件只在特定條件下才需要顯示或使用時(shí),可以使用數(shù)據(jù)懶加載。根據(jù)條件動(dòng)態(tài)加載數(shù)據(jù)或組件,避免不必要的加載和資源占用。
- 路由級(jí)別的懶加載:在Vue的路由中,可以使用懶加載來(lái)按需加載路由組件。只有當(dāng)用戶(hù)訪(fǎng)問(wèn)某個(gè)路由時(shí)才進(jìn)行組件的加載,提高初始加載速度和路由切換的性能。
- 異步加載模塊:對(duì)于大型的Vue項(xiàng)目,可以使用數(shù)據(jù)懶加載來(lái)異步加載模塊,減少初始加載時(shí)間。將模塊分割成小塊,并在需要時(shí)按需加載,提高應(yīng)用的性能和可維護(hù)性。
上述場(chǎng)景在此部分只做簡(jiǎn)單描述,如果你有空余時(shí)間,各個(gè)場(chǎng)景具體代碼實(shí)例請(qǐng)見(jiàn)第三部分總結(jié)。
我們這次的需求實(shí)現(xiàn),就是符合第1條場(chǎng)景。
2、數(shù)據(jù)懶加載代碼實(shí)例
當(dāng)選中某一級(jí)時(shí),動(dòng)態(tài)加載該級(jí)下的選項(xiàng)。
通過(guò)lazy開(kāi)啟動(dòng)態(tài)加載,并通過(guò)lazyload方法來(lái)設(shè)置加載數(shù)據(jù)源。
- lazyload方法有兩個(gè)參數(shù),第一個(gè)參數(shù)node為當(dāng)前點(diǎn)擊的節(jié)點(diǎn),第二個(gè)resolve為數(shù)據(jù)加載完成的回調(diào)(必須調(diào)用)。
- node可以解構(gòu)出level參數(shù),它從0開(kāi)始計(jì)數(shù),指明當(dāng)前點(diǎn)擊節(jié)點(diǎn)的層級(jí)。
- 為數(shù)據(jù)對(duì)象添加leaf屬性,是為了指明哪一級(jí)的對(duì)象是葉子節(jié)點(diǎn)。
模板代碼:
<template>
<!-- 級(jí)聯(lián)面板 -->
<el-cascader-panel :props="props"></el-cascader-panel>
</template>
邏輯代碼:
<script setup>
// 級(jí)聯(lián)面板配置項(xiàng)
const props = reactive({
// 開(kāi)啟懶加載
lazy: true,
// 懶加載加載數(shù)據(jù)源方法
async lazyLoad(node, resolve) {
const { level } = node;
// 已經(jīng)有數(shù)據(jù)了,不需要重復(fù)請(qǐng)求了
if (node.children && node.children.length > 0) return;
// 模擬掉接口
setTimeout(() => {
let result;
switch (level) {
case 0: // 一級(jí)目錄
result = [
{
value: "0",
label: "行內(nèi)轉(zhuǎn)賬-手機(jī)號(hào)",
children: []
},
{
value: "1",
label: "行內(nèi)轉(zhuǎn)賬-銀行賬號(hào)",
children: []
},
{
value: "2",
label: "匯款到當(dāng)?shù)劂y行",
children: []
},
{
value: "3",
label: "匯款到錢(qián)包",
children: []
},
{
value: "4",
label: "轉(zhuǎn)賬到CUPD",
children: []
}
];
break;
case 1: //二級(jí)目錄
// 省略此處代碼...
break;
case 2: //三級(jí)目錄
// 省略此處代碼...
break;
case 3: //四級(jí)目錄
// 省略此處代碼...
break;
case 4: // 五級(jí)目錄
result = [
{
value: "最低系統(tǒng)版本",
label: "最低系統(tǒng)版本要求"
},
{
value: "0",
label: "不啟用"
},
{
value: "1",
label: "內(nèi)測(cè)"
},
{
value: "2",
label: "上線(xiàn)"
}
];
// 設(shè)置五級(jí)菜單為葉子節(jié)點(diǎn),無(wú)子節(jié)點(diǎn)
result.forEach((item) => {
item.leaf = level >= 4;
});
break;
default:
result = [];
break;
}
resolve(result);
}, 1000);
}
});
</script>
3、插入輸入框代碼實(shí)例
通過(guò)插槽和條件判斷,在級(jí)聯(lián)面板中插入輸入框,其中data.label為節(jié)點(diǎn)對(duì)象的名稱(chēng)。
通過(guò)@click.stop阻止事件冒泡、寬度為父元素的100%、以及清除級(jí)聯(lián)面板組件一些默認(rèn)樣式,實(shí)現(xiàn)了輸入框不可被選中的效果。
模版代碼:
<template>
<!-- 級(jí)聯(lián)面板 -->
<el-cascader-panel :props="props">
<template #default="{ data }">
<!-- 輸入框節(jié)點(diǎn)對(duì)象 -->
<div v-if="data.label == '最低系統(tǒng)版本要求'" @click.stop style="width: 100%">
<div>{{ data.label }}</div>
<div class="searchText">
<el-input v-model="searchText" :placeholder="'最低系統(tǒng)版本要求'"></el-input>
</div>
</div>
<div v-else>
<div>{{ data.label }}</div>
</div>
</template>
</el-cascader-panel>
</template>
樣式代碼:
<style lang="scss" scope>
.el-cascader-panel.is-bordered {
border: none;
}
.el-cascader-menu:last-child .el-cascader-node:nth-of-type(1):has(.searchText) {
margin-bottom: 30px;
padding: 0;
&:hover,
&:focus {
background: #fff;
}
}
.el-cascader-menu__list {
padding: 20px 0;
}
</style>
三、問(wèn)題詳解
1、關(guān)于CascaderProps配置項(xiàng)的參數(shù)一覽
2、懶加載各個(gè)場(chǎng)景代碼實(shí)例總結(jié)
長(zhǎng)列表或分頁(yè)數(shù)據(jù)懶加載:
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<li v-if="loading">Loading...</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
loading: false,
};
},
mounted() {
this.loadMore(); // 初始加載
window.addEventListener('scroll', this.handleScroll);
},
methods: {
loadMore() {
this.loading true;
// 模擬異步加載數(shù)據(jù)
setTimeout(() => {
const newItems = /* 請(qǐng)求新數(shù)據(jù) */;
this.items = this.items.concat(newItems);
this.loading = false;
}, 1000);
},
handleScroll() {
const scrollPosition = window.innerHeight + window.pageYOffset;
const contentHeight = document.documentElement.scrollHeight;
if (scrollPosition >= contentHeight && !this.loading) {
this.loadMore(); // 滾動(dòng)到底部時(shí)加載更多數(shù)據(jù)
}
},
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll);
},
};
</script>
圖片懶加載:
<template>
<div>
<img :src="placeholder" ref="image" style="display: none;">
</div>
</template>
<script>
export {
data() {
return {
placeholder: require('@/assets/placeholder.png'), // 占位圖
};
},
mounted() {
window.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll() {
const imageElement = this.$refs.image;
const rect = imageElement.getBoundingClientRect();
if (rect.top < window.innerHeight) {
const src = /* 獲取圖片真實(shí)地址 */;
imageElement.src = src; // 加載圖片
window.removeEventListener('scroll', this.handleScroll); // 圖片加載后移除滾動(dòng)監(jiān)聽(tīng)
}
},
},
beforeDestroy() {
window.removeEventListener('scroll', this.handle);
},
};
</script>
條件性數(shù)據(jù)懶加載:
<template>
<div>
<button @click="showData = true">顯示數(shù)據(jù)</button>
<div v-if="showData">
<!-- 顯示數(shù)據(jù)的組件或內(nèi)容 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
showData: false,
};
},
};
</script>
路由懶加載(基于 Vue Router):
const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');
const Contact = () => import('./components/Contact.vue');
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact },
];
const router = new VueRouter({
routes,
});
異步加載模塊(基于 import() 動(dòng)態(tài)導(dǎo)入語(yǔ)法):
<template>
<div>
<button @click="loadModule">加載模塊</button>
<div v-if="moduleLoaded">
<!-- 顯示已加載的模塊 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
moduleLoaded: false,
};
},
methods: {
loadModule() {
import('./path/to/module.js')
.then((module) => {
// 模塊加載成功
this.moduleLoaded = true;
})
.catch((error) => {
console.error(error);
});
},
},
};
</script>