📘 Vue基础语法与响应式系统详解
作为后端开发者,您已经熟悉数据和状态管理的概念。Vue的响应式系统是其核心特色,让您只需关注数据变化,UI会自动更新。
🎯 Vue 3 的两种API风格
1. 选项式 API (Options API) - 适合初学者
<template>
<div>{{ count }}</div>
<button @click="increment">+1</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
watch: {
count(newVal, oldVal) {
console.log(`从 ${oldVal} 变为 ${newVal}`)
}
},
mounted() {
console.log('组件已挂载')
}
}
</script>
2. 组合式 API (Composition API) - 推荐!更灵活
<template>
<div>{{ count }}</div>
<button @click="increment">+1</button>
<p>双倍: {{ doubleCount }}</p>
</template>
<script setup>
// 导入响应式API
import { ref, computed, watch, onMounted } from 'vue'
// 1. 响应式变量
const count = ref(0) // 基本类型用 ref
const user = reactive({ // 对象类型用 reactive
name: '张三',
age: 25
})
// 2. 计算属性
const doubleCount = computed(() => count.value * 2)
// 3. 方法
const increment = () => {
count.value++ // 注意:ref 需要 .value
user.age = 30 // reactive 直接修改属性
}
// 4. 侦听器
watch(count, (newVal, oldVal) => {
console.log(`count变化: ${oldVal} -> ${newVal}`)
})
// 5. 生命周期
onMounted(() => {
console.log('组件挂载完成')
})
</script>
🔄 响应式核心:ref vs reactive
ref - 创建任何类型的响应式数据
import { ref } from 'vue'
// 基本类型
const count = ref(0)
console.log(count.value) // 0
// 对象类型也可以
const user = ref({ name: '张三' })
console.log(user.value.name) // 张三
// 数组
const list = ref([1, 2, 3])
console.log(list.value[0]) // 1
reactive - 创建响应式对象
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: '张三',
age: 25
},
list: [1, 2, 3]
})
// 直接访问,不需要 .value
state.count = 1
state.user.name = '李四'
state.list.push(4)
何时使用哪种?
| 场景 | 使用 | 原因 |
|---|---|---|
| 基本类型 (string, number, boolean) | ref |
reactive 不处理基本类型 |
| 对象类型 | 都可,reactive更简洁 |
reactive 修改时不需要 .value |
| 数组 | 都可,ref更常见 |
方便重新赋值整个数组 |
| 函数返回值 | ref |
可以返回单个响应式变量 |
📊 响应式API全览
import {
ref, // 创建响应式数据
reactive, // 创建响应式对象
computed, // 计算属性
watch, // 侦听单个数据
watchEffect, // 自动追踪依赖
readonly, // 创建只读对象
toRef, // 响应式对象转ref
toRefs, // 响应式对象解构
shallowRef, // 浅层ref
shallowReactive, // 浅层reactive
triggerRef // 手动触发更新
} from 'vue'
🎪 模板语法
1. 文本插值
<template>
<p>Message: {{ message }}</p>
<p>计算属性: {{ reversedMessage }}</p>
<p>方法调用: {{ formatDate(new Date()) }}</p>
</template>
2. 属性绑定
<template>
<!-- 动态属性 -->
<div :id="dynamicId"></div>
<img :src="imageSrc" :alt="altText">
<!-- 类名绑定 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<div :class="[isActive ? 'active' : '', 'base']"></div>
<!-- 样式绑定 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="[baseStyles, overridingStyles]"></div>
</template>
3. 条件渲染
<template>
<!-- v-if / v-else-if / v-else -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>其他</div>
<!-- v-show (只切换display) -->
<div v-show="isVisible">可见时显示</div>
</template>
4. 列表渲染
<template>
<!-- 数组渲染 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
</ul>
<!-- 对象渲染 -->
<ul>
<li v-for="(value, key, index) in user" :key="key">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>
<!-- 范围循环 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
</template>
5. 事件处理
<template>
<!-- 内联处理器 -->
<button @click="count++">增加</button>
<!-- 方法处理器 -->
<button @click="greet('Hello')">打招呼</button>
<!-- 事件修饰符 -->
<form @submit.prevent="onSubmit">
<button @click.stop="doThis">阻止冒泡</button>
<a @click.once="doThis">只触发一次</a>
</form>
<!-- 按键修饰符 -->
<input @keyup.enter="submit" />
<input @keyup.ctrl.enter="submit" />
</template>
6. 表单绑定
<template>
<!-- 文本 -->
<input v-model="text" placeholder="请输入" />
<p>输入的内容: {{ text }}</p>
<!-- 多行文本 -->
<textarea v-model="message"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<!-- 多个复选框 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<!-- 单选按钮 -->
<input type="radio" id="one" value="One" v-model="picked" />
<input type="radio" id="two" value="Two" v-model="picked" />
<!-- 选择框 -->
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
</select>
</template>
🔍 计算属性 vs 侦听器
计算属性 (computed) - 依赖缓存
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 自动追踪 firstName 和 lastName
const fullName = computed(() => {
console.log('计算属性执行')
return `${firstName.value}${lastName.value}`
})
// 只有当 firstName 或 lastName 变化时才会重新计算
侦听器 (watch) - 响应变化
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({ name: '张三', age: 25 })
// 1. 侦听单个 ref
watch(count, (newVal, oldVal) => {
console.log(`count变化: ${oldVal} -> ${newVal}`)
})
// 2. 侦听 reactive 对象的属性
watch(() => user.name, (newVal, oldVal) => {
console.log(`名字变化: ${oldVal} -> ${newVal}`)
})
// 3. 侦听多个源
watch([count, () => user.name], ([newCount, newName]) => {
console.log(`count: ${newCount}, name: ${newName}`)
})
// 4. watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(`count: ${count.value}, name: ${user.name}`)
})
🔄 响应式转换工具
toRef / toRefs - 保持响应式
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// 解构会丢失响应性
const { name, age } = state // ❌ 不是响应式
// 使用 toRef 保持响应性
const nameRef = toRef(state, 'name') // ✅ 响应式
// 使用 toRefs 解构所有属性
const { name, age } = toRefs(state) // ✅ 都是响应式
🎮 实战例子:计数器组件
<template>
<div class="counter">
<h2>计数器: {{ count }}</h2>
<p>双倍值: {{ doubleCount }}</p>
<p>平方值: {{ squareCount }}</p>
<div class="buttons">
<button @click="increment">增加 (+1)</button>
<button @click="decrement">减少 (-1)</button>
<button @click="reset">重置</button>
</div>
<div class="history">
<h3>历史记录</h3>
<ul>
<li v-for="(record, index) in history" :key="index">
{{ record.timestamp }}: {{ record.oldValue }} → {{ record.newValue }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
const history = ref([])
// 计算属性
const doubleCount = computed(() => count.value * 2)
const squareCount = computed(() => count.value * count.value)
// 方法
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
// 侦听器
watch(count, (newVal, oldVal) => {
console.log(`计数器变化: ${oldVal} -> ${newVal}`)
// 记录历史
history.value.unshift({
timestamp: new Date().toLocaleTimeString(),
oldValue: oldVal,
newValue: newVal
})
// 最多保留5条记录
if (history.value.length > 5) {
history.value.pop()
}
})
// 生命周期
onMounted(() => {
console.log('计数器组件已挂载')
})
</script>
<style scoped>
.counter {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
max-width: 400px;
margin: 0 auto;
}
.buttons {
display: flex;
gap: 10px;
margin: 20px 0;
}
.buttons button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #3498db;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
.buttons button:hover {
background-color: #2980b9;
}
.history {
margin-top: 20px;
text-align: left;
}
.history ul {
list-style: none;
padding: 0;
}
.history li {
padding: 5px 0;
border-bottom: 1px solid #eee;
}
</style>
🔧 响应式进阶技巧
1. 响应式数组处理
import { ref } from 'vue'
const list = ref([1, 2, 3])
// 以下操作是响应式的
list.value.push(4) // 末尾添加
list.value.pop() // 末尾移除
list.value.shift() // 开头移除
list.value.unshift(0) // 开头添加
list.value.splice(1, 1) // 删除/替换
list.value.sort() // 排序
list.value.reverse() // 反转
// 以下操作不是响应式的
list.value[0] = 100 // ❌ Vue 3 中现在也是响应式的
list.value.length = 0 // ❌ 仍然不是响应式的
2. 深度侦听
import { reactive, watch } from 'vue'
const obj = reactive({
nested: {
deep: {
value: 1
}
}
})
// 深度侦听
watch(
() => obj.nested.deep,
(newVal, oldVal) => {
console.log('深度变化', newVal, oldVal)
},
{ deep: true } // 深度侦听
)
// 立即执行
watch(
() => obj.nested.deep.value,
(newVal, oldVal) => {
console.log('值变化', newVal, oldVal)
},
{ immediate: true } // 立即执行一次
)
🎯 后端开发者特别关注点
与后端状态的对比
| Vue 响应式 | 后端状态管理 | 区别 |
|---|---|---|
ref/reactive |
数据库/缓存 | 自动UI更新 vs 持久化存储 |
computed |
SQL视图/计算字段 | 自动缓存依赖 vs 需要手动刷新 |
watch |
触发器/监听器 | 前端UI响应 vs 后端业务逻辑 |
最佳实践
// 1. 类型提示 (TypeScript)
import type { Ref } from 'vue'
interface User {
id: number
name: string
age: number
}
// 明确类型
const user: Ref<User> = ref({ id: 1, name: '张三', age: 25 })
// 2. 批量更新
import { nextTick } from 'vue'
const updateMultiple = async () => {
count.value++
user.value.age = 30
list.value.push(4)
// DOM更新后执行
await nextTick()
console.log('DOM已更新')
}
// 3. 防抖/节流的watch
import { debounce } from 'lodash-es'
watch(
() => searchInput.value,
debounce((newVal) => {
searchAPI(newVal)
}, 300)
)
📚 学习建议
-
动手实践 :在 Vue Playground (https://play.vuejs.org) 中尝试
-
理解原理:响应式是 Proxy 实现的
-
对比学习:对比 React 的 useState 和 Vue 的 ref
-
调试工具:安装 Vue DevTools 浏览器扩展
🎪 快速测验
<template>
<div>
<h1>响应式测验</h1>
<p>count: {{ count }}</p>
<p>user: {{ user }}</p>
<button @click="update">更新</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// 问题1: 以下哪个是正确的?
const count = ref(0)
const user = reactive({ name: '张三' })
const update = () => {
// 问题2: 如何正确更新?
count.value += 1 // ✅
// count += 1 // ❌
user.name = '李四' // ✅
// user.value.name = '李四' // ❌
}
</script>
掌握 Vue 响应式是掌握 Vue 的关键。作为后端开发者,您已经理解状态管理,现在只需将这种理解应用到前端UI交互中。记住:Vue 让您只关注数据的变化,UI会自动更新!