1. 組件的遞歸
如果期望你有以下實現:
這是一個嵌套了很多層的組件,你會有什么樣的思路?
1.1 遞歸組件
Vue 允許我們遞歸的調用一個組件,即在一個組件內部不斷的嵌套調用自身。
為了實現組件的遞歸,Vue 要求我們必須指定組件的 name
屬性。
此外,必須給渲染加上條件判斷,在適當的時刻停止調用。
我們以實現如上的demo為目的,認識一下Vue中遞歸組件是如何使用的。
//上層組件(調用根父組件)
<template>
<div style="width:100%;height:100%">
<BoxRecursion />
</div>
</template>
<script>
import BoxRecursion from "./comps/BoxRecursion.vue";
export default {
components: {
BoxRecursion,
},
created() {
localStorage.setItem("key", 0);
},
};
</script>
這是我們在一個組件中對即將嵌套的組件的首次調用。
為了有一個合適的判斷條件,我們通過localStorage 設定了一個全局可訪問的變量值key
, 並給定初始值為0
, 接着,預期每當一次渲染就讓這個key
值加一,並在達到一定值后停止渲染。 (你可以用任何其他的方式達成相同的目的,如store,vuex,eventBus,$root...)
一下是嵌套組件本身的實現:
//嵌套組件本身
<template>
<div class="box">
<BoxRecursion v-if="count <= 50" />
</div>
</template>
<script>
export default {
name: "BoxRecursion",
data() {
return {
count: 0,
};
},
created() {
let a, b;
a = localStorage.getItem("key");//獲取key值
b = a * 1;//轉換為Number 類型
b++;//加一操作
localStorage.setItem("key", b);//重新set key值
this.count = b;
},
};
</script>
<style>
.box {
width: 100%;
height: 100%;
padding: 5px;
border: 1px solid red;
}
</style>
可以看到,我們定義了一個簡單的BoxRecursion
組件,其內容就是一個空的class=box的div元素,此外什么都沒有。為了比較明顯的看出嵌套效果,我們寫了簡單的基本樣式。
值得注意的是,我們給定了name
屬性,即當前組件的名稱。
緊接着,我們直接在BoxRecursion
組件內部,dom中寫了<BoxRecursion/>
最重要的,我們通過v-if
對渲染進行了條件控制,這樣,就能在達到條件是停止渲染。 以免發生棧溢出。
這上面的過程中,還有一個最重要的點,就是怎么判斷v-if
條件,以控制渲染。
以上示例中,我們通過一個全局的變量的自增來達到目的的。 在實際工作中,應當靈活處理。
1.2 遞歸組件 - 拓展示例1 : 遞歸渲染扁平數組
例如,你可能會遞歸的去渲染一個一維數組?
//index
<template>
<div style="width:100%;height:100%">
<BoxRecursion :arr="arr" />
</div>
</template>
<script>
import BoxRecursion from "./comps/BoxRecursion.vue";
export default {
components: {
BoxRecursion,
},
data() {
return {
arr: [1, 2, 3, 4, 5],
};
},
};
</script>
//BoxRecursion
<template>
<div class="box">
<BoxRecursion :arr="propArr" v-if="propArr.length != 0" />
{{ arr }}
</div>
</template>
<script>
export default {
name: "BoxRecursion",
props: ["arr"],
computed: {
propArr: function() {
return this.arr.slice(1, this.arr.length);
},
},
};
</script>
<style>
.box {
width: 100%;
height: 100%;
padding: 5px;
border: 1px solid red;
}
</style>
看起來,就像這樣:
1.3 遞歸組件 - 拓展示例2 : 遞歸渲染Tree型數組
最常見的樹形數據結構,就是文件目錄了,以linux上的tree命令就是一個很好的例子:
jayce@LAPTOP-0CA0HBLH:Vue$ tree
.
├── $parent
│ ├── comps
│ │ ├── ChildA.vue
│ │ ├── ChildB.vue
│ │ └── ComSubChild.vue
│ └── index.vue
├── $ref
│ ├── comps
│ │ ├── ChildA.vue
│ │ └── ChildB.vue
│ └── index.vue
.............
利用遞歸組件,我們也能夠簡單實現一個展示一個tree數據結構的組件:
//index.vue
<template>
<div style="width:100%;height:100%">
<BoxRecursion :dirlist="dirlist" />
</div>
</template>
<script>
import BoxRecursion from "./comps/BoxRecursion.vue";
export default {
components: {
BoxRecursion,
},
data() {
return {
dirlist: [
// 為了文章閱讀體驗,請在下方點擊展開查看數據代碼
],
};
},
};
</script>
點擊查看展開dirlist數據
dirlist: [
{
path: "0",
child: [
{
path: "0-1",
child: [
{
path: "0-1-1",
},
],
},
{
path: "0-2",
child: [
{
path: "0-2-1",
},
],
},
{
path: "0-3",
},
],
},
{
path: "1",
},
{
path: "2",
},
{
path: "3",
child: [
{
path: "3-1",
child: [
{
path: "3-1-1",
},
],
},
{
path: "3-2",
child: [
{
path: "3-2-1",
child: [
{
path: "3-2-1-1",
child: [
{
path: "3-2-1-1-1",
},
{
path: "3-2-1-1-2",
},
],
},
],
},
],
},
],
},
],
//遞歸組件 BoxRecursion.vue
<template>
<div class="box">
<details open v-for="(item, index) in dirlist" :key="index">
<summary :style="{ color: item.child ? '#1890ff' : 'grey' }">{{
item.path
}}</summary>
<template v-if="item.child && item.child.length">
<BoxRecursion :dirlist="item.child" />
</template>
</details>
</div>
</template>
<script>
export default {
name: "BoxRecursion",
props: ["dirlist"],
};
</script>
最終你將得到如下實現:
2. 組件的循環調用
以上示例中,即拓展示例2 ,實際上我們只通過了一個組件就完成了tree結構數據的遞歸調用組件。思路也很簡單,傳入一個初始化數組,然后取每一個對象元素中的path
屬性,先渲染。 然后在加一個條件判斷,當前對象是否存在子列表,如果存在,就嵌套調用。 在下一輪循環中,同樣的規則,直到沒有嵌套的列表才停止嵌套調用。
實際工作中,可能會存在更加復雜的需求。可能出於維護,或者其他一些原因。 需要你把根父節點內容的渲染寫在一個組件中,把子節點的內容寫在另一個組件中,然后循環的互相調用,以完成組件的嵌套調用。
所以,我們嘗試將剛才同樣的數據內容,抽離成兩個相互調用的組件,加以實現。
//根父節點組件index.vue
<template>
<div>
<p v-for="(folder, index) in list" :key="index">
<span>{{ folder.path }}</span>
<tree-folder-contents v-if="folder.child" :children="folder.child" />
</p>
</div>
</template>
<script>
export default {
name: "tree-folder",
props: ["folderList"],
components: {
TreeFolderContents: () => import("./comps/TreeFolderContents.vue"),
},
created() {
if (this.folderList) {
//第一層時:folderList為undefined
this.list = this.folderList;
}
},
data() {
return {
//....拓展示例2相同的數據....
};
},
};
</script>
//子節點組件TreeFolderContents
<template>
<ul>
<li v-for="(item, index) in children" :key="index">
<tree-folder v-if="item.child" :folderList="[item]" />
<span v-else>{{ item.path }}</span>
</li>
</ul>
</template>
<script>
import TreeFolder from "../index.vue";
export default {
props: ["children"],
name: "tree-folder-contents",
components: {
TreeFolder,
// TreeFolder: () => import("../index.vue"),
},
};
</script>
它的實現效果如下:
你特別需要值得注意的有這樣幾點:
-
這兩個組件時互相調用的
-
這兩個組件在引入注冊的時候,須至少有一個是懶加載的。 還有別的解決方案,見Vue 相關文檔。
相關說明如下(但是這里我還沒有特別清楚究竟為什么能這樣被解決)
”我們先把兩個組件稱為 A 和 B。模塊系統發現它需要 A,但是首先 A 依賴 B,但是 B 又依賴 A,但是 A 又依賴 B,如此往復。這變成了一個循環,不知道如何不經過其中一個組件而完全解析出另一個組件。為了解決這個問題,我們需要給模塊系統一個點,在那里“A 反正是需要 B 的,但是我們不需要先解析 B。”
”在我們的例子中,把
<tree-folder>
組件設為了那個點。我們知道那個產生悖論的子組件是<tree-folder-contents>
組件,所以我們會等到生命周期鈎子 beforeCreate 時去注冊它:“beforeCreate: function () { this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default }
或者,在本地注冊組件的時候,你可以使用 webpack 的異步
import
:components: {TreeFolderContents: () => import('./tree-folder-contents.vue')}
這樣問題就解決了! 摘自link
另: 兩個組件循環調用,有些難度,我在調試這個demo的時候,有時候一個細節錯了,調半天。 - - 西吧~~~ 媽個雞~~!!
所以,要想能活用,首先得熟練,要多看看。