入门
快速上手 | 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}`);
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}`);
从
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. 项目核心架构
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应用的初始化中心[5](https://cloud.tencent.com.cn/developer/information/此main.js的vuejs 3版本-album)10。
它的主要职责是:
- 导入依赖:从
vue包中导入createApp函数,并导入根组件App.vue。 - 创建应用实例:调用
createApp(App),以根组件App.vue为参数,创建一个Vue应用实例。 - 配置应用(可选):在此阶段,你可能会为应用实例配置插件(如Vue Router、Pinia)、注册全局组件或设置全局属性。
- 挂载应用:调用应用实例的
.mount('#app')方法,告诉Vue将创建好的应用实例挂载到index.html中ID为app的DOM元素上。
一个典型的
main.js文件内容如下[5](https://cloud.tencent.com.cn/developer/information/此main.js的vuejs 3版本-album):// 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)即可6。
- 开发时,运行
整个流程的核心顺序可以概括为:加载 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
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限制思维,而更多关注逻辑内聚问题。
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事件。xxx
