Vue 3 进阶实战教程
本教程深入讲解 Vue 3 的高级特性,帮助你编写更优雅、可复用且高性能的代码。
目录
- 组合式函数 (Composables)
- 深入 Props 与 v-model
- 插槽 (Slots)
- Teleport (传送门)
- 异步组件与 Suspense
- 自定义指令
- 依赖注入 (Provide / Inject)
- 性能优化
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. 性能优化
v-memo: 缓存模板子树,仅当依赖变化时重新渲染。适用于大型列表。
<div v-for="item in list" v-memo="[item.id, item.selected]"> ... </div>shallowRef: 仅对
.value这一层进行响应式处理,不对内部对象深层代理。适用于大型不可变数据结构。const bigData = shallowRef({ ... });defineAsyncComponent: 路由懒加载与组件拆分。