返回首页

Vue 3 进阶实战教程

Vue 3 进阶实战教程

本教程深入讲解 Vue 3 的高级特性,帮助你编写更优雅、可复用且高性能的代码。


目录

  1. 组合式函数 (Composables)
  2. 深入 Props 与 v-model
  3. 插槽 (Slots)
  4. Teleport (传送门)
  5. 异步组件与 Suspense
  6. 自定义指令
  7. 依赖注入 (Provide / Inject)
  8. 性能优化

1. 组合式函数 (Composables)

Vue 3 最大的变革之一就是利用 Composition API 封装可复用的逻辑。相比 Vue 2 的 Mixins,Composable 解决了命名冲突和数据来源不清晰的问题。

实战:封装鼠标追踪器

创建 composables/useMouse.js:

import { ref, onMounted, onUnmounted } from "vue";

export function useMouse() {
  const x = ref(0);
  const y = ref(0);

  function update(event) {
    x.value = event.pageX;
    y.value = event.pageY;
  }

  onMounted(() => window.addEventListener("mousemove", update));
  onUnmounted(() => window.removeEventListener("mousemove", update));

  // 返回响应式状态
  return { x, y };
}

在组件中使用:

<template>
  <div>鼠标位置: {{ x }}, {{ y }}</div>
</template>

<script setup>
import { useMouse } from "./composables/useMouse";

const { x, y } = useMouse();
</script>

实战:封装异步请求 (useFetch)

import { ref, watchEffect, toValue } from "vue";

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  watchEffect(async () => {
    // toValue 允许 url 是 ref 或 getter
    const urlValue = toValue(url);
    data.value = null;
    error.value = null;

    try {
      const res = await fetch(urlValue);
      data.value = await res.json();
    } catch (e) {
      error.value = e;
    }
  });

  return { data, error };
}

2. 深入 Props 与 v-model

2.1 组件上的 v-model

在 Vue 3 中,自定义组件的 v-model 默认绑定 modelValue 属性和 update:modelValue 事件。

子组件 (CustomInput.vue):

<script setup>
defineProps(["modelValue"]);
defineEmits(["update:modelValue"]);
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

父组件:

<CustomInput v-model="searchText" />

2.2 多个 v-model

Vue 3 支持在一个组件上绑定多个 v-model。

子组件:

<script setup>
defineProps({
  firstName: String,
  lastName: String,
});
defineEmits(["update:firstName", "update:lastName"]);
</script>

<template>
  <input
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

父组件:

<UserName v-model:first-name="first" v-model:last-name="last" />

3. 插槽 (Slots)

3.1 具名插槽

子组件 (BaseLayout.vue):

<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
      <!-- 默认插槽 -->
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

父组件:

<BaseLayout>
  <template #header>
    <h1>页面标题</h1>
  </template>

  <p>主要内容...</p>

  <template #footer>
    <p>联系我们</p>
  </template>
</BaseLayout>

3.2 作用域插槽 (Scoped Slots)

子组件向父组件传递数据。

子组件 (MyList.vue):

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <!-- 将 item 数据传给插槽 -->
      <slot :item="item"></slot>
    </li>
  </ul>
</template>

父组件:

<MyList :items="products">
  <template #default="{ item }">
    <!-- 父组件决定如何渲染每一项 -->
    <b>{{ item.name }}</b> - ${{ item.price }}
  </template>
</MyList>

4. Teleport (传送门)

将组件的 DOM 渲染到 DOM 树的其他位置(如 <body>),常用于模态框 (Modal) 或通知 (Toast)。

<template>
  <button @click="open = true">打开模态框</button>

  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>这是一个全局模态框!</p>
      <button @click="open = false">关闭</button>
    </div>
  </Teleport>
</template>

<script setup>
import { ref } from "vue";
const open = ref(false);
</script>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  transform: translateX(-50%);
  background: white;
  padding: 20px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
</style>

5. 异步组件与 Suspense

5.1 定义异步组件

import { defineAsyncComponent } from "vue";

const AsyncComp = defineAsyncComponent(() =>
  import("./components/HeavyChart.vue")
);

5.2 Suspense (实验性)

处理异步依赖(如 async setup() 或异步组件)的加载状态。

<template>
  <Suspense>
    <!-- 主要内容 -->
    <template #default>
      <AsyncComp />
    </template>

    <!-- 加载中状态 -->
    <template #fallback> Loading chart... </template>
  </Suspense>
</template>

6. 自定义指令

封装 DOM 操作逻辑。

实战:v-focus 指令

<script setup>
const vFocus = {
  mounted: (el) => el.focus(),
};
</script>

<template>
  <input v-focus />
</template>

7. 依赖注入 (Provide / Inject)

在深层组件树中共享数据,避免 Prop 逐层透传 (Prop Drilling)。

根组件:

<script setup>
import { provide, ref } from "vue";

const theme = ref("dark");
provide("theme", theme); // 提供数据
</script>

深层子组件:

<script setup>
import { inject } from "vue";

const theme = inject("theme"); // 注入数据
</script>

<template>
  <div :class="theme">当前主题: {{ theme }}</div>
</template>

8. 性能优化

  1. v-memo: 缓存模板子树,仅当依赖变化时重新渲染。适用于大型列表。

    <div v-for="item in list" v-memo="[item.id, item.selected]">
      ...
    </div>
    
  2. shallowRef: 仅对 .value 这一层进行响应式处理,不对内部对象深层代理。适用于大型不可变数据结构。

    const bigData = shallowRef({ ... });
    
  3. defineAsyncComponent: 路由懒加载与组件拆分。