入门
快速上手 | Vue.js
Vue3开始官方推荐使用Vite构建项目:开始 | Vite 官方中文文档
搭建项目
官方脚手架
npm create vue@latest
Vite
# npm 7+,需要添加额外的 --:
npm create vite@latest my-vue3-app -- --template vue
pinia
Pinia | The intuitive store for Vue.js (vuejs.org)
示例
import { defineStore } from "pinia";
import { ref } from "vue";
import { listOfXxx } from "@/api/xxx-data"
// Pinia 是单例模式的,store 只会被初始化一次;调用`useXxxStore()`返回的都是同一个实例;
export const useXxxStore = defineStore('xxx', () => {
console.log(`defineStore('xxx')执行了!`); // 这个只会打印一次
const xxxList = ref([]);
const xxxMap = ref(new Map());
const getXxxList = () => {
// 调用api 去后端查询一些全局都需要用到的数据吗,例如下拉框`select`中的`option`
listOfXxx({}).then((response) => {
xxxList.value = response.data;
const map = new Map();
xxxList.value.forEach((supplier) => {
map.set(supplier.id, supplier);
});
xxxMap.value = map;
});
};
return {
xxxList,
xxxMap,
getXxxList
}
}
import { useXxxStore } from '@/stores/xxxStore' // 根据实际路径调整
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
// 方式1:直接使用整个 store
const xxxStore = useXxxStore()
// 方式2:使用 storeToRefs 解构保持响应式
const { xxxList, xxxMap } = storeToRefs(xxxStore)
// 组件挂载时获取数据
onMounted(() => {
// 如果有多个页面需要使用,可以在一个地方批量初始化,例如登录成功之后的路由拦截里处理。
xxxStore.getXxxList() // 不推荐在某一个页面内调用
})
父子传参
子组件(可能需要 v-model语法糖)
<template>
<!-- 模版中直接使用属性 -->
{{ customerIdList }}
</template>
<script setup>
// 定义 props 参数
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
accountId: {
type: [String, Number],
default: null
},
customerIdList: {
type: [Array],
default: []
}
})
// 定义 emits 事件
const emit = defineEmits(['update:modelValue', 'success'])
// js中使用属性
console.log(props.customerIdList)
// js中调用方法
emit('success')
</script>
Vue-CLI & Vite
| 对比项 | Vite | Vue-CLI |
|---|---|---|
| 构建工具 | 基于 ESM + Rollup | 基于 Webpack |
| 启动速度 | ⚡ 极快(利用浏览器原生ESM) | 🐢 较慢(需预先打包) |
| HMR(热更新) | ⚡ 毫秒级 | 🚀 较快(但慢于Vite) |
| 生产构建 | Rollup(优化更好) | Webpack(稳定但稍重) |
| 配置复杂度 | ✅ 更简单(约定优于配置) | ⚙️ 较复杂(依赖vue.config.js) |
| 生态插件 | 🌱 较新(但增长迅速) | 🌳 成熟(Webpack生态丰富) |
| 适用场景 | 现代Vue 3项目、追求速度 | 传统项目、需要Webpack插件 |
环境变量
.env 文件放在 Vue 3 项目的根目录下(与 package.json 同级)。
不同
- 在 Vite 项目 (
VITE_前缀) 中:
在 Vite 配置文件 (vite.config.js/ts) 中:使用
process.env.VITE_XXX或import.meta.env.VITE_XXX。在 客户端代码 (Vue 组件、JS/TS 文件) 中:使用
import.meta.env.VITE_XXX。
// 在 Vue 组件或 JS/TS 文件中
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
const isDebug = import.meta.env.VITE_DEBUG_MODE === 'true'; // 注意:从 .env 加载的值都是字符串,需要转换类型
console.log(`Using API: ${apiBaseUrl}, Debug: ${isDebug}`);
- 注意:除了
BASE_URL和NODE_ENV,所有自定义环境必须都是VUE_APP_开头。
2.在 Vue CLI 项目 (VUE_APP_ 前缀) 中:
在 Vue 配置文件 (vue.config.js) 中:使用
process.env.VUE_APP_XXX。在 客户端代码 (Vue 组件、JS/TS 文件) 中:使用
process.env.VUE_APP_XXX。
// 在 Vue 组件或 JS/TS 文件中
const appTitle = process.env.VUE_APP_TITLE;
console.log(`Welcome to ${appTitle}`);
注意:除了BASE_URL和NODE_ENV,所有自定义环境必须都是VITE_开头。
从
import.meta.env或process.env获取的值总是字符串类型。需要其他类型需要自己转换;修改
.env文件后,通常需要重启开发服务器 (npm run dev) 才能使新的环境变量生效(Vite 有时会自动检测.env变更,但重启最保险)。构建 (npm run build) 会使用构建时的环境变量。
加载优先级
当项目运行时,环境变量会根据当前模式 (NODE_ENV 或 --mode 指定的值) 从多个 .env 文件中加载。加载顺序和优先级如下(后者覆盖前者):
.env // 所有情况下都会加载
.env.local // 所有情况下都会加载,但会被 git 忽略
.env.[mode] // 只在指定模式下加载
.env.[mode].local // 只在指定模式下加载,但会被 git 忽略
指定模式
默认模式:
npm run dev / vite // 模式为 development
npm run build / vite build // 模式为 production
自定义模式:
你可以在 package.json 的 scripts 中创建自定义命令,并通过 --mode 参数指定模式:
"scripts": {
"dev": "vite", // 默认 development
"build": "vite build", // 默认 production
"build:staging": "vite build --mode staging", // 自定义 staging 模式
"preview:staging": "vite preview --mode staging" // 预览 staging 构建
}
请注意,如果想要在环境变量中使用 $ 符号,则必须使用 \ 对其进行转义。
.env
KEY=123
NEW_KEY1=test$foo // test
NEW_KEY2=test\$foo // test$foo
NEW_KEY3=test$KEY // test123
新文件的一些不同
Vue 2 中的 data 函数:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
data() {
return { count: 0 };
},
methods: {
increment() { this.count++; }
},
computed: {
doubled() { return this.count * 2; }
}
// 一个功能的代码分散在各处
}
</script>
Vue 3 中的 setup 函数:
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, computed } from 'vue';
// Options API:选项式api 必须要在setup方法内显式return,支持与Vue2风格的选项式api混合使用;
export default {
// 如果有父组件传入值
props: {
message: String
}
setup(props) {
// 可以直接使用父组件传入的属性 props.message,注意如果你解构了props对象,解构出的变量将会丢失响应性。
// 计数器功能的相关代码都在一起
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() { count.value++; }
return { count, doubled, increment };
}
}
</script>
// Composition API:组合式api,现在更推荐使用,自动暴露顶层变量,与TS友好;
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
2 & 3一些主要不同
在 Vue3 中,setup 函数接收两个参数,props 和 context,但通常情况下,我们只关心 context,而不再直接返回响应式的数据对象。相反,我们通过 ref、reactive 等函数来创建响应式数据,并将它们以对象的形式返回,以便在模板中使用。ref 用于包装基本类型的数据,而 reactive 则用于包装对象类型的数据。
以下是Vue2 和 Vue3 语法上的一些主要区别,通过表格展示:
| 功能/语法 | Vue 2 | Vue 3 |
|---|---|---|
| 创建Vue实例 | new Vue({}) | createApp({}).mount('#app') |
| 模板语法 | 使用字符串模板 | 使用 setup 函数和模板,支持 template 选项 |
| 数据响应式定义 | data 选项中返回对象 | 使用 ref(基本类型)、reactive({对象})、computed 等 Composition API |
| 组件定义 | Vue.component('my-component', {...}) | app.component('my-component', {...}) |
| 组件通信 | props 选项传递数据,$emit 触发事件 | props 选项传递数据,$emit 触发事件 |
| 计算属性 | computed 选项 | 使用 computed(() => {return ...}) 函数 |
| 监听属性 | watch 选项 | 使用 watchEffect 函数和 watch 函数 |
| 生命周期钩子 | beforeCreate, created, mounted, 等等 | setup 函数中使用 onBeforeMount, onMounted, 等等 |
| 自定义指令 | directives 选项 | app.directive |
| 过渡动画 | <transition> 和 <transition-group> 元素 | <transition> 和 <transition-group> 组件 |
| 全局API | Vue.use(plugin) 和 Vue.mixin(mixin) | 移除 Vue 对象,使用 app.use(plugin) 和 app.mixin(mixin) |
| 路由 | Vue Router 插件 | Vue Router 升级到 4.x,语法略有不同 |
| 状态管理 | Vuex 插件 | Vuex 升级到 4.x,语法略有不同 |
| 渲染函数 | 使用 render 选项 | 使用 setup 函数和 h 函数(createElement 的别名) |
上述表格列举的是一些主要的语法和功能上的区别,详情参考 官网 ;
创建响应式对象的区别
Vue3 中的 ref 和 Vue2 中的响应式对象(通过 data 函数返回的对象)在实现原理和用法上存在一些区别。
1. 创建方式
Vue3
ref:import { ref } from 'vue'; const myRef = ref(101);Vue2 响应式对象:
data() { return { myData: 101, }; }
2. 访问和修改值
Vue 3
ref:就算是
ref()的引用类型,也需要使用.value来获取值、使用值。const value = myRef.value; // 获取值 myRef.value = newValue; // 修改值Vue 2 响应式对象:
const value = this.myData; // 获取值 this.myData = newValue; // 修改值
3. 自动解包
Vue 3
ref: 在模板中使用ref创建的响应式对象时,Vue 3 会自动解包其值,无需手动访问.value。- 使用
watch或computed时,ref对象也会自动解包。
<template> <div>{{ myRef }}</div> </template>- 使用
Vue 2 响应式对象: 在模板中需要手动访问对象的属性。
<template> <div>{{ myData }}</div> </template>
4. 响应式对象的引用类型
Vue 3
ref:ref会自动保持响应式对象的引用类型。如果将一个对象传递给ref,它将被视为一个响应式对象。const obj = { prop: 'value' }; const objRef = ref(obj);Vue 2 响应式对象: 如果需要将对象转为响应式,通常需要使用
Vue.observable。const obj = { prop: 'value' }; const objData = Vue.observable(obj);
扩展
Ref是一个TypeScript的类型,用于表示一个由ref创建的响应式引用对象的类型。Ref类型用于类型注解,以便在TypeScript中对使用ref创建的响应式引用进行类型检查。import { Ref, ref } from 'vue'; const count: Ref<number> = ref(0); console.log(count.value); // 0 count.value++; console.log(count.value); // 1ref是一个函数,用于创建响应式引用。Ref是一个TypeScript类型,用于类型注解ref创建的响应式引用。
总体概念
Vue 3
ref:ref是 Vue 3 中专门用于创建响应式对象的 API,它不仅可以包装基本类型的值,还可以用于转换普通对象。Vue 2 响应式对象: Vue 2 中通过
data函数返回的对象是通过 Vue 内部的响应式系统实现的,但它并非专门用于创建响应式对象的 API。
总体来说,Vue 3 的 ref 更加灵活,直观,并且在使用上更为一致。同时,Vue 3 的 ref 也提供了一些新的特性,例如可以用于创建响应式的 reactive 对象,以及与 Composition API 更好的集成等。
hooks
验证码倒计时hook
/hooks/useCountDown.ts
import { Ref, ref } from "vue"; // 默认导出的函数 接受一个参数(number),返回值是一个Object{ string, Function } export default ( downNum: number ): ({ sendBtnText: Ref<string>, sendCode: () => void }) => { const sendBtnText = ref("发送验证码"); const countDownNum = ref(downNum); // 这里省略调用发送短信接口逻辑,省略禁止点击逻辑 const sendCode = () => { if (countDownNum.value > 0) { // 模拟发送验证码的逻辑 console.log("验证码已发送"); // 启动倒计时 const timer = setInterval(() => { countDownNum.value--; sendBtnText.value = `重新发送(${countDownNum.value}s)`; if (countDownNum.value <= 0) { clearInterval(timer); sendBtnText.value = "发送验证码"; // 重置倒计时 countDownNum.value = downNum; } }, 1000); }; return { sendBtnText, sendCode }; }index.vue
<template> <div> <input type="text" placeholder="请输入验证码" v-model="code"> <button @click="sendCode">{{ sendBtnText }}</button> </div> </template> <script lang='ts' setup> import { ref } from "vue"; import useCountDown from "../hooks/useCountDown"; const code = ref(""); const { sendBtnText, sendCode } = useCountDown(60);
依赖
在 package.json 文件中,依赖导入的 key 通常用于指定项目的依赖项。这些 key 表示你的项目依赖于哪些 npm 包,以及需要这些包的哪个版本。以下是 package.json 中几种常见的依赖导入方式:
dependencies
dependencies 是项目运行时需要的依赖项。这些依赖会在运行 npm install 或 yarn install 时被安装。
{
"dependencies": {
"vue": "^3.0.0",
"axios": "^0.21.1"
}
}
devDependencies
devDependencies 是仅在开发阶段需要的依赖项,这些依赖不会在生产环境中使用。常见的例子包括构建工具、测试框架等。
{
"devDependencies": {
"webpack": "^5.0.0",
"babel-loader": "^8.1.0"
}
}
peerDependencies
peerDependencies 是指定你的包所兼容的依赖版本。它通常用于库或插件开发中,确保消费者安装与你的包兼容的依赖版本。
{
"peerDependencies": {
"react": "^17.0.0"
}
}
optionalDependencies
optionalDependencies 是可选的依赖项,如果这些依赖项的安装失败,npm 不会因为这些失败而终止整个安装过程。
{
"optionalDependencies": {
"fsevents": "^2.1.2"
}
}
bundledDependencies 或 bundleDependencies
bundledDependencies(或 bundleDependencies)是指在发布包时希望捆绑在一起的依赖项。使用这些依赖项的包会被捆绑在一起发布。
{
"bundledDependencies": [
"module-name"
]
}
resolutions
resolutions 是一种在项目中强制使用特定版本依赖的方式,常用于处理子依赖的版本冲突。这个字段主要用于 Yarn。
{
"resolutions": {
"some-package": "1.2.3"
}
}
版本号的几种格式
在 dependencies 、 devDependencies 等字段中,版本号的格式可以有以下几种:
确切版本号:只安装确切的 3.0.0 版本。
"vue": "3.0.0"波浪号(
~):安装 3.0.x 版本,但不包括 3.1.0。"vue": "~3.0.0"插入号(
^):安装 3.x.x 版本,但不包括 4.0.0。"vue": "^3.0.0"星号(
*):安装任何版本。"vue": "*"范围:安装在 3.0.0 和 4.0.0 之间的版本。
"vue": ">=3.0.0 <4.0.0"最新版本:安装最新发布的版本。
"vue": "latest"
Vue3 项目架构与启动流程
1. 项目核心架构
一个典型的基于Vite的 Vue 3 项目结构如下所示:
my-vue3-app
├── node_modules/ # 项目依赖包
├── public/ # 静态资源(不会被Vite处理)
│ └── favicon.ico
├── src/ # 源码目录(核心)
│ ├── assets/ # 静态资源(会被Vite处理,如图片、样式)
│ ├── components/ # 可复用Vue组件
│ ├── App.vue # 应用的根组件
│ └── main.js # **应用的入口文件**
├── index.html # **HTML页面模板**
├── package.json # 项目配置和依赖列表
├── vite.config.js # Vite配置文件
└── ... (其他配置文件)
2. 启动流程详解
整个过程可以看作是一个“挂载”的过程:将Vue应用实例挂载到真实的DOM节点上。
起点:
index.html浏览器首先加载
index.html。这个文件是Vite提供服务的入口页面。你会发现其中有一个
<div id="app"></div>的容器元素。这就是未来Vue应用要控制的DOM区域。它通过ES模块导入的方式引入了
/src/main.js文件:<!DOCTYPE html> <html lang="en"> <head> ... </head> <body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body> </html>
核心入口:
/src/main.js这是JavaScript的 入口 文件,也是整个Vue应用的初始化中心。
它的主要职责是:
- 导入依赖:从
vue包中导入createApp函数,并导入根组件App.vue。 - 创建应用实例:调用
createApp(App),以根组件App.vue为参数,创建一个Vue应用实例。 - 配置应用(可选):在此阶段,你可能会为应用实例配置插件(如Vue Router、Pinia)、注册全局组件或设置全局属性。
- 挂载应用:调用应用实例的
.mount('#app')方法,告诉Vue将创建好的应用实例挂载到index.html中 id 为app的DOM元素上。
一个典型的
main.js文件内容如下:// 1. 导入依赖和根组件 import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; // 导入路由配置 import store from './store'; // 导入状态管理 store (如Pinia) // 2. 创建应用实例并以根组件作为参数 const app = createApp(App); // 3. (可选)配置应用:使用路由器、状态管理库等插件 app.use(router); app.use(store); // 4. 将应用实例挂载到DOM节点上 app.mount('#app');- 导入依赖:从
根组件:
/src/App.vuemain.js中创建的Vue应用实例以App.vue作为其根组件。- 这个文件通常定义了应用的 整体布局结构 (如顶部导航栏、侧边栏、主内容区等),并包含了路由的出口(
<router-view>),不同页面组件的内容将在这里被渲染。
<template> <!-- 应用的整体布局 --> <div id="app"> <NavBar /> <main> <!-- 路由出口,页面内容将在此渲染 --> <router-view /> </main> <AppFooter /> </div> </template> <script> import NavBar from './components/NavBar.vue'; import AppFooter from './components/AppFooter.vue'; export default { name: 'App', components: { NavBar, AppFooter } } </script>路由与视图渲染
- 当你在浏览器中切换路由(URL)时,Vue Router会根据配置的路由规则匹配到对应的页面组件(如
Home.vue,About.vue)。 - 这个页面组件会被动态地渲染到
App.vue中的<router-view>位置,从而显示出不同的页面内容。
- 当你在浏览器中切换路由(URL)时,Vue Router会根据配置的路由规则匹配到对应的页面组件(如
构建与部署
- 开发时,运行
npm run dev,Vite会启动一个开发服务器,处理上述所有流程。 - 当项目需要上线时,运行
npm run build。Vite(使用Rollup)会将你的所有Vue组件、CSS、JS代码进行打包、压缩和优化,生成最终的静态文件(HTML, JS, CSS, 图片等)并放置在dist目录中。你将这个dist目录部署到任何静态文件服务器(如Nginx)即可。
- 开发时,运行
整个流程的核心顺序可以概括为:加载 index.html → 执行 main.js → 创建Vue应用实例 → 挂载到DOM → 渲染 App.vue → 通过路由渲染具体页面组件。
坑
reactive 响应式丢失
原因:对 reactive 变量进行再赋值;
let selectedColumns = reactive([]);
// 在某个方法中
selectedColumns = [...]; // 此时 selectedColumns 已经丢失了响应式信息。
// 如果有组件在使用这个变量,在上一步重新赋值之后就不在响应数组的改变了。
解决办法:
方法1:使用 Object.assign (推荐)
let selectedColumns = reactive([]);
// 再赋值时不丢失响应式
function updateColumns(newColumns) {
// 清空原数组
selectedColumns.length = 0;
// 添加新元素
selectedColumns.push(...newColumns);
}
// 或者使用 Object.assign
function updateColumns(newColumns) {
// 将一个或多个源对象的所有可枚举的自身属性复制到目标对象。它返回修改后的目标对象。Object.assign(target, source);
Object.assign(selectedColumns, newColumns);
}
方法5:使用 reactive 包装对象
// 将数组包装在对象中
const state = reactive({
selectedColumns: []
});
// 赋值时
state.selectedColumns = newArray; // 这样不会丢失响应式
// 或者在模板中直接使用 state.selectedColumns
FAQ
Options API 与 Composition API 如何选择及混用是否对性能有影响
1、使用了 Vue3,是否都要遵循用 Composition API 的形式去写页面?
答案是否定的。
需要注意一点:Vue3 并没有废弃 Options API,甚至还会全力支持兼容 Vue2 语法的工作。
而 CompositionAPI 出现的背景主要是为了解决逻辑抽象和和复用的问题,但不意味着它成为了 Vue3 的标准。
因此如何区分场景使用 Options API or Composition API 主要看业务逻辑的复杂程序 ,例如一些简单的 toast/button 等基础组件,用options API形式会更加清晰和简洁。而相对复杂的业务逻辑,可以用 Composition API,可以把单独一块逻辑抽离到一个模块,通过 hook 函数的方式去解决。
2、Vue3 中混用 Options API 和 Composition API 会不会对性能产生影响?
答案是不会。其实从问题 1 就可以明显地看出来并不会对性能产生任何影响。不应该被 option api 限制思维,而更多关注逻辑内聚问题。
关于 setup 中没有 this 的问题及 setup 的执行时机
vue 官方文档是这么解释的:在 setup() 内部,this 不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性 :本地状态、计算属性/方法。
但是 从源码实现你会发现其实组件实例创建在前,函数之所以访问不到 this,是因为它在执行 setup 函数的时候,就 **没有把组件实例 instance 传给 setup **。也 没有把 this 指向实例 instance 。
因此执行顺序其实是:组件实例创建在 setup 函数执行之前,但是 setup 执行的时候,组件还没有 mounted,而晚于 beforeCreate 钩子,早于 create 钩子。
语法
v-model="属性名" : 双向绑定(v-model 默认指的是 v-model:value)
v-bind:属性名 或者 :属性名 : 使用data() return{} 里面的值,父组件传值给子组件。父 -> 子单向流。
v-on:change="方法名()" 或者 @change="方法名()" : 事件绑定
// 引入外部vue
import MainContent from "@/layouts/MainContent.vue";
// 使用(按中划线隔开)
<main-content></main-content>
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App);
app.use(router);
app.mount('#app')
// 路由渲染
<router-view></router-view>
// 填入路由表中对应的地址
router.push("/")
arco design vue
<a-row :gutter="16">
<a-col :span="8"></a-col>
</a-row>
上面另外有一个知识点,是vue中,比如option组件 value 需要传给后端数字,在前面加上冒号 :value 就行了。
语法糖
v-model
在 Vue3 中,给子组件使用 v-model(双向更新) 时,它默认绑定到子组件的 modelValue 属性,并通过 update:modelValue 事件进行更新。
父组件
<ChildComponent v-model="parentValue" />等价于
<ChildComponent :modelValue="parentValue" @update:modelValue="newValue => parentValue = newValue" /> <!-- newValue => parentValue = newValue 语法糖,相当于 (newValue) => { parentValue = newValue } -->子组件
ChildComponent.vue<script setup> defineProps(['modelValue']) defineEmits(['update:modelValue']) <!-- 如果不是input事件触发的,可以利用watch监听变化之后调用update方法 --> watch(visible, (newVal) => { emit('update:modelValue', newVal) }) </script> <template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template>使用多个 v-model:
<ChildComponent v-model:firstName="first" v-model:lastName="last" />这时子组件需要分别定义
firstName和lastName属性,以及对应的update:firstName和update:lastName事件。Vue 3 的 v-model 相比 Vue 2 更加灵活和明确,不再默认绑定到
value属性和input事件。
Teleport
<Teleport to="元素选择器"> 是一个内置组件,用于 将组件内容渲染到DOM树中的不同位置 ,而不受当前组件DOM层级的限制。
提示
虽然代码写在组件模板中,但实际DOM会被移动到body元素内。
解决z-index问题:避免模态框被父组件的样式影响
避免CSS继承:防止父组件的样式影响弹窗等组件
全局组件定位:让某些组件脱离当前组件层级限制
示例:
<!-- 传送到body内-->
<Teleport to="body">
<Toast />
</Teleport>
<!-- 传送到指定ID的元素 -->
<Teleport to="#modal-container">
<Modal />
</Teleport>
<!-- 传送到CSS选择器匹配的元素 -->
<Teleport to=".global-container">
<Notification />
</Teleport>
Transition
Vue3 内置组件,为 (子)元素/(子)组件 的 进入/离开 、条件渲染 等场景提供动画过渡效果:
- v-if / v-else
- v-show
- 动态组件
<component :is="..."> - 组件根节点变化
工作原理:
当你设置了组件的 name 属性,Vue 会根据元素的进入/离开状态自动添加以下类名:
进入动画:
toast-enter-from- 进入开始状态toast-enter-active- 进入激活状态toast-enter-to- 进入结束状态
离开动画:
toast-leave-from- 离开开始状态toast-leave-active- 离开激活状态toast-leave-to- 离开结束状态
提示
-from= 从哪里开始(初始状态)-active= 怎么运动(过渡配置)-to= 到哪里结束(最终状态)
支持 JS 钩子:
进入阶段(Enter):
@before-enter- 在元素插入 DOM 前触发
- 适合设置初始状态
@enter- 在元素插入 DOM 后触发
- 接收
done回调,必须在动画完成后调用
@after-enter- 进入过渡完成后触发
- 适合清理工作
@enter-cancelled- 进入过渡被取消时触发
离开阶段(Leave):
@before-leave- 在离开过渡开始前触发
@leave- 在离开过渡开始时触发
- 接收
done回调,必须在动画完成后调用
@after-leave- 离开过渡完成后触发
- 元素已从 DOM 移除
@leave-cancelled- 离开过渡被取消时触发
例如自己写的Toast组件:
提示
项目内示例文件见:vue-chat/src/components/Toast.vue
<template>
<Teleport to="body">
<!-- 使用 Transition 包裹 -->
<Transition
name="toast-fade"
@after-leave="$emit('afterLeave')"
>
<!-- 弹框遮罩层 -->
<div v-if="modelValue" class="toast-overlay" @click="handleOverlayClick">
<!-- 弹框内容... -->
<div class="toast-container" :class="typeClass"></div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
interface Props {
modelValue: boolean;
type?: ConfirmType;
}
const props = withDefaults(defineProps<Props>(), {
type: "warning",
});
const emit = defineEmits<{
"update:modelValue": [value: boolean];
// 可以在此设置回调事件
"afterLeave": [];
}>();
</script>
<style scoped>
/*
* 遮罩层 (toast-overlay) 只有 opacity 淡入淡出效果
* toast 内容 (toast-container) 有 translateY 上下移动效果
*/
.toast-fade-enter-active,
.toast-fade-leave-active {
transition: opacity 0.3s ease;
}
.toast-fade-enter-active .toast-container,
.toast-fade-leave-active .toast-container {
transition: all 0.3s ease;
}
.toast-fade-enter-from {
opacity: 0;
}
.toast-fade-enter-from .toast-container {
transform: translateY(-20px);
}
.toast-fade-enter-to {
opacity: 1;
}
.toast-fade-enter-to .toast-container {
transform: translateY(0);
}
.toast-fade-leave-from {
opacity: 1;
}
.toast-fade-leave-from .toast-container {
transform: translateY(0);
}
.toast-fade-leave-to {
opacity: 0;
}
.toast-fade-leave-to .toast-container {
transform: translateY(20px);
}
</style>
Solt
slot(插槽) 用于组件间的 内容分发 。它允许你将模板片段作为内容传递给子组件,使组件更灵活、可复用。
🎯 核心作用
- 内容注入:在父组件中定义内容,插入到子组件的指定位置。
- 结构复用:子组件提供“占位符”,父组件控制具体显示内容。
📦 Slot 类型与用法
- 默认插槽(Default Slot)
子组件预留位置,父组件传入内容填充。
子组件 Child.vue:
<template>
<div>
<h3>子组件标题</h3>
<slot>默认内容(可选)</slot>
</div>
</template>
父组件使用:
<template>
<Child>
<p>这是插入到子组件的内容</p>
</Child>
</template>
- 具名插槽(Named Slot)
子组件定义多个插槽,父组件按名称指定内容。
子组件 Layout.vue:
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
父组件使用(Vue 3 语法):
<template>
<Layout>
<template #header>
<h1>页面标题</h1>
</template>
<p>这里是主内容(默认插槽)</p>
<template #footer>
<p>版权信息</p>
</template>
</Layout>
</template>
- 作用域插槽(Scoped Slot)
子组件向插槽传递数据,父组件接收并使用这些数据。
子组件 List.vue:
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index">
默认显示: {{ item.name }}
</slot>
</li>
</ul>
</template>
<script setup>
const items = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' }
]
</script>
父组件使用:
<template>
<List>
<template #default="{ item, index }">
<span style="color: blue">{{ index + 1 }}. {{ item.name }}</span>
</template>
</List>
</template>
⚡ Vue 3 语法变化
v-slot简写:#符号替代v-slot:- 默认插槽:可用
#default明确指定 - 解构支持:直接在插槽prop中解构
🎪 动态插槽名
<template>
<Component>
<template #[dynamicSlotName]>
动态插槽内容
</template>
</Component>
</template>
💡 使用场景
- 布局组件:Header、Footer、Sidebar 等布局结构
- 列表渲染:自定义每项内容的显示方式
- UI 组件库:Table、Grid 等需要高度定制的内容
- 复合组件:多个组件协同工作的复杂场景
注意
⚠️ 注意事项
- 插槽内容在父组件作用域中编译
- 作用域插槽数据来自子组件
- 可设置默认内容,当父组件不提供内容时显示
