Vue.js是一个渐进式JavaScript框架,用于构建用户界面。Vue的核心库专注于视图层,不仅易于上手,还便于与第三方库或既有项目整合。Vue采用自底向上增量开发的设计,其核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Vue由尤雨溪于2014年创建,随后迅速成为最受欢迎的前端框架之一。Vue的设计理念是"渐进式",意味着您可以根据需求逐步采用Vue的功能,从简单的组件替换到完整的单页应用都可以实现。Vue的中文文档非常完善,对中文开发者非常友好。
Vue的主要特点包括:响应式数据绑定、组件系统、指令系统、虚拟DOM、单文件组件等。这些特性使得Vue具有学习曲线平缓、文档友好、生态系统丰富等优势。
| 概念 | 说明 |
|---|---|
| 响应式数据 | 数据变化时自动更新视图 |
| 组件 | 可复用的Vue实例 |
| 指令 | 带有v-前缀的特殊属性 |
| 虚拟DOM | 内存中的DOM表示 |
| 单文件组件 | .vue文件包含模板、脚本、样式 |
| 版本 | 发布年份 | 主要特性 |
|---|---|---|
| Vue 2 | 2016 | 响应式系统、组件、Vue CLI |
| Vue 3 | 2020 | 组合式API、Teleport、Fragments、性能提升 |
Vue CLI是Vue.js官方提供的脚手架工具,用于快速搭建Vue项目。
# 安装Vue CLI
npm install -g @vue/cli
# 创建项目
vue create my-vue-app
# 进入目录
cd my-vue-app
# 启动开发服务器
npm run serve
Vue CLI提供了交互式的项目创建界面,可以选择预设配置或手动配置项目选项,包括TypeScript支持、CSS预处理器、路由、状态管理等功能。
Vite是新一代构建工具,为Vue提供了极快的开发体验。
# 使用Vite创建Vue项目
npm create vite@latest my-vue-app -- --template vue
# 进入目录
cd my-vue-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
Vite利用浏览器原生ES模块支持,实现了即时的服务器启动和热模块替换。相比Vue CLI,Vite在大型项目中具有明显的速度优势。
对于简单的项目,可以直接使用CDN引入Vue。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue CDN示例</title>
</head>
<body>
<div id="app">
<h1>{{ message }}</h1>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: "Hello Vue!"
};
}
}).mount("#app");
</script>
</body>
</html>
Vue模板使用双花括号进行数据绑定,称为"Mustache"语法。
<!-- 文本插值 -->
<p>{{ message }}</p>
<!-- 原始HTML -->
<p v-html="rawHtml"></p>
<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>
<!-- JavaScript表达式 -->
<p>{{ number + 1 }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<p>{{ ok ? 'Yes' : 'No' }}</p>
指令是带有v-前缀的特殊属性,用于在模板中应用响应式行为。
| 指令 | 说明 | 示例 |
|---|---|---|
| v-bind | 绑定属性 | <img :src="../media/imageUrl"> |
| v-on | 绑定事件 | <button @click="handleClick"> |
| v-if | 条件渲染 | <div v-if="show"> |
| v-else | 条件else | <div v-else> |
| v-for | 列表渲染 | <li v-for="item in items"> |
| v-model | 双向绑定 | <input v-model="text"> |
| v-show | 显示隐藏 | <div v-show="show"> |
| v-once | 一次性渲染 | <span v-once>{{ msg }}</span> |
<!-- 条件渲染 -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>其他</div>
<!-- 列表渲染 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
</ul>
<!-- 对象遍历 -->
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
data选项用于声明组件的响应式状态。
const app = {
data() {
return {
message: "Hello Vue!",
user: {
name: "张三",
age: 25
},
items: ["a", "b", "c"]
};
}
};
Vue会递归遍历data中的所有属性,将其转换为响应式的。当这些属性变化时,视图会自动更新。
Vue 3使用Proxy实现响应式系统,相比Vue 2的Object.defineProperty有更好的性能和支持。
import { reactive, ref } from "vue";
// reactive用于对象
const state = reactive({
count: 0,
user: { name: "张三" }
});
// ref用于基本类型
const count = ref(0);
// 访问和修改
console.log(state.count);
state.count++;
console.log(count.value);
count.value++;
| API | 用途 |
|---|---|
| reactive | 创建响应式对象 |
| ref | 创建响应式引用 |
| computed | 创建计算属性 |
| watch | 监听响应式数据变化 |
| toRefs | 将响应式对象转换为ref |
import { reactive, ref, computed, watch, toRefs } from "vue";
export default {
setup() {
// reactive对象
const state = reactive({
name: "张三",
age: 25
});
// ref引用
const count = ref(0);
// 计算属性
const doubleCount = computed(() => count.value * 2);
// watch监听
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`);
});
// toRefs转换
const { name, age } = toRefs(state);
return {
state,
count,
doubleCount,
name,
age
};
}
};
计算属性是基于现有响应式数据计算得出的新数据,具有缓存特性。
export default {
data() {
return {
firstName: "张",
lastName: "三",
items: [
{ name: "苹果", price: 10 },
{ name: "香蕉", price: 5 },
{ name: "橙子", price: 8 }
]
};
},
computed: {
// 简单计算属性
fullName() {
return this.firstName + this.lastName;
},
// getter和setter
fullName: {
get() {
return this.firstName + " " + this.lastName;
},
set(newValue) {
const names = newValue.split(" ");
this.firstName = names[0];
this.lastName = names[1];
}
},
// 基于列表计算
totalPrice() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
};
计算属性会自动缓存结果,只有当依赖的响应式数据变化时才会重新计算。这避免了不必要的计算,提高性能。
watch选项用于监听数据变化并执行相应的操作。
export default {
data() {
return {
question: "",
answer: ""
};
},
watch: {
// 监听单个数据
question(newQuestion, oldQuestion) {
if (newQuestion.includes("?")) {
this.fetchAnswer();
}
},
// 监听对象
"user.name"(newName, oldName) {
console.log(`用户名从${oldName}改为${newName}`);
},
// 深度监听
options: {
handler(newOptions) {
console.log("选项变化:", newOptions);
},
deep: true
},
// 立即执行
name: {
handler(newName) {
console.log("立即执行:", newName);
},
immediate: true
}
},
methods: {
fetchAnswer() {
this.answer = "加载中...";
// 模拟API调用
setTimeout(() => {
this.answer = "这是答案";
}, 500);
}
}
};
v-if和v-show都用于条件渲染,但实现方式不同。
<!-- v-if:条件性地渲染元素,条件为false时不渲染 -->
<div v-if="show">内容1</div>
<div v-else-if="show2">内容2</div>
<div v-else>内容3</div>
<!-- v-show:始终渲染,通过display控制显示隐藏 -->
<div v-show="show">内容</div>
<!-- v-if vs v-show -->
<!-- v-if: 切换时销毁/重建元素,开销大,初始条件false不渲染 -->
<!-- v-show: 始终渲染,切换时只改display,开销小 -->
export default {
data() {
return {
type: "A",
show: true
};
},
methods: {
toggle() {
this.show = !this.show;
}
}
};
v-for用于渲染列表,支持遍历数组、对象、整数等。
<!-- 遍历数组 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
<!-- 遍历对象 -->
<div v-for="(value, key, index) in object" :key="key">
{{ index }}. {{ key }}: {{ value }}
</div>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
<!-- v-for with template -->
<template v-for="item in items">
<li>{{ item.name }}</li>
<li class="divider" role="presentation"></li>
</template>
<!-- 方法处理器 -->
<button @click="greet">打招呼</button>
<!-- 访问原生事件对象 -->
<button @click="handleClick">点击</button>
<button @click="handleClick($event)">点击并传参</button>
<!-- 多个事件 -->
<button @click="one($event), two($event)">多事件</button>
<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止冒泡</button>
<button @click.prevent="handleSubmit">阻止默认</button>
<button @click.stop.prevent="handleBoth">阻止冒泡和默认</button>
<button @click.once="handleOnce">只触发一次</button>
<button @click.self="handleSelf">仅自身触发</button>
<!-- 键盘事件 -->
<input @keyup.enter="submit" />
<input @keyup.enter.exact="submitExact" />
| 修饰符 | 说明 |
|---|---|
| .stop | 阻止事件冒泡 |
| .prevent | 阻止默认行为 |
| .capture | 使用事件捕获模式 |
| .self | 只有event.target是自身时才触发 |
| .once | 事件只触发一次 |
| .passive | 监听器使用被动模式 |
export default {
methods: {
greet(event) {
console.log("Hello!", event);
},
handleClick(event) {
console.log("点击了", event.target);
},
handleSubmit(event) {
console.log("表单提交");
},
submit(event) {
if (event.key === "Enter") {
console.log("提交表单");
}
}
}
};
v-model用于创建表单元素与数据的双向绑定。
<!-- 文本输入 -->
<input v-model="message">
<p>输入的内容:{{ message }}</p>
<!-- 多行文本 -->
<textarea v-model="message"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked ? "已勾选" : "未勾选" }}">
<!-- 多个复选框 -->
<input type="checkbox" value="苹果" v-model="fruits">
<input type="checkbox" value="香蕉" v-model="fruits">
<input type="checkbox" value="橙子" v-model="fruits">
<p>选择的水果:{{ fruits }}</p>
<!-- 单选按钮 -->
<input type="radio" value="A" v-model="choice">
<input type="radio" value="B" v-model="choice">
<p>选择:{{ choice }}</p>
<!-- 下拉选择 -->
<select v-model="selected">
<option value="">请选择</option>
<option value="a">选项A</option>
<option value="b">选项B</option>
</select>
| 修饰符 | 说明 |
|---|---|
| .lazy | 改为change事件同步 |
| .number | 自动转换为数字 |
| .trim | 自动去除首尾空格 |
<!-- lazy: 失去焦点时同步 -->
<input v-model.lazy="message">
<!-- number: 自动转数字 -->
<input v-model.number="age" type="number">
<!-- trim: 去除空格 -->
<input v-model.trim="username">
export default {
data() {
return {
message: "",
age: 0,
username: "",
checked: false,
fruits: [],
choice: "A",
selected: ""
};
}
};
组件是可复用的Vue实例。
// 全局注册
import { createApp } from "vue";
import MyComponent from "./MyComponent.vue";
const app = createApp({});
app.component("MyComponent", MyComponent);
// 局部注册(在另一个组件中)
import MyComponent from "./MyComponent.vue";
export default {
components: {
MyComponent
}
};
单文件组件(.vue文件)将模板、脚本和样式封装在一个文件中。
<!-- MyComponent.vue -->
<template>
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ message }}</p>
<button @click="handleClick">点击</button>
</div>
</template>
<script>
export default {
name: "MyComponent",
props: {
title: {
type: String,
default: "默认标题"
}
},
data() {
return {
message: "Hello Vue!"
};
},
methods: {
handleClick() {
this.$emit("click", "数据");
}
}
};
</script>
<style scoped>
.my-component {
padding: 20px;
background: #f0f0f0;
}
h2 {
color: #333;
}
</style>
<template>
<div>
<MyComponent
title="自定义标题"
@click="handleData"
/>
</div>
</template>
<script>
import MyComponent from "./MyComponent.vue";
export default {
components: {
MyComponent
},
methods: {
handleData(data) {
console.log("收到子组件数据:", data);
}
}
};
</script>
父子组件通信的基本方式。
// 父组件
<template>
<ChildComponent
:message="parentMessage"
:count="parentCount"
@update="handleUpdate"
/>
</template>
<script>
export default {
data() {
return {
parentMessage: "来自父组件",
parentCount: 0
};
},
methods: {
handleUpdate(newValue) {
this.parentCount = newValue;
}
}
};
</script>
// 子组件
<script>
export default {
name: "ChildComponent",
props: {
message: String,
count: {
type: Number,
required: true,
default: 0
}
},
emits: ["update"],
methods: {
sendToParent() {
this.$emit("update", this.count + 1);
}
}
};
</script>
跨层级组件通信。
// 祖先组件
export default {
provide() {
return {
message: "祖先提供的数据",
user: reactive({ name: "张三" }),
updateMessage: (newValue) => {
this.message = newValue;
}
};
}
};
// 后代组件
export default {
inject: ["message", "user", "updateMessage"],
mounted() {
console.log(this.message);
this.updateMessage("新消息");
}
};
| 方式 | 通信层级 | 说明 |
|---|---|---|
| Props/Emit | 父子 | 最常用 |
| $refs | 父访问子 | 直接调用子组件方法 |
| $parent/$children | 父子 | 不推荐 |
| Provide/Inject | 祖先与后代 | 跨层级 |
| EventBus | 任意 | 全局事件 |
| Vuex/Pinia | 任意 | 全局状态 |
# 安装Vue Router
npm install vue-router@4
// router/index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";
const routes = [
{ path: "/", name: "Home", component: Home },
{ path: "/about", name: "About", component: About },
{ path: "/user/:id", name: "User", component: User },
{ path: "/:pathMatch(.*)*", redirect: "/" } // 404重定向
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
createApp(App).use(router).mount("#app");
<!-- 声明式导航 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>
<!-- 编程式导航 -->
<button @click="$router.push('/about')">跳转</button>
<button @click="$router.replace('/about')">替换</button>
<button @click="$router.go(-1)">后退</button>
<button @click="$router.forward()">前进</button>
const router = createRouter({ ... });
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to: 目标路由
// from: 来源路由
// next: 放行函数
if (to.meta.requiresAuth && !isAuthenticated) {
next("/login");
} else {
next();
}
});
// 全局后置钩子
router.afterEach((to, from) => {
console.log("导航完成");
});
// 路由独享守卫
{
path: "/admin",
component: Admin,
beforeEnter: (to, from, next) => {
next();
}
}
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 不能访问this
next(vm => {
console.log(vm);
});
},
beforeRouteUpdate(to, from, next) {
// 可以访问this
next();
},
beforeRouteLeave(to, from, next) {
next();
}
};
Pinia是Vue 3推荐的状态管理库,API设计直观。
# 安装Pinia
npm install pinia
// stores/counter.js
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
name: "张三"
}),
getters: {
doubleCount: (state) => state.count * 2,
formattedName: (state) => `Hello, ${state.name}!`
},
actions: {
increment() {
this.count++;
},
incrementAsync() {
setTimeout(() => {
this.count++;
}, 1000);
}
}
});
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount("#app");
import { useCounterStore } from "@/stores/counter";
export default {
setup() {
const counterStore = useCounterStore();
// 访问state
console.log(counterStore.count);
// 访问getters
console.log(counterStore.doubleCount);
// 调用actions
const increment = () => {
counterStore.increment();
};
return {
count: counterStore.count,
doubleCount: counterStore.doubleCount,
increment
};
}
};
setup是Vue 3新增的组件选项,是组合式API的入口。
export default {
setup() {
// 定义响应式状态
const count = ref(0);
const user = reactive({
name: "张三",
age: 25
});
// 定义方法
const increment = () => {
count.value++;
};
// 定义计算属性
const doubleCount = computed(() => count.value * 2);
// 生命周期钩子
onMounted(() => {
console.log("组件挂载");
});
// 返回模板中使用的变量和方法
return {
count,
user,
increment,
doubleCount
};
}
};
| API | 说明 |
|---|---|
| ref | 创建响应式引用 |
| reactive | 创建响应式对象 |
| computed | 创建计算属性 |
| watch | 监听数据变化 |
| watchEffect | 立即执行的监听 |
| onMounted | 挂载完成钩子 |
| onUpdated | 更新完成钩子 |
| onUnmounted | 卸载完成钩子 |
| provide/inject | 依赖注入 |
| toRefs | 转换为ref |
import { ref, reactive, computed, watch, watchEffect, onMounted } from "vue";
export default {
setup() {
// ref - 基本类型
const count = ref(0);
// reactive - 对象
const state = reactive({
name: "张三",
age: 25
});
// computed
const adult = computed(() => state.age >= 18);
// watch
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`);
});
// watchEffect - 立即执行,自动追踪依赖
watchEffect(() => {
console.log("name变化:", state.name);
});
// 生命周期
onMounted(() => {
console.log("挂载完成");
});
return {
count,
state,
adult
};
}
};
| 钩子 | 说明 |
|---|---|
| setup | 组件创建前 |
| onBeforeMount | 挂载前 |
| onMounted | 挂载完成 |
| onBeforeUpdate | 更新前 |
| onUpdated | 更新完成 |
| onBeforeUnmount | 卸载前 |
| onUnmounted | 卸载完成 |
| onErrorCaptured | 错误捕获 |
| Vue 2 | Vue 3 |
|---|---|
| beforeCreate | setup |
| created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| errorCaptured | onErrorCaptured |
Vue提供了Transition组件用于动画效果。
<template>
<div>
<button @click="show = !show">切换</button>
<transition name="fade">
<div v-if="show">内容</div>
</transition>
</div>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<transition-group name="list" tag="ul">
<li v-for="(item, index) in items" :key="item.id">
{{ item.name }}
</li>
</transition-group>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-move {
transition: transform 0.5s;
}
</style>
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<div v-if="show">内容</div>
</transition>
<script>
export default {
methods: {
beforeEnter(el) {
el.style.opacity = 0;
},
enter(el, done) {
el.offsetHeight; // 触发重绘
el.style.transition = "opacity 0.5s";
el.style.opacity = 1;
done();
},
leave(el, done) {
el.style.transition = "opacity 0.5s";
el.style.opacity = 0;
done();
}
}
};
</script>
| 原则 | 说明 |
|---|---|
| 单一职责 | 每个组件只负责一个功能 |
| 合理拆分 | 根据功能模块拆分组件 |
| 命名规范 | 使用有意义的命名 |
| Props验证 | 声明Props类型和默认值 |
// 1. 使用v-show代替v-if频繁切换的元素
<div v-show="show">频繁切换</div>
// 2. 使用computed缓存
computed: {
filteredItems() {
return this.items.filter(item => item.active);
}
}
// 3. 使用v-memo优化列表
<div v-for="item in items" :key="item.id" v-memo="[item.selected]">
{{ item.content }}
</div>
// 4. 路由懒加载
{
path: "/admin",
component: () => import("../views/Admin.vue")
}
// 5. 大列表使用虚拟滚动
// vue-virtual-scroller库
// 推荐:使用组合式API组织代码
export default {
setup() {
// 相关状态和方法放在一起
const searchState = reactive({
keyword: "",
results: []
});
const search = async () => {
searchState.results = await fetchData(searchState.keyword);
};
// 另一个功能模块
const userState = reactive({
user: null,
isLoggedIn: false
});
const login = async () => { /* ... */ };
return {
...toRefs(searchState),
search,
...toRefs(userState),
login
};
}
};
本教程系统介绍了Vue.js开发的各个方面,从基础概念到高级应用。主要内容包括:Vue核心概念和响应式系统、模板语法和指令系统、计算属性和监听器、条件渲染和列表渲染、事件处理和表单操作、组件开发和通信、Vue Router路由配置、Pinia状态管理、组合式API、生命周期钩子、动画与过渡效果,以及性能优化技巧。Vue采用渐进式的设计理念,学习曲线平缓,文档完善,对中文开发者非常友好。掌握这些知识后,您可以独立开发功能完整的Vue应用。
本文档为Vue教程,适合Vue初学者和有一定前端基础的开发者学习参考。