專業(yè)足球網(wǎng)站開發(fā)營銷型網(wǎng)站策劃書
前端組件庫造輪子——Tree組件開發(fā)教程
前言
本系列旨在記錄前端組件庫開發(fā)經(jīng)驗(yàn),我們的組件庫項(xiàng)目目前已在Github
開源,下面是項(xiàng)目的部分組件。文章會詳細(xì)介紹一些造組件庫輪子的技巧并且最后會給出完整的演示demo。
文章旨在總結(jié)經(jīng)驗(yàn),開源分享,有問題的話也希望路過的大佬指正。
組件開發(fā)流程
組件遞歸
Tree
組件算是比較有難度的組件了,其核心功能其實(shí)就是實(shí)現(xiàn)樹一樣的聯(lián)級結(jié)構(gòu)。其實(shí)實(shí)現(xiàn)就是組件遞歸。
我們來復(fù)習(xí)一下遞歸代碼
我們的遞歸代碼實(shí)現(xiàn),必然是由一個函數(shù)和調(diào)用函數(shù)組成的。同理,要實(shí)現(xiàn)組件遞歸也需要做類似的操作。
function dfs() {...
}function Main() {dfs()
}
在組件遞歸中,我們就需要類比遞歸函數(shù)的操作,我們需要用一個組件node
來作為遞歸組件,這個組件起到主要渲染的作用,并且需要一個tree
組件,來調(diào)用組件執(zhí)行。
🆗,現(xiàn)在知道了大致思路,我們在補(bǔ)充一下如何編寫組件。
對于遞歸函數(shù),很重要的一點(diǎn),我們?nèi)绾巫屗粩噙f歸同時讓他停下來。
我們可以利用props
把參數(shù)傳進(jìn)去,然后在渲染的時候去判斷有沒有孩子,如果沒有孩子就不渲染,這個可以用v-if
來完成。
// node 組件中
<div v-if="isRender" v-show="items.isOpen"><nodev-for="(child, index) in items.children":key="index":items="child":label="label":children="children"></node>
</div>// 判斷是否要渲染
const isRender = computed(() => {return (props.items.children && props.items.children.length);
});
那這樣我們就可以實(shí)現(xiàn)node
組件的正確遞歸,所以我們只需在tree
組件中在調(diào)用一次node
組件就可以了。
<div class="tree"><nodev-for="(item, index) in copyData":key="index":items="item":label="label":children="children"></node></div>
深拷貝和初始化
還沒完,我們需要對傳進(jìn)來的數(shù)據(jù)做一些深拷貝和初始化。
為什么要深拷貝應(yīng)該知道吧?vue
中props
是單向數(shù)據(jù)流,我們是不能直接修改的,因此我們需要深拷貝一份來操作。
const deepCopy = (target: any, hash_table = new WeakMap()) => {if (typeof target === "object") {let clone = Array.isArray(target) ? [] : {};if (hash_table.get(target)) return hash_table.get(target);hash_table.set(target, clone);for (const key in target) {clone[key] = deepCopy(target[key], hash_table);}return clone;} else {return target;}
};
為什么要初始化呢?因?yàn)樵陂_發(fā)tree
還需要預(yù)設(shè)置很多數(shù)據(jù),例如:是否展開?那需要實(shí)現(xiàn)展開的功能,那么每個節(jié)點(diǎn)必然需要一個isOpen
來控制,除此之外,還有很多其它的功能,比如判斷層級等。
interface dataType {label: string;children?: dataType[];isOpen: boolean;
}
const copyData = ref([]);
onMounted(() => {copyData.value = init(deepCopy(props.data));
});const init = (data: dataType[]) => {if (!data.length || !data) return [];let res = [];for (let i = 0; i < data.length; i++) {const child = data[i];const children = init(child[props.children] || []);const label = child[props.label];const isOpen = false;res.push({label,children,isOpen,});}return res;
};
展開和收縮
接下來,我們實(shí)現(xiàn)如何渲染節(jié)點(diǎn)和展開,這個其實(shí)很簡單,我們只要在遞歸組件上面補(bǔ)充我們的想要插入的數(shù)據(jù)即可,同時綁定好事件,利用isOpen
屬性來實(shí)現(xiàn)展開收縮,我們只需要在渲染v-if
上在添加v-show
即可。
<ul class="tree-node"><div class="tree-node-content" @click.stop="handleToggle(items)"><span>{{ items.label }}</span></div><div v-if="isRender" v-show="items.isOpen"><nodev-for="(child, index) in items.children":key="index":items="child":label="label":children="children"></node></div>
</ul>const handleToggle = (item: any) => {item.isOpen = !item.isOpen;
};
參數(shù)設(shè)置
接下來我們來設(shè)置一些參數(shù),因?yàn)槲覀儾磺宄脩魝鬟M(jìn)來的樹結(jié)構(gòu)的屬性是什么樣子的,因此我們可以用參數(shù)來標(biāo)識,比如用children
來標(biāo)識子節(jié)點(diǎn),這些東西就可以自由發(fā)揮了。
const props = defineProps({data: {type: Array,default: () => [],},label: {type: String,default: "label",},children: {type: String,default: "children",},
});
🆗到此為止,我們就把核心功能實(shí)現(xiàn)完成,其實(shí)基礎(chǔ)的功能并沒有多困難,后續(xù)會補(bǔ)充源碼。
懶加載優(yōu)化
在這里我補(bǔ)充一個優(yōu)化吧,一個簡單的懶加載可以是這樣的,只渲染第一層,深層的如果沒有點(diǎn)擊過就不去渲染。
這個實(shí)現(xiàn)思路也很容易,再增加一個isLazy
參數(shù),在初始化的時候給每個節(jié)點(diǎn)綁定上isLazy
,在渲染時v-if
增加判斷isLazy
就可以了。在點(diǎn)擊的時候再把isLazy
取消即可。
在參考了element
的源碼后,他們的懶加載還可以傳入一個load
函數(shù),并用isLeft
來標(biāo)識動態(tài)加入新的數(shù)據(jù)。參考鏈接
其實(shí)實(shí)現(xiàn)起來也不難,我們只需要多傳入一個load
函數(shù),在點(diǎn)擊時調(diào)用該函數(shù),并且new Promise
來回調(diào)執(zhí)行即可。
const init = (data: dataType[], level: number) => {if (!data.length || !data) return [];let res = [];for (let i = 0; i < data.length; i++) {const child = data[i];const children = init(child[props.children] || [], level + 1);const label = child[props.label];const isOpen = false;
+ const isLazy = props.isLazy;
+ const isLeft = child["isLeft"] || false;res.push({label,children,isOpen,isLazy,
+ isLeft,
+ level,});}return res;
};
點(diǎn)擊后加載數(shù)據(jù)
const handleToggle = async (item: any) => {item.isOpen = !item.isOpen;if (item.isLazy) {if (item.isLeft && props.load) {await new Promise((resolve) => {props.load(item, resolve);}).then((res: any) => {for (let i = 0; i < res.length; i++) {res[i].isLazy = item.isLazy;res[i].level = item.level + 1;}item.children = res.slice();}).catch((err) => {console.log("[Tree Component] load Funtion Error", err);});}item.isLazy = false;}
};
演示demo
完整項(xiàng)目demo
結(jié)語
Tree
組件的核心開發(fā)功能就是上面這些,其他更多的詳細(xì)功能開發(fā)可以參考Hview-ui
項(xiàng)目源碼
如果想要了解更多的組件輪子開發(fā),或者組件庫開發(fā)流程,更多詳細(xì)的組件開發(fā)過程更新在GitHub
項(xiàng)目源碼,最后覺得我們項(xiàng)目or文章不錯可以點(diǎn)個star,點(diǎn)點(diǎn)小手支持一下,也歡迎各路大佬為我們的開源項(xiàng)目添磚加瓦。