Vue 3 的组件式开发(2)

1 Vue 3 组件的插槽

插槽(Slot)是Vue组件中的一个重要概念,它允许父组件向子组件中插入HTML结构或其他组件,从而实现内容的自定义和复用。以下是对Vue 3组件插槽的详细讲解:

1.1 插槽的基本概念

插槽可以被视为子组件中预留给父组件填充内容的占位符。在子组件中,你可以使用<slot>标签来定义插槽,而在父组件中,你可以通过在该标签内部放置内容来填充这个插槽。

1.2 默认插槽(匿名插槽)

默认插槽是最简单的插槽类型,它没有名字,因此也被称为匿名插槽。在子组件中,你只需要使用一个<slot>标签来定义它。在父组件中,你可以直接在该标签内部放置需要插入的内容。

vue 复制代码
<!-- 子组件 -->
<template>
  <div>
    <p>我是子组件</p>
    <slot></slot> <!-- 默认插槽 -->
  </div>
</template>

<!-- 父组件 -->
<template>
  <div>
    <ChildComponent>
      <p>这是父组件插入到子组件的内容</p>
    </ChildComponent>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

1.3 具名插槽

当子组件中有多个插槽时,你可以使用具名插槽来区分它们。在子组件中,你需要给<slot>标签添加一个name属性来指定插槽的名字。在父组件中,你可以使用v-slot:插槽名(简写为#插槽名)来指定要填充哪个插槽。

vue 复制代码
<!-- 子组件 -->
<template>
  <div>
    <p>我是子组件</p>
    <slot name="header"></slot> <!-- 具名插槽 -->
    <slot name="content"></slot> <!-- 具名插槽 -->
    <slot name="footer"></slot> <!-- 具名插槽 -->
  </div>
</template>

<!-- 父组件 -->
<template>
  <div>
    <ChildComponent>
      <template #header>
        <h1>这是头部内容</h1>
      </template>
      <template #content>
        <p>这是主体内容</p>
      </template>
      <template #footer>
        <p>这是底部内容</p>
      </template>
    </ChildComponent>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

1.4 动态插槽名

Vue 2.6.0+版本支持动态插槽名,这意味着插槽名可以是一个变量,你可以根据变量的值来动态地选择填充哪个插槽。在父组件中,你可以使用v-slot:[动态插槽名](简写为#[动态插槽名])来实现这一点。

vue 复制代码
<!-- 父组件 -->
<template>
  <div>
    <ChildComponent>
      <template #[currentSlotName]>
        <p>这是动态插槽的内容</p>
      </template>
    </ChildComponent>
    <button @click="changeSlotName">切换插槽</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const currentSlotName = ref('header');

function changeSlotName() {
  currentSlotName.value = currentSlotName.value === 'header' ? 'content' : 'header';
}
</script>

1.5 作用域插槽

作用域插槽允许子组件向父组件传递数据,以便父组件在填充插槽时使用这些数据。在子组件中,你可以使用v-bind(简写为:)在<slot>标签上绑定数据。在父组件中,你可以使用v-slot指令接收这些数据,并根据需要进行处理。

vue 复制代码
<!-- 子组件 -->
<template>
  <div>
    <slot :user="userData"></slot>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const userData = ref({
  name: '张三',
  age: 30
});
</script>

<!-- 父组件 -->
<template>
  <div>
    <ChildComponent v-slot:default="{ user }">
      <p>用户名:{{ user.name }}</p>
      <p>年龄:{{ user.age }}</p>
    </ChildComponent>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

在这个例子中,子组件通过作用域插槽向父组件传递了userData对象。父组件在填充插槽时接收了这个对象,并使用了它的nameage属性。

1.6 插槽的默认内容

当父组件没有提供任何插槽内容时,你可以为子组件的插槽指定默认内容。这可以通过在<slot>标签内部放置默认内容来实现。

vue 复制代码
<!-- 子组件 -->
<template>
  <div>
    <p>我是子组件</p>
    <slot>这是默认内容</slot> <!-- 带有默认内容的插槽 -->
  </div>
</template>

<!-- 父组件 -->
<template>
  <div>
    <ChildComponent></ChildComponent> <!-- 没有提供插槽内容 -->
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

在这个例子中,如果父组件没有提供任何插槽内容,子组件将显示默认内容"这是默认内容"。

2 Vue 3 单文件组件

Vue 3单文件组件(Single-File Components,简称SFC)是一种特殊的文件格式,它允许开发者将Vue组件的模板(Template)、脚本(Script)和样式(Style)封装在同一个文件中,从而提高代码的可维护性和可读性。以下是对Vue 3单文件组件的详细讲解:

2.1 单文件组件的结构

每一个.vue文件都由三种顶层语言块构成:<template><script><style>,以及一些其他的自定义块。

  1. <template>:定义了组件的HTML结构。在Vue 3中,模板语法得到了进一步的优化,支持更多的指令和功能,如条件渲染、列表渲染、事件处理等。
  2. <script>:包含了组件的JavaScript逻辑。在这里,可以定义组件的数据、方法、计算属性、生命周期钩子等。Vue 3引入了组合式API(Composition API),使得逻辑复用和组件组织变得更加灵活。
  3. <style> :用于定义组件的样式。Vue 3支持scoped属性,使得样式只作用于当前组件,避免了全局样式的污染。此外,还可以使用CSS预处理器(如Sass、Less等)来编写更复杂的样式。

2.2 单文件组件的优点

  1. 模块化:将组件的模板、脚本和样式封装在一起,使得组件更加模块化,易于维护和复用。
  2. 语法熟悉:使用熟悉的HTML、CSS和JavaScript语法来编写组件,降低了学习成本。
  3. 预编译模板:Vue 3在构建阶段会对模板进行预编译,避免了运行时的编译开销,提高了性能。
  4. 组件作用域的CSS :通过scoped属性,可以确保样式只作用于当前组件,避免了全局样式的冲突。
  5. 更好的IDE支持:许多现代IDE都提供了对Vue单文件组件的良好支持,包括语法高亮、自动补全、类型检查等功能。
  6. 热更新(HMR):在开发阶段,可以支持模块热替换(Hot Module Replacement),使得开发者可以在不刷新页面的情况下更新组件。

2.3 单文件组件的使用

  1. 创建组件:使用Vue CLI或Vite等构建工具可以轻松地创建Vue 3单文件组件。这些工具提供了合理的默认配置,并可以通过插件进行扩展。
  2. 导入组件 :在父组件中,可以使用import语句来导入子组件,并在components选项中注册它。然后,就可以在父组件的模板中使用这个子组件了。
  3. 自定义块 :除了<template><script><style>之外,.vue文件还可以包含其他自定义块。这些自定义块可以用于文档、国际化、类型定义等目的。自定义块的处理需要依赖工具链,如Vite插件或Webpack loader等。

2.4 单文件组件的编译

在构建过程中,Vue 3单文件组件会被编译为标准的JavaScript和CSS。具体来说:

  1. 模板编译<template>中的内容会被提取出来,传递给@vue/compiler-dom进行预编译,生成JavaScript渲染函数,并附在导出的组件上作为其render选项。
  2. 脚本编译<script>中的内容会被当作ES模块来执行。如果使用了<script setup>语法糖,它会被预处理为组件的setup()函数。
  3. 样式编译<style>中的内容会被抽取、合并成单独的CSS文件(在生产环境下),并注入到页面中。如果使用了scoped属性,Vue会在编译时为每个组件生成唯一的属性选择器,以确保样式只作用于当前组件。

2.5 示例

以下是一个简单的Vue 3单文件组件示例:

vue 复制代码
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button @click="increment">点击我</button>
    <p>当前计数:{{ count }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const msg = 'Hello, Vue 3!';
const count = ref(0);

function increment() {
  count.value++;
}
</script>

<style scoped>
.hello {
  text-align: center;
}

button {
  margin-top: 20px;
  padding: 10px 20px;
  font-size: 16px;
}
</style>

在这个示例中,我们定义了一个简单的Vue 3单文件组件,它包含一个消息、一个按钮和一个计数器。通过点击按钮,可以增加计数器的值。样式部分使用了scoped属性,确保样式只作用于当前组件。

3 Vue 3 组件的生命周期

在Vue.js框架中,组件生命周期是指组件从创建到销毁的整个过程。Vue 3引入了新的组件生命周期钩子函数,为开发者提供了更精细的控制能力。下面是对Vue 3组件生命周期中关键钩子函数的详细讲解:

3.1 beforeCreate / created

  • beforeCreate

    • 触发时机:在实例初始化之后、数据观测之前被调用。
    • 组件状态:此时组件的data和methods还未初始化,Vue实例的数据和事件都没有初始化。
    • 用途:由于此时组件尚未完全创建,因此一般不在此阶段进行组件的逻辑处理。但在一些特殊情况下,如需要全局变量或事件监听器的初始化设置,可以在此阶段进行。
  • created

    • 触发时机:在实例创建完成后被立即调用。
    • 组件状态:此时组件的data和methods已经初始化,但模板渲染还未开始,尚未挂载DOM。Vue实例已经完成了数据观测(data observer)和事件初始化。
    • 用途:此阶段可以进行一些异步请求、事件监听器的注册等操作。因为此时组件的数据和方法已经可用,但DOM还未生成,所以适合进行一些与数据相关的初始化工作。

3.2 beforeMount / mounted

  • beforeMount

    • 触发时机:在挂载之前被调用,此时模板编译已经完成,但DOM尚未生成。
    • 组件状态:组件已经完成了其响应式状态的设置,但还没有创建DOM节点。Vue实例已经生成了一个虚拟DOM,并且将要被渲染到实际的DOM元素上。
    • 用途:此阶段可以进行一些DOM相关的操作,例如获取DOM节点、样式等。因为此时模板已经编译完成,但还未挂载到DOM上,所以适合进行一些与模板编译相关的准备工作。
  • mounted

    • 触发时机:在挂载之后被调用,此时组件已经被挂载到页面上,可以访问到组件的DOM元素。
    • 组件状态:所有同步子组件都已经被挂载,其自身的DOM树已经创建完成并插入了父容器中。
    • 用途:此阶段通常用于执行需要访问组件所渲染的DOM树相关的操作,例如对DOM元素进行样式修改、事件绑定等。因为此时组件已经挂载到DOM上,所以可以安全地操作DOM元素。

3.3 beforeUpdate / updated

  • beforeUpdate

    • 触发时机:在组件更新之前被调用,此时数据已经被更改,但页面还未重新渲染。
    • 组件状态:Vue实例的数据已经发生了改变,但是DOM还没有被重新渲染。
    • 用途:此阶段可以用于在Vue更新DOM之前访问DOM状态,或者进行一些在更新前需要完成的逻辑处理。例如,可以在此阶段手动更改DOM元素的状态以匹配新的数据。
  • updated

    • 触发时机:在组件更新后被调用,此时组件的DOM已经重新渲染完毕。
    • 组件状态:Vue实例的数据已经发生了改变,并且DOM已经被重新渲染。
    • 用途:此阶段通常用于执行更新后的副作用操作,例如更新后的DOM元素样式调整、动画效果等。但需要注意避免在此阶段更改组件的状态,这可能会导致无限更新循环。

3.4 beforeUnmount / unmounted

  • beforeUnmount

    • 触发时机:在组件卸载之前被调用,此时组件仍然可用,但DOM已经被删除。
    • 组件状态:组件即将被销毁,但尚未完全销毁。此时组件实例依然还保有全部的功能。
    • 用途:此阶段可以用于执行一些清理操作,例如取消事件监听器、清除定时器等。这些操作有助于避免内存泄漏和不必要的资源消耗。
  • unmounted

    • 触发时机:在组件卸载之后被调用,此时组件已经完全销毁,无法再次使用。
    • 组件状态:组件已经被销毁,所有的事件监听器和子组件都将被移除,所有的子组件的destroyed钩子也会被调用。此时组件的所有指令都已解绑,所有的响应式作用(渲染作用以及setup()时创建的计算属性和侦听器)都已经停止。
    • 用途:此阶段通常用于执行一些最终的清理工作,例如手动清理一些副作用(计时器、DOM事件监听器或者与服务器的连接等)。但需要注意此时组件已经完全销毁,无法再进行任何操作。

3.4 综合示例

vue 复制代码
<!-- MyComponent.vue -->
<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="updateMessage">Update Message</button>
  </div>
</template>

<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';

export default {
  setup() {
    const message = ref('Hello, Vue 3!');

    // 在组件挂载之前调用
    onBeforeMount(() => {
      console.log('Before mount');
      // 此时,组件的模板已经渲染为HTML,但还没有添加到页面中
    });

    // 在组件挂载完成后调用
    onMounted(() => {
      console.log('Mounted');
      // 此时,可以执行依赖于DOM的操作,例如获取元素的尺寸或启动动画
      // 也可以进行DOM操作、数据请求等
    });

    // 在组件更新之前调用
    onBeforeUpdate(() => {
      console.log('Before update');
      // 此时,内存中的数据已经被更新,但视图中的数据还没有更新
    });

    // 在组件更新完成后调用
    onUpdated(() => {
      console.log('Updated');
      // 此时,可以执行依赖于更新后DOM的操作
    });

    // 在组件卸载之前调用
    onBeforeUnmount(() => {
      console.log('Before unmount');
      // 此时,组件即将被销毁,可以进行清理工作,如移除事件监听器、取消定时器等
    });

    // 在组件卸载完成后调用
    onUnmounted(() => {
      console.log('Unmounted');
      // 此时,组件已经被销毁,可以进行一些后续操作
    });

    // 更新message的方法
    const updateMessage = () => {
      message.value += '!';
    };

    return {
      message,
      updateMessage,
    };
  },
};
</script>

<style scoped>
h1 {
  color: blue;
}
</style>
  1. onBeforeMount:在组件挂载之前调用。此时,组件的模板已经渲染为HTML,但还没有添加到页面中。
  2. onMounted:在组件挂载完成后调用。此时,可以执行依赖于DOM的操作,例如获取元素的尺寸或启动动画。也可以进行DOM操作、数据请求等。
  3. onBeforeUpdate:在组件更新之前调用。此时,内存中的数据已经被更新,但视图中的数据还没有更新。
  4. onUpdated:在组件更新完成后调用。此时,可以执行依赖于更新后DOM的操作。
  5. onBeforeUnmount:在组件卸载之前调用。此时,组件即将被销毁,可以进行清理工作,如移除事件监听器、取消定时器等。
  6. onUnmounted:在组件卸载完成后调用。此时,组件已经被销毁,可以进行一些后续操作。

使用场景:

  • 挂载前后:进行DOM操作、数据请求等。
  • 更新前后:响应数据变化,执行依赖于DOM的操作。
  • 卸载前后:清理资源,如移除事件监听器、取消定时器等。

4 组件的复用与组合

4.1 Mixin

在Vue 3中,混入(Mixins)是一种将一组组件选项合并到目标组件中的技术。通过混入,开发者可以在多个组件之间共享重复的逻辑、方法和数据,从而提高代码的可复用性和可维护性。以下是对Vue 3组件使用Mixins的详细讲解:

4.1.1 Mixins的定义

Mixins是一个普通的JavaScript对象,它可以包含任何组件选项,如data、methods、computed、watch、生命周期钩子等。当组件使用Mixins时,所有Mixins的选项都会被"混入"到该组件自身的选项中。

4.1.2 Mixins的使用

  1. 定义Mixins

首先,需要定义一个Mixins对象。例如,创建一个名为myMixin的Mixins对象,它包含一个响应式数据mixinData、一个生命周期钩子created和一个方法mixinMethod

javascript 复制代码
// mixins.js
export default {
  data() {
    return {
      mixinData: 'This is data from mixin'
    };
  },
  created() {
    console.log('Mixin created hook called');
  },
  methods: {
    mixinMethod() {
      console.log('This is a method from mixin');
    }
  }
};
  1. 在组件中使用Mixins

接下来,在Vue 3组件中通过mixins选项引入并使用这个Mixins对象。例如,创建一个名为MyComponent的组件,并在其中引入myMixin

vue 复制代码
<template>
  <div>
    <p>{{ mixinData }}</p>
    <button @click="mixinMethod">Call Mixin Method</button>
  </div>
</template>

<script>
import myMixin from './mixins.js';

export default {
  mixins: [myMixin],
  data() {
    return {
      componentData: 'This is data from the component'
    };
  },
  mounted() {
    console.log('Component mounted hook called');
  }
};
</script>

在这个例子中,MyComponent组件继承了myMixinmixinData数据、created生命周期钩子和mixinMethod方法。当MyComponent组件被创建时,会调用myMixincreated钩子函数,并在控制台中输出"Mixin created hook called"。同时,可以在组件模板中访问mixinData数据,并调用mixinMethod方法。

4.1.3 Mixins的合并策略

当组件和Mixins具有相同的选项时,Vue 3会按照以下策略进行合并:

  1. 数据(data) :如果组件和Mixins都有data函数,那么它们返回的对象会被合并到一个新的对象中。如果键名冲突,则组件的data会覆盖Mixins的data
  2. 方法(methods):如果组件和Mixins都有相同的方法名,那么组件的方法会覆盖Mixins的方法。
  3. 生命周期钩子:如果组件和Mixins都有相同的生命周期钩子,那么这些钩子函数会被依次调用。Mixins的钩子会在组件的钩子之前调用。
  4. 计算属性(computed)侦听器(watch):这些选项也会被合并,如果键名冲突,则组件的选项会覆盖Mixins的选项。

4.1.4 Mixins的注意事项

  1. 命名冲突:由于Mixins的选项会被合并到组件中,因此如果组件和Mixins具有相同的选项名,可能会导致命名冲突。为了避免这种情况,可以使用命名空间来区分Mixins中的选项。
  2. 可维护性:随着项目的增长,使用过多的Mixins可能会导致代码难以维护。因此,在大型项目中,建议优先考虑使用Vue 3的组合式API(Composition API)来实现代码复用和逻辑组织。
  3. 调试困难:由于Mixins的选项会被合并到组件中,因此在调试时可能会难以追踪某个选项的来源。为了解决这个问题,可以在Mixins中使用注释或文档来记录每个选项的用途和来源。

4.1.5 Mixins与Composition API的比较

虽然Mixins在Vue 3中仍然有效,但Vue 3官方更推荐使用组合式API(Composition API)来实现代码复用和逻辑组织。组合式API提供了一种更加灵活和模块化的方式来编写组件逻辑,可以更容易地实现逻辑关注点分离和代码复用。此外,组合式API还提供了更好的TypeScript支持和更好的性能优化机会。

综上所述,Vue 3中的Mixins是一种强大的代码复用机制,但需要注意命名冲突、可维护性和调试困难等问题。在大型项目中,建议优先考虑使用组合式API来实现代码复用和逻辑组织。

4.2 Composition API

4.2.1 Composition API 概述

Composition API 是 Vue 3 提供的一组新的 API,旨在简化组件的逻辑复用与组织,使得组件变得更易于理解和维护。与 Vue 2 中的 Options API(如 data、methods、computed 等选项)不同,Composition API 提供了一种更加灵活和可组合的方式来编写组件逻辑。

4.2.2 Composition API 的核心部分

  1. setup 函数

    • setup 函数是 Composition API 的入口函数,它在组件创建之前被调用,用于初始化响应式数据、计算属性、侦听器等。
    • setup 函数接收两个参数:propscontextprops 是父组件传递给子组件的属性,而 context 是一个包含 attrsslotsemitexpose 的对象。
    • setup 函数返回一个对象,该对象中的属性、方法可以在模板中直接使用。
  2. 响应式数据

    • reactive 函数用于创建响应式对象,该对象的属性值会自动更新并反映其依赖项的变化。
    • ref 函数用于创建响应式引用,可以通过 .value 属性来访问或修改它的值。ref 更适合包装简单类型的值(如数字、字符串、布尔值等)。
  3. 计算属性

    • computed 函数用于创建计算属性,它基于其依赖项进行缓存,只有在其依赖项发生变化时才会重新计算。这可以避免不必要的计算和性能开销。
  4. 侦听器

    • watchwatchEffect 函数用于侦听响应式数据的变化,并在数据变化时执行相应的回调函数。
    • watch 需要显式指定侦听的数据源和回调函数,支持立即执行、深度侦听等选项。
    • watchEffect 自动侦听回调函数中使用的响应式数据,无需显式指定数据源。当回调函数中使用的数据变化时,watchEffect 会自动重新执行回调函数。

4.2.3 Composition API 的使用示例

以下是一个综合示例,展示了如何在 Vue 3 组件中使用 Composition API,包括 setup 函数、响应式数据(reactiveref)、计算属性(computed)以及侦听器(watchwatchEffect)。

vue 复制代码
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="resetCount">Reset Count</button>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, watchEffect } from 'vue';

export default {
  name: 'CompositionApiExample',
  setup(props, context) {
    // 响应式数据
    const count = ref(0);
    const state = reactive({
      title: 'Composition API Example',
      isButtonClicked: false
    });

    // 计算属性
    const doubleCount = computed(() => count.value * 2);

    // 方法
    const increment = () => {
      count.value++;
    };

    const resetCount = () => {
      count.value = 0;
      state.isButtonClicked = false; // 也可以同时重置其他响应式数据
    };

    // 侦听器 - 使用 watch
    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
      if (newValue === 5) {
        state.isButtonClicked = true; // 当 count 达到 5 时,改变另一个响应式数据的状态
      }
    });

    // 侦听器 - 使用 watchEffect
    watchEffect(() => {
      console.log(`Double Count is ${doubleCount.value}`);
      // 注意:这里不需要显式指定 doubleCount 作为侦听源,因为 watchEffect 会自动侦听其依赖的响应式数据
    });

    // 暴露给模板的属性和方法
    return {
      count,
      state,
      doubleCount,
      increment,
      resetCount
    };
  }
};
</script>

<style scoped>
/* 样式可以根据需要自定义 */
button {
  margin-right: 10px;
}
</style>

在这个示例中:

  • setup 函数接收 propscontext 作为参数,但在这个例子中并没有使用 propscontext 的内容。
  • 使用 ref 创建了一个响应式引用 count,用于存储一个数字。
  • 使用 reactive 创建了一个响应式对象 state,包含 titleisButtonClicked 两个属性。
  • 使用 computed 创建了一个计算属性 doubleCount,它基于 count 的值计算得到。
  • 定义了 incrementresetCount 两个方法,用于修改 count 的值和重置组件的状态。
  • 使用 watch 侦听 count 的变化,并在控制台输出新旧值。当 count 达到 5 时,改变 state.isButtonClicked 的值。
  • 使用 watchEffect 自动侦听 doubleCount 的依赖(即 count),并在控制台输出 doubleCount 的值。注意,watchEffect 不需要显式指定侦听源。

最后,将 countstatedoubleCountincrementresetCount 暴露给模板,以便在模板中使用这些属性和方法。

4.2.4 Composition API 的优势

  1. 更好的代码组织:随着组件功能的增加,Options API 可能会导致代码难以维护和理解。而 Composition API 通过将逻辑拆分为多个可复用的函数,提高了代码的可读性和可维护性。
  2. 逻辑复用:在 Options API 中,复用逻辑通常需要通过 mixins 或高阶组件实现,但这些方式可能导致命名冲突和关系不清晰。而 Composition API 中的函数可以像普通 JavaScript 函数一样被复用,无需担心命名冲突。
  3. 更好的 TypeScript 支持:Vue 3 与 TypeScript 的集成更加紧密,而 Composition API 的函数式编程风格更适合 TypeScript 的类型推断和静态检查。

4.2.5 注意事项

  1. 避免全局状态管理:Composition API 鼓励将状态局部化,避免全局状态管理。这有助于减少组件之间的耦合度,提高代码的可维护性。
  2. 合理使用 Ref 和响应式对象 :根据需要选择使用 ref 还是响应式对象来存储数据。对于简单类型的值(如数字、字符串、布尔值等),可以使用 ref;对于对象或数组等复杂类型的数据,可以使用 reactive
  3. 性能优化:在使用 Composition API 时,要注意性能优化。例如,避免不必要的计算和重复操作,使用计算属性来缓存计算结果等。

5 动态组件与异步组件

5.1 动态组件

5.1.1 动态组件的基本概念

动态组件指的是在同一个页面中使用多个组件,并且可以根据某些条件(如用户点击、数据变化等)动态地切换这些组件的显示。这种机制使得开发者能够创建更加灵活和交互性强的用户界面。

5.1.2 实现动态组件的方式

Vue 3 提供了多种方式来实现动态组件的切换,主要包括使用 <component> 标签的 is 属性和使用 v-if/v-show 指令。

  1. 使用 <component> 标签的 is 属性

    <component> 标签是 Vue 中的一个特殊标签,它可以根据 is 属性的值动态地渲染不同的组件。is 属性可以是一个字符串,表示要渲染的组件的名称,也可以是一个计算属性或方法,返回要渲染的组件的引用。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <button @click="currentComponent = 'ComponentA'">Show Component A</button>
        <button @click="currentComponent = 'ComponentB'">Show Component B</button>
        <component :is="currentComponent"></component>
      </div>
    </template>
    
    <script>
    import ComponentA from './ComponentA.vue';
    import ComponentB from './ComponentB.vue';
    
    export default {
      data() {
        return {
          currentComponent: 'ComponentA'
        };
      },
      components: {
        ComponentA,
        ComponentB
      }
    };
    </script>

    在这个例子中,点击按钮会改变 currentComponent 的值,从而动态地切换 <component> 标签渲染的组件。

  2. 使用 v-if/v-show 指令

    除了使用 <component> 标签外,还可以使用 v-if/v-show 指令来实现动态组件的切换。v-if 指令会根据表达式的真假值来条件性地渲染元素,而 v-show 指令则只是简单地切换元素的 CSS 属性(如 display)来显示或隐藏元素。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <button @click="showComponentA = true; showComponentB = false">Show Component A</button>
        <button @click="showComponentA = false; showComponentB = true">Show Component B</button>
        <ComponentA v-if="showComponentA" />
        <ComponentB v-if="showComponentB" />
      </div>
    </template>
    
    <script>
    import ComponentA from './ComponentA.vue';
    import ComponentB from './ComponentB.vue';
    
    export default {
      data() {
        return {
          showComponentA: true,
          showComponentB: false
        };
      },
      components: {
        ComponentA,
        ComponentB
      }
    };
    </script>

    在这个例子中,使用 v-if 指令来根据 showComponentAshowComponentB 的值动态地渲染 ComponentAComponentB

5.1.3 动态组件的高级用法

  1. 使用 keep-alive 包裹动态组件

    <keep-alive> 是 Vue 中的一个内置组件,它可以缓存不活动的组件实例,而不是销毁它们。这对于需要频繁切换且不希望重新渲染的组件来说非常有用。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <button @click="currentComponent = 'ComponentA'">Show Component A</button>
        <button @click="currentComponent = 'ComponentB'">Show Component B</button>
        <keep-alive>
          <component :is="currentComponent"></component>
        </keep-alive>
      </div>
    </template>

    在这个例子中,<keep-alive> 会缓存 currentComponent 所指向的组件实例,当再次切换回该组件时,它会保持之前的状态,而不会重新渲染。

  2. 动态创建和销毁组件实例

    在某些情况下,可能需要动态地创建和销毁组件实例,而不是仅仅在模板中切换它们的显示状态。这可以通过 Vue 的 createApp 方法来实现,它允许在运行时创建一个新的 Vue 应用实例,并将其挂载到 DOM 上。然后,可以使用 unmount 方法来销毁该实例。

    示例代码(动态 Toast 组件):

    javascript 复制代码
    // Toast.vue
    <template>
      <div class="toast">{{ message }}</div>
    </template>
    
    <script>
    export default {
      props: {
        message: {
          type: String,
          required: true
        }
      }
    };
    </script>
    
    // 在需要显示 Toast 的地方调用 createToast 函数
    import { createApp } from 'vue';
    import Toast from './Toast.vue';
    
    function createToast(message) {
      const app = createApp(Toast, { message });
      const mountNode = document.createElement('div');
      document.body.appendChild(mountNode);
      const instance = app.mount(mountNode);
    
      // 可以在这里设置定时器来自动销毁 Toast 组件
      setTimeout(() => {
        instance.unmount();
        document.body.removeChild(mountNode);
      }, 3000); // 例如,3秒后自动销毁
    }
    
    // 使用示例
    createToast('这是一个动态创建的 Toast 消息');

5.2 异步组件

5.2.1 异步组件的基本概念

异步组件是指在需要时才被加载和渲染的组件。这意味着,当应用启动时,这些组件不会被立即加载,而是在真正需要显示它们时,才会动态地从服务器或文件系统中加载。这种机制有助于减少初始包的大小,降低应用的初始加载时间,提高加载速度和应用的性能。

5.2.2 定义异步组件的方式

在 Vue 3 中,可以使用 defineAsyncComponent 函数来定义异步组件。这个函数是 Vue 官方提供的,它简化了异步组件的使用过程,并提供了丰富的配置选项。

  1. 基本用法

    使用 defineAsyncComponent 函数时,需要传入一个返回 Promise 的函数。这个 Promise 在解析时应该返回一个组件配置对象(通常是一个组件的选项对象或一个组件定义函数)。这个函数通常使用动态导入 import() 来实现。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <AsyncComponent />
      </div>
    </template>
    
    <script>
    import { defineAsyncComponent } from 'vue';
    
    const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
    
    export default {
      components: {
        AsyncComponent
      }
    };
    </script>

    在这个例子中,当 AsyncComponent 被渲染时,Vue 会异步地加载 ./AsyncComponent.vue 这个模块。

  2. 配置选项

    defineAsyncComponent 函数还可以接受一个可选的配置对象,该对象可以包含以下属性:

    • loadingComponent:当异步组件正在加载时显示的组件。默认情况下,如果没有提供,Vue 会显示一个默认的加载指示器。
    • errorComponent:当异步组件加载失败时显示的组件。默认情况下,如果没有提供,Vue 会显示一个错误信息。
    • delay:一个数字,表示在显示加载组件之前等待的时间(以毫秒为单位)。默认为 0,即立即显示加载组件。
    • timeout:一个数字,表示异步组件加载的超时时间(以毫秒为单位)。如果超时,将触发错误处理。默认为 Infinity,即没有超时限制。
    • onError:一个函数,当异步组件加载失败时调用。这个函数接收错误对象作为参数。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <AsyncComponent />
      </div>
    </template>
    
    <script>
    import { defineAsyncComponent, ref } from 'vue';
    import LoadingComponent from './LoadingComponent.vue';
    import ErrorComponent from './ErrorComponent.vue';
    
    const AsyncComponent = defineAsyncComponent({
      loader: () => import('./AsyncComponent.vue'),
      loadingComponent: LoadingComponent,
      errorComponent: ErrorComponent,
      delay: 200, // 延迟 200 毫秒加载
      timeout: 3000 // 3 秒超时
    });
    
    export default {
      components: {
        AsyncComponent
      }
    };
    </script>

    在这个例子中,当异步组件正在加载时,会显示 LoadingComponent;如果加载失败,则会显示 ErrorComponent;同时设置了 200 毫秒的延迟加载和 3 秒的超时时间。

5.2.3 异步组件与 Suspense 的配合使用

Suspense 是 Vue 3 中的一个内置组件,用于在组件树中协调对异步依赖的处理。它允许开发者在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。

  1. Suspense 的基本用法

    Suspense 组件有两个插槽:#default#fallback#default 插槽用于显示要加载的组件,而 #fallback 插槽则用于在异步组件加载时显示的内容(如加载指示器)。

    示例代码:

    vue 复制代码
    <template>
      <div>
        <Suspense>
          <template #default>
            <AsyncComponent />
          </template>
          <template #fallback>
            <div>Loading...</div>
          </template>
        </Suspense>
      </div>
    </template>
    
    <script>
    import { defineAsyncComponent } from 'vue';
    
    const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
    
    export default {
      components: {
        AsyncComponent
      }
    };
    </script>

    在这个例子中,当 AsyncComponent 正在加载时,会显示 #fallback 插槽中的 "Loading..." 内容。

  2. 异步组件的 suspensible 特性

    异步组件默认是 "suspensible" 的,这意味着如果组件关系链上有一个 Suspense,那么这个异步组件就会被当作这个 Suspense 的一个异步依赖。在这种情况下,加载状态是由 Suspense 控制的,而该组件自己的加载、报错、延时和超时等选项都将被忽略。

    如果希望异步组件不使用 Suspense 控制其加载状态,可以在定义异步组件时指定 suspensible: false

5.2.4 动态加载异步组件

除了静态地定义异步组件外,Vue 3 还支持根据条件动态地加载不同的异步组件。这可以通过在 loader 函数中返回不同的 Promise 来实现。

示例代码:

vue 复制代码
<template>
  <div>
    <button @click="loadComponent('ComponentA')">Load Component A</button>
    <button @click="loadComponent('ComponentB')">Load Component B</button>
    <component :is="dynamicComponent" />
  </div>
</template>

<script>
import { ref, defineAsyncComponent } from 'vue';

const componentName = ref('ComponentA');
let dynamicComponent = null;

const loadComponent = async (name) => {
  dynamicComponent = await defineAsyncComponent(() =>
    import(`./components/${name}.vue`)
  );
};

export default {
  setup() {
    return {
      dynamicComponent,
      loadComponent
    };
  }
};
</script>

在这个例子中,点击按钮会根据 componentName 的值动态地加载不同的异步组件,并将其赋值给 dynamicComponent,然后在模板中使用 <component :is="dynamicComponent" /> 来渲染该组件。

相关推荐
要加油哦~3 分钟前
字节青训营 | 数字分组求偶数和
开发语言·javascript·ecmascript
safe0304 分钟前
SPA和SSR
开发语言·前端·javascript·汇编·vue.js·react.js·前端框架
二川bro8 分钟前
“前端兼容——CSS篇”(进阶版)
前端·css
GISer_Jing1 小时前
CSS常见适配布局方式
前端·css·html
卡布达ovo2 小时前
Django+Vue全栈开发项目入门(四)
vue.js·django
csdn_金手指3 小时前
mac电脑通过 npm 安装 @vue/cli脚手架超时问题;
vue.js·macos·npm
LvManBa3 小时前
Vue学习记录之二十二 Vue3+vite+electron 构建项目实例
vue.js·学习·electron
llrraa20104 小时前
Chrome异步编程
前端·chrome
王哲晓5 小时前
第九章 Vue之watch监听器
css·vue.js·css3
follycat5 小时前
Litctf-web
android·linux·前端·网络安全