[Vue深入組件]:遞歸組件和組件的循環引用

1. 組件的遞歸

如果期望你有以下實現:

image-20210925183434867

這是一個嵌套了很多層的組件,你會有什麼樣的思路?

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>

看起來,就像這樣:

image-20210925191133742

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>

最終你將得到如下實現:

image-20210925202948428

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>

它的實現效果如下:

image-20210925235528983

你特別需要值得注意的有這樣幾點:

  1. 這兩個組件時互相調用的

  2. 這兩個組件在引入註冊的時候,須至少有一個是懶加載的。 還有別的解決方案,見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的時候,有時候一個細節錯了,調半天。 - - 西吧~~~ 媽個雞~~!!

    所以,要想能活用,首先得熟練,要多看看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章