1. 项目概述:Vue基础与响应式系统入门
这个学习包面向的是刚接触Vue框架的前端开发者。Vue作为当前最流行的前端框架之一,其核心特性就是响应式数据绑定系统。我清楚地记得2016年第一次接触Vue 2.x时,那种"数据驱动视图"的编程模式给我带来的震撼。如今Vue 3已经成熟,但响应式原理依然是每个Vue开发者必须扎实掌握的基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Vue基础环境搭建
2.1 开发环境配置
现代Vue开发已经离不开Node.js环境。我建议使用nvm(Node Version Manager)来管理Node版本,这样可以避免全局安装带来的权限问题。实测在Mac/Linux下通过brew安装nvm最为稳定:
bash复制brew install nvm
nvm install 16 # Vue 3官方推荐Node 16+
对于Windows用户,可以直接从Node官网下载LTS版本安装包。安装完成后,通过以下命令验证环境:
bash复制node -v
npm -v
2.2 Vue CLI与Vite选择
目前Vue项目初始化有两个主流选择:
- 传统的Vue CLI(基于webpack)
- 新兴的Vite(基于原生ESM)
对于新手,我建议从Vue CLI开始,因为它的生态更成熟,遇到问题更容易找到解决方案:
bash复制npm install -g @vue/cli
vue create vue-basic-demo
选择默认的Vue 3预设即可。创建完成后,项目结构如下:
code复制├── public/ # 静态资源
├── src/ # 主代码目录
│ ├── assets/ # 模块资源
│ ├── components/ # 组件
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── package.json # 项目配置
└── vue.config.js # Vue专属配置
3. Vue核心概念解析
3.1 单文件组件(SFC)结构
Vue的单文件组件是.vue后缀的文件,典型结构如下:
vue复制<template>
<!-- HTML模板 -->
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
}
}
</script>
<style scoped>
/* 组件作用域CSS */
div {
color: red;
}
</style>
关键点:
<template>:必须有一个根元素<script>:可以使用Options API或Composition API<style scoped>:加上scoped属性可使CSS只作用于当前组件
3.2 响应式数据原理
Vue 3使用Proxy实现响应式,相比Vue 2的Object.defineProperty有显著改进:
javascript复制const data = { count: 0 }
// Vue 2的实现方式
Object.defineProperty(data, 'count', {
get() {
console.log('get count')
return val
},
set(newVal) {
console.log('set count', newVal)
val = newVal
}
})
// Vue 3的实现方式
const reactiveData = new Proxy(data, {
get(target, key) {
console.log('get', key)
return target[key]
},
set(target, key, value) {
console.log('set', key, value)
target[key] = value
return true
}
})
实际开发中,我们会使用Vue提供的reactive或ref:
javascript复制import { reactive, ref } from 'vue'
// 对象类型用reactive
const state = reactive({
count: 0
})
// 基本类型用ref
const count = ref(0)
console.log(count.value) // 访问ref值需要.value
4. 响应式系统实战
4.1 计算属性(computed)
计算属性是基于响应式依赖进行缓存的:
vue复制<script>
export default {
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
fullName() {
// 只有当firstName或lastName变化时才会重新计算
return this.firstName + this.lastName
}
}
}
</script>
在Composition API中的写法:
javascript复制import { computed, reactive } from 'vue'
const state = reactive({
firstName: '张',
lastName: '三'
})
const fullName = computed(() => {
return state.firstName + state.lastName
})
4.2 侦听器(watch)
watch用于在数据变化时执行副作用:
javascript复制import { watch, ref } from 'vue'
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`)
}, {
immediate: true // 立即执行一次
})
对于响应式对象,需要深度监听:
javascript复制const state = reactive({
user: {
name: '张三',
age: 20
}
})
watch(
() => state.user,
(newVal) => {
console.log('user变化了', newVal)
},
{ deep: true }
)
5. 组件通信机制
5.1 Props与Events
父传子用props,子传父用emit:
vue复制<!-- 父组件 -->
<template>
<ChildComponent
:title="parentTitle"
@update-title="handleUpdate"
/>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: { ChildComponent },
data() {
return {
parentTitle: '初始标题'
}
},
methods: {
handleUpdate(newTitle) {
this.parentTitle = newTitle
}
}
}
</script>
<!-- 子组件 -->
<script>
export default {
props: {
title: String
},
methods: {
updateTitle() {
this.$emit('update-title', '新标题')
}
}
}
</script>
5.2 Provide/Inject
跨层级组件通信:
javascript复制// 祖先组件
import { provide } from 'vue'
export default {
setup() {
provide('theme', 'dark')
}
}
// 后代组件
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme', 'light') // 默认值light
return { theme }
}
}
6. 路由管理(Vue Router)
6.1 基本配置
安装Vue Router:
bash复制npm install vue-router@4
路由配置示例:
javascript复制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
6.2 路由守卫
全局前置守卫:
javascript复制router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})
组件内守卫:
javascript复制export default {
beforeRouteEnter(to, from, next) {
// 不能访问this
next(vm => {
// 通过vm访问组件实例
})
},
beforeRouteUpdate(to, from) {
// 当前路由改变但组件被复用时调用
}
}
7. 状态管理(Vuex/Pinia)
7.1 Vuex基础
Vuex的核心概念:
javascript复制import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
7.2 Pinia使用
Pinia是Vue官方推荐的新一代状态管理库:
javascript复制import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
},
getters: {
doubleCount: (state) => state.count * 2
}
})
在组件中使用:
vue复制<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<div>{{ counter.count }}</div>
<button @click="counter.increment()">+</button>
</template>
8. 常见问题与解决方案
8.1 响应式失效问题
以下情况会导致响应式失效:
javascript复制const state = reactive({
user: {
name: '张三'
}
})
// 错误1:直接替换整个对象
state.user = { name: '李四' } // 响应式丢失
// 正确做法
Object.assign(state.user, { name: '李四' })
// 错误2:直接解构
const { user } = state // 响应式丢失
// 正确做法
import { toRefs } from 'vue'
const { user } = toRefs(state)
8.2 性能优化技巧
- v-for使用key:
vue复制<template v-for="item in list" :key="item.id">
<div>{{ item.name }}</div>
</template>
- 避免不必要的响应式:
javascript复制// 不需要响应式的数据
const staticData = Object.freeze({
PI: 3.14,
colors: ['red', 'green']
})
- 组件懒加载:
javascript复制const LazyComponent = defineAsyncComponent(() =>
import('./LazyComponent.vue')
)
9. 项目实战:Todo应用
完整实现一个响应式Todo应用:
vue复制<template>
<div>
<input v-model="newTodo" @keyup.enter="addTodo">
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">×</button>
</li>
</ul>
<div>
<button @click="filter = 'all'">全部</button>
<button @click="filter = 'active'">未完成</button>
<button @click="filter = 'completed'">已完成</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const newTodo = ref('')
const todos = ref([])
const filter = ref('all')
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active': return todos.value.filter(t => !t.done)
case 'completed': return todos.value.filter(t => t.done)
default: return todos.value
}
})
function addTodo() {
todos.value.push({
id: Date.now(),
text: newTodo.value,
done: false
})
newTodo.value = ''
}
function removeTodo(id) {
todos.value = todos.value.filter(t => t.id !== id)
}
</script>
<style>
.done {
text-decoration: line-through;
color: #999;
}
</style>
10. 进阶学习路线
掌握基础后,可以继续深入学习:
- 自定义指令:
javascript复制app.directive('focus', {
mounted(el) {
el.focus()
}
})
- Render函数与JSX:
javascript复制export default {
render() {
return h('div', this.$slots.default())
}
}
- 插件开发:
javascript复制const MyPlugin = {
install(app, options) {
app.config.globalProperties.$myMethod = () => {
console.log('插件方法')
}
}
}
app.use(MyPlugin)
- 服务端渲染(SSR):
bash复制npm create vue@latest -- --ssr
- TypeScript支持:
bash复制npm install -D @vue/cli-plugin-typescript
