Vue.js 教程
Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。它被设计为可以自底向上逐层应用,核心库只关注视图层,易于上手,便于与第三方库或既有项目整合。
什么是 Vue.js?
Vue.js 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Vue.js 的特点
- 渐进式框架:可以逐步采用,不需要重写整个项目
- 响应式数据绑定:数据变化时视图自动更新
- 组件化开发:可复用的组件系统
- 虚拟 DOM:高效的渲染性能
- 单文件组件:.vue 文件包含模板、脚本和样式
快速开始
环境要求
- Node.js 12.0 或更高版本
- npm 或 yarn 包管理器
创建 Vue 应用
使用 Vue CLI 创建新项目:
npm install -g @vue/cli
vue create my-project
cd my-project
npm run serve
使用 Vite 创建项目(推荐)
npm create vue@latest my-project
cd my-project
npm install
npm run dev
CDN 引入
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
基础语法
第一个 Vue 应用
<!DOCTYPE html>
<html>
<head>
<title>Vue 3 App</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<h1>{{ message }}</h1>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: "Hello Vue!",
};
},
}).mount("#app");
</script>
</body>
</html>
模板语法
<template>
<div>
<!-- 文本插值 -->
<p>{{ message }}</p>
<!-- 原始 HTML -->
<div v-html="rawHtml"></div>
<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
<!-- 条件渲染 -->
<p v-if="seen">现在你看到我了</p>
<!-- 列表渲染 -->
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
<!-- 事件监听 -->
<button v-on:click="doThis">点击我</button>
<button @click="doThis">点击我</button>
</div>
</template>
组件系统
单文件组件
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count++;
},
},
};
</script>
<style scoped>
.hello {
color: #42b983;
}
</style>
组件注册
// 全局注册
import { createApp } from "vue";
import MyComponent from "./MyComponent.vue";
const app = createApp({});
app.component("my-component", MyComponent);
// 局部注册
export default {
components: {
MyComponent,
},
};
组件通信
父子组件通信
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage" @update="handleUpdate" />
</div>
</template>
<script>
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
ChildComponent,
},
data() {
return {
parentMessage: "Hello from parent",
};
},
methods: {
handleUpdate(newMessage) {
this.parentMessage = newMessage;
},
},
};
</script>
<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
<button @click="updateParent">Update Parent</button>
</div>
</template>
<script>
export default {
props: {
message: String,
},
methods: {
updateParent() {
this.$emit("update", "Updated from child");
},
},
};
</script>
跨组件通信
// 使用 provide/inject
// 祖先组件
export default {
provide() {
return {
theme: 'dark'
}
}
}
// 后代组件
export default {
inject: ['theme'],
created() {
console.log(this.theme) // 'dark'
}
}
响应式数据
响应式基础
import { ref, reactive, computed, watch } from "vue";
export default {
setup() {
// ref 用于基本类型
const count = ref(0);
// reactive 用于对象
const state = reactive({
name: "Vue",
version: "3.0",
});
// 计算属性
const doubleCount = computed(() => count.value * 2);
// 监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`);
});
return {
count,
state,
doubleCount,
};
},
};
生命周期钩子
import { onMounted, onUpdated, onUnmounted } from "vue";
export default {
setup() {
onMounted(() => {
console.log("组件已挂载");
});
onUpdated(() => {
console.log("组件已更新");
});
onUnmounted(() => {
console.log("组件已卸载");
});
},
};
路由
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,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
<!-- App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view />
</div>
</template>
路由守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 检查用户是否已登录
if (isAuthenticated()) {
next();
} else {
next("/login");
}
} else {
next();
}
});
状态管理
Vuex 基础
npm install vuex@next
// store/index.js
import { createStore } from "vuex";
export default createStore({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
},
},
actions: {
increment({ commit }) {
commit("increment");
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
});
Pinia(推荐)
npm install pinia
// stores/counter.js
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
});
表单处理
双向绑定
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" placeholder="姓名" />
<input v-model="form.email" placeholder="邮箱" />
<button type="submit">提交</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
name: "",
email: "",
},
};
},
methods: {
handleSubmit() {
console.log("Form submitted:", this.form);
},
},
};
</script>
表单验证
<template>
<form @submit.prevent="handleSubmit">
<div>
<input
v-model="form.email"
:class="{ error: errors.email }"
placeholder="邮箱"
/>
<span v-if="errors.email" class="error-message">
{{ errors.email }}
</span>
</div>
<button type="submit" :disabled="!isValid">提交</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: "",
},
errors: {},
};
},
computed: {
isValid() {
return Object.keys(this.errors).length === 0;
},
},
watch: {
"form.email"(value) {
this.validateEmail(value);
},
},
methods: {
validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
this.errors.email = "请输入有效的邮箱地址";
} else {
delete this.errors.email;
}
},
handleSubmit() {
if (this.isValid) {
console.log("Form submitted:", this.form);
}
},
},
};
</script>
HTTP 请求
Axios 集成
npm install axios
// api/index.js
import axios from "axios";
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
});
export default api;
<template>
<div>
<ul v-if="users.length">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
<p v-else>加载中...</p>
</div>
</template>
<script>
import api from "@/api";
export default {
data() {
return {
users: [],
};
},
async mounted() {
try {
const response = await api.get("/users");
this.users = response.data;
} catch (error) {
console.error("获取用户列表失败:", error);
}
},
};
</script>
测试
Vue Test Utils
npm install --save-dev @vue/test-utils jest
import { mount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";
describe("HelloWorld.vue", () => {
test("renders props.msg when passed", () => {
const msg = "new message";
const wrapper = mount(HelloWorld, {
props: { msg },
});
expect(wrapper.text()).toMatch(msg);
});
});
部署
构建生产版本
npm run build
部署到 Vercel
npm install -g vercel
vercel
部署到 Netlify
npm install -g netlify-cli
netlify deploy --prod --dir=dist
最佳实践
- 组件设计:保持组件单一职责
- 状态管理:合理使用 Vuex 或 Pinia
- 性能优化:使用 v-memo、keep-alive
- 代码组织:按功能模块组织代码
- 类型安全:使用 TypeScript
学习资源
常见问题
Q: Vue 2 和 Vue 3 有什么区别?
A: Vue 3 引入了 Composition API、更好的 TypeScript 支持、更小的包体积和更好的性能。
Q: 什么时候使用 Vuex 还是 Pinia?
A: 新项目推荐使用 Pinia,它更简单、类型安全,并且与 Vue 3 的 Composition API 配合更好。
Q: 如何优化 Vue 应用性能?
A: 使用 v-memo、keep-alive、懒加载、代码分割等技术来优化性能。