不只懂 Vue 语法:试解释递回元件的用法?

问题回答

递回元件是指同一个元件里不断引用自己,造成重复一层元件包着一层元件的情况,直至该元件所渲染的资料没有满足你设定的 v-if 资料,就代表此递回结束,不会再往下层产生同一个元件。此技巧常用於制作树状结构的 menu。

虽然能应用此技巧的情况不多,但也曾经出现在常见面试题中,因此也值得花时间学习一下。以下会再作详细解说。

什麽是树状 menu?

以上是多层树状 menu 的示范。

本篇文章会以歌单为例子。

  • 第一层:音乐类别(K-Pop、US-Pop)
  • 第二层:歌手
  • 第三层:专辑
  • 最後一层:专辑的其中三首歌

递回元件的限制

  • 必须设定 name 属性
  • 必须设定结束递回的条件

示范例子:歌单树状 menu

资料结构如下:

menu: [
    { 
        title: "K-POP",
        children: [
            { 
            title: 'BTS',
            children: [
                { 
                  title: "BE",
                  children: [
                    { 
                      title: "Life Goes On",
                    },
                    {
                      title: "Dynamite",
                    },
                    {
                      title: "Blue & Gray",
                    },
                  ],
                },
             ],
            }
        ]
    },
    {
        title: "US-POP",
        children: [
            {
                title: 'Billie Eilish',
                children: [ {...} ]
            }
        ]
    },
]

需要留意的重点:

  • 最外层没有 titlechildren 属性。
  • 由最外层的下一层开始,直至最内层之前,都有 titlechildren 属性。
  • 最内层没有 children 属性。

因此,可以总结出:

  • 最外层的下一层才真正开始递回。
  • 递回条件是:看这个物件有没有 children 属性。
    • 最外层没有 children --> 没开始递回。
    • 最内层没有 children --> 停止递回。

就着以上特点,先看看如何规划整段程序码:

黑色就是 <Menu> 元件,即是我们要递回的元件。以下会逐步解说做法。

第一步:印出最外层的项目

首先,建立 App.vue。在 App.vue,使用 v-for 把最外层的 title,即是音乐类别印出来。

App.vue

  <div class="menu-bg">
    <Menu v-for="item in menu" :key="item.title" :item="item" />
  </div>

所有资料

data() {
    return {
        menu: [
            {
                title: "K-POP",
                children: [{...}]
            },
            {
                title: "US-POP",
                children: [{...}]
            },
        ]
    }
}

加上一个灰色背景:

.menu-bg {
  background: #ededed;
}

Menu.vue:

<template>
  <p>
    {{ item.title }}
  </p>
</template>
export default {
  props: {
    item: {
      type: Object
    }
  }
};

第一步是把最外层印出来,并在 Menu 元件使用 item 来接收 menu 里的 2 个物件。像是这样:

<div>
    <!-- Menu 元件 -->
        <p> K-POP </p>
    <!-- Menu 元件 -->
        <p> US-POP </p>
</div>

结果:

第二步:把内层渲染出来

目前还不是树状 menu,只有把最外层印出来。举例说,我们要把 title: K-POP 的 children 里的资料渲染出来。做法很简单:

  1. 在 Menu.vue 里的 props,即是 item.children 取出 children 资料。
  2. 这时候你会发现,children 里的每一笔资料,是由同样的结构组成,即是同样有 titlechildren
  3. 因此,你可以在 Menu.vue 里,再次引用 <Menu /> 元件,即是在再次引用自己。

Menu.vue

<template>
    <div>
      <p>
        {{ item.title }}
      </p>
      <Menu v-for="item in item.children" :key="item.title" :item="item" />
    </div>
</template>

注意,在元件里引用自己时,需要加入 name 属性:

export default {
  // 加入 name,template 才认得
  name: 'Menu',
  props: {
    item: {
      type: Object
    }
  }
};

结果是把所有资料都渲染出来:

打开 Vue 检查工具就一目了然:

但是,现在看来不像树状 menu,因为:

  • 最後一层并没有 children 属性,会造成渲染错误。
  • 子 menu 没有 margin-left,画面上很难分辨哪个是子 menu。
  • 没有 toggle 的开关收合显示内容。

第三步:加入 v-if 终止递回,以及加入所需的 CSS

先加入 CSS,让整体看来像一个树状 menu:

<template>
  <div>
    <p>
      {{ item.title }}
    </p>
    <Menu
      v-for="item in item.children"
      :key="item.title"
      :item="item"
      class="sub-menu"
    />
  </div>
</template>
<style scoped>
    .sub-menu {
      margin-left: 30px;
    }
</style>

之後,加入 toggle 开关切换显示子 menu 的内容:

<template>
  <div>
    <div class="title-bar">
      <p>
        {{ item.title }}
      </p>
      <a class="toggle-icon" href="#" @click.prevent="isOpen = !isOpen">
        {{ isOpen ? "-" : "+" }}
      </a>
    </div>
      <Menu
        v-show="isOpen"
        v-for="item in item.children"
        :key="item.title"
        :item="item"
        class="sub-menu"
      />
  </div>
</template>
data() {
    return {
      isOpen: false,
    };
}
.title-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 50px;
}

.toggle-icon {
  color: grey;
  text-decoration: none;
}

结果:

最後,加入 v-if 来设定终止递回的条件:没有 children 属性。

<template>
  <div>
    <div class="title-bar">
      <p>
        {{ item.title }}
      </p>
      <a class="toggle-icon" href="#" @click.prevent="isOpen = !isOpen">
        {{ isOpen ? "-" : "+" }}
      </a>
    </div>
      <template v-if="item.children">
        <Menu
          v-show="isOpen"
          v-for="item in item.children"
          :key="item.title"
          :item="item"
          class="sub-menu"
        />
      </template>
  </div>
</template>

最终结果

完整程序码

https://codesandbox.io/s/shu-zhuang-menu-db6wu?file=/src/components/Menu.vue

总结

  • 递回元件是指在该元件里不断引用自己,常用於制作树状 menu。
  • 限制是要在该元件设定 name 属性,以及设定终止递回的条件。

参考资料

VueJS - Recursive Components
重新认识 Vue.js - 2-2 元件之间的沟通传递


<<:  【DAY 18】数据分析没有这麽难,透过 Microsoft Power BI ,让你事半功倍!

>>:  SSL凭证检测工具-SSL Lab

【Day27】Git 版本控制 - Stash 暂存

身为一名工程师真的很讨厌被打断工作,当你沈浸在 coding 的世界时,如果有人来吵闹真的会很想揍他...

[2021铁人赛 Day-03] ARM and 嵌入式开发板

前言 昨天朋友知道我正在撰写文章,果然是朋友啊XD 毫不留情地被呛爆,写一大堆屁话,却没有介绍嵌入...

Gulp 合并来自 npm 的 Javascript的资源 DAY96

在载入 bootstrap 的 js之前 我们可以看到 https://getbootstrap.c...

如何清除 iMac/Macbook 上所有资料?--〖2022亲测有用〗

如何完成Mac的所有信息? 分享阅读本文,恢复最完整的Mac和明确的教学,我们将使用专业的Mac清洁...

D29: 工程师太师了: 第15话

工程师太师了: 第15话 杂记: 近期工作了一些障碍, 导致效率低下, 渐渐发现自己对於一件事情必须...