# 一、动态组件

  动态组件是指 动态的切换组件的显示和隐藏 。先来一个案例体验一些吧:

多标签界面组件切换案例 (opens new window)

  这个部分的需求应用非常广泛,几乎所有的网站应用都会使用到。

# 效果思考

<template>
  <div>
    <div class="tabs">
      <button
        v-for="item in tabs"
        :key="item"
        :class="{ active: currentTab === item }"
        @click="tabClick(item)"
      >
        {{ item }}
      </button>
    </div>

    <div class="main">
      <template v-if="currentTab == 'home'">
        <home-view></home-view>
      </template>
      <template v-else-if="currentTab == 'category'">
        <category></category>
      </template>
      <template v-else>
        <home-view></home-view>
      </template>
    </div>
  </div>
</template>

<script>
import Home from './view/HomeView'
import Category from './view/Category'
import About from './view/AboutWorld'

export default {
  name: 'App',
  components: {
    Home,
    Category,
    About
  },
  data() {
    return {
      tabs: ['home','category','about']
      currentTab: 'Home'
    }
  },
  methods: {
    tabClick(item) {
      this.currentTab = item
    }
  }
}
</script>

# 1、使用示例

  那如果实现动态组件呢?我们可以使用 vue 提供的<component> 标签,它和 slot 标签一样,是作为占位符存在。不同的是,<component> 标签是为要所以的子组件占位。

  在 component 标签中使用 is 属性可以决定最终使用/显示的是那个子组件。

<template>
  <div>
    <div class="main">
      <!-- 动态组件 🤔 -->
      <component :is="currentTab"></component>
    </div>
  </div>
</template>

  当然 component 标签可以和子组件标签一样,利用 props 属性自定义事件进行数据交互。

<component
  active-color="#4fc08d"
  :dot="isShow"
  @click-tab="tabClickFn"
  :is="currentTab"
></component>

# 2、组件状态 ✨

  上面的小案例貌似没有什么问题,但在其他应用场景却可能存在 bug:

问题

若像京东 App 这样的,在下面在加一个购物车(Cart.vue),来回切换时,发现购物车中原先选好的商品不见了 —— 回到了购物车的原始界面。

  默认情况下组件之间切换的时候是无法保持组件的状态,并且每次切换新标签的时候,Vue 都创建了一个新的 currentTabComponent 实例,这样容易反复重新渲染导致的性能问题。

验证
<script>
  export default {
    // 离开Cart组件时,组件被销毁
    destroyed() {
      console.log('组件被销毁')
    },

    // 回到Cart组件时,重新创建组件
    created() {
      console.log('组件被创建')
    }
  }
</script>

  此时我们可以使用 vue 内置的 <keep-alive> 组件可以保持动态组件的状态。

注意

<keep-alive>要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项,还是局部/全局注册。

<template>
  <div>
    <div class="main">
      <!-- 保持组件状态 -->
      <keep-alive>
        <component :is="currentTab"></component>
      </keep-alive>
    </div>
  </div>
</template>
原理解析

  通过 vue 调试工具可以发现,切换页面时,对<Cart>标签进行了 inactive 标记。

<script>
  export default {
    // 离开Cart组件时,它会被缓存(同时自动触发组件的 deactivated生命周期函数)
    deactivated() {
      console.log('Cart组件被缓存')
    },

    // 回到Cart组件时,它会被激活(同时自动触发组件的 activated生命周期函数)
    activated() {
      console.log('Cart组件被激活')
    }
  }
</script>

# 3、include 属性 🤔

  在上面的案例中我们发现,若使用了 keep-alive,那么所以的组件都会被缓存,而对于 Ower 组件,显然是没有必要的。

  而 keep-alive (opens new window) 中的 include 属性 可以指定哪些组件会被缓存(多个组件名之间用英文逗号隔开)。对应的,也有一个指定哪些组件不用被缓存的 exclude 属性,但二者不能同时使用。

<template>
  <div>
    <!-- 指定组件保持状态 -->
    <div class="main">
      <keep-alive exclude="home, about">
        <component :is="currentTab"></component>
      </keep-alive>
    </div>
  </div>
</template>

  如果在子组件中有可用的 name 选项,但其选项值和父组件中 include 属性值不同的话,include 属性是不会生效的。

原理及案例演示

  若使用了include / exinclude 属性,它会首先检查子组件的 name 选项,如果该 name 值不可用,则匹配父组件 components 选项的键值)。匿名组件不能被匹配。

  • 父组件
<template>
  <div>
    <div class="main">
      <!-- 这里的exclude使用值为:👀about -->
      <keep-alive include="category">
        <component :is="currentTab"></component>
      </keep-alive>
    </div>
  </div>
</template>
  • 子组件
<script>
  export default {
    // name值为: CategoryView, 和 exclude 属性值: category 不一致
    name: 'CategoryView'
  }
</script>

  所以,我们引入子组件时,✍ 注册的组件名字尽量和原子组件的 name 选项值相同

# 二、其他

# 1、vue 路由

  对于大多数单页面应用,都推荐使用官方支持的 vue-router 库 (opens new window)。更多细节可以移步 vue-router 文档 (opens new window)

<template>
  <div>
    <!-- 1、以前 -->
    <div class="box">
      <keep-alive>
        <component :is="comp_Name"></component>
      </keep-alive>
    </div>

    <!-- 2、现在 -->
    <div class="box">
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
    </div>
  </div>
</template>

# 2、异步组件

<script>
// import Home from './view/HomeView'

export default {
  name: 'App',
  components: {
    // Home
    home: () => import('./view/HomeView')
  }
}
</script>
更新于 : 8/7/2024, 2:16:31 PM