还记得刚开始学Vue2的时候吗?那个长长的data函数,那一堆堆的methods,组件稍微复杂点就得上下翻飞找逻辑。每次改个功能,都像是在玩“大家来找茬”——在几百行代码里寻找那个该死的变量在哪里定义、在哪里使用。
别急,Vue3的组合式API和响应式系统就是来拯救你的!这就像是给你配了个私人助理,让你的代码从“杂货铺”变成“精品店”。
为什么我们需要响应式?先来个灵魂拷问
想象一下这个场景:你在写一个计数器。点击按钮,数字+1。用最原始的JavaScript怎么写?
let count = 0;
const button = document.getElementById('btn');
const display = document.getElementById('display');
button.addEventListener('click', () => {
count++;
display.textContent = count;
});
发现问题了吗?每次count变化,你得手动去更新DOM!这就好比你每次喝水都得自己动手倒,累不累啊?
Vue的响应式系统就像是给你配了个贴心管家:你只需要关心数据(count)的变化,DOM更新这种脏活累活,管家自动帮你搞定!
Vue3响应式API全家桶,到底有哪些宝贝?
1. ref - 你的“万能小助手”
ref是Vue3里最常用的响应式API,它能搞定基本类型(数字、字符串、布尔值)和引用类型(对象、数组)。
<template>
<div>
<h2>我是个计数器</h2>
<p>当前计数:{{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 声明响应式数据
const count = ref(0)
// 方法
const increment = () => {
count.value++ // 注意这里要.value
}
const decrement = () => {
count.value--
}
</script>
划重点:在JavaScript里操作ref时,记得用.value!不过在模板里不用,Vue自动帮你解包了。
ref原理小揭秘:它其实是个“包装盒”,把你的数据装进去,然后通过getter和setter来追踪变化。当数据变化时,Vue就知道该更新哪些组件了。
2. reactive - 处理“复杂对象”的专车
当你要处理一个对象时,reactive比ref更香:
<template>
<div>
<h2>用户信息</h2>
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<p>城市:{{ user.address.city }}</p>
<button @click="updateUser">更新信息</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 用reactive创建响应式对象
const user = reactive({
name: '张三',
age: 25,
address: {
city: '北京',
district: '朝阳区'
}
})
const updateUser = () => {
user.age++
user.address.city = '上海' // 深度响应,嵌套对象也会触发更新
}
</script>
reactive的好处:不用.value,直接操作属性,代码更简洁!
3. computed - 你的“智能计算器”
有些数据需要根据其他数据计算得来,这时候computed就派上用场了:
<template>
<div>
<h2>购物车</h2>
<ul>
<li v-for="item in cart" :key="item.id">
{{ item.name }} - ¥{{ item.price }} × {{ item.quantity }}
</li>
</ul>
<p>总价:{{ totalPrice }}元</p>
<p>折扣价:{{ discountedPrice }}元</p>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const cart = reactive([
{ id: 1, name: 'iPhone', price: 5999, quantity: 1 },
{ id: 2, name: 'AirPods', price: 1299, quantity: 2 }
])
// 计算总价 - 当cart变化时自动重新计算
const totalPrice = computed(() => {
return cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
// 打8折的价格
const discountedPrice = computed(() => {
return totalPrice.value * 0.8 // 注意这里用了.totalPrice.value
})
</script>
computed最大的好处是:它会缓存计算结果,只有依赖的数据变化时才会重新计算,性能杠杠的!
4. watch - 你的“数据侦探”
想要监听数据变化并执行一些操作?watch就是你的侦探:
<template>
<div>
<h2>搜索功能</h2>
<input v-model="searchKeyword" placeholder="输入搜索关键词">
<p>搜索结果:{{ searchResults }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const searchKeyword = ref('')
const searchResults = ref('')
// 监听searchKeyword的变化
watch(searchKeyword, (newValue, oldValue) => {
console.log(`搜索词从"${oldValue}"变为"${newValue}"`)
// 模拟搜索API调用
if (newValue) {
searchResults.value = `正在搜索"${newValue}"...`
// 这里实际会调用API
} else {
searchResults.value = ''
}
})
// 立即执行版本
watch(searchKeyword, (newValue) => {
// 处理逻辑
}, { immediate: true })
</script>
实战:组合式API实现购物车功能
来,咱们把上面的知识点串起来,做个完整的购物车:
<template>
<div class="cart">
<h2>我的购物车</h2>
<div v-if="cartItems.length === 0" class="empty-cart">
购物车空空如也,快去逛逛吧~
</div>
<div v-else>
<div v-for="item in cartItems" :key="item.id" class="cart-item">
<span class="name">{{ item.name }}</span>
<span class="price">¥{{ item.price }}</span>
<div class="quantity-controls">
<button @click="decreaseQuantity(item)">-</button>
<span class="quantity">{{ item.quantity }}</span>
<button @click="increaseQuantity(item)">+</button>
</div>
<span class="item-total">¥{{ item.price * item.quantity }}</span>
<button @click="removeItem(item.id)" class="remove-btn">删除</button>
</div>
<div class="cart-summary">
<p>商品总数:{{ totalItems }}件</p>
<p>商品总价:{{ totalPrice }}元</p>
<p v-if="discount > 0">折扣:-{{ discount }}元</p>
<p class="final-price">实付:{{ finalPrice }}元</p>
<button @click="checkout" class="checkout-btn">去结算</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
// 响应式数据
const cartItems = ref([
{ id: 1, name: 'Vue3实战指南', price: 89, quantity: 1 },
{ id: 2, name: 'TypeScript入门', price: 69, quantity: 2 },
{ id: 3, name: '前端工程化', price: 79, quantity: 1 }
])
// 计算属性
const totalItems = computed(() => {
return cartItems.value.reduce((sum, item) => sum + item.quantity, 0)
})
const totalPrice = computed(() => {
return cartItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
const discount = computed(() => {
// 满200减30
return totalPrice.value >= 200 ? 30 : 0
})
const finalPrice = computed(() => {
return totalPrice.value - discount.value
})
// 方法
const increaseQuantity = (item) => {
item.quantity++
}
const decreaseQuantity = (item) => {
if (item.quantity > 1) {
item.quantity--
}
}
const removeItem = (itemId) => {
cartItems.value = cartItems.value.filter(item => item.id !== itemId)
}
const checkout = () => {
alert(`结算成功!共支付${finalPrice.value}元`)
cartItems.value = []
}
// 监听器 - 当购物车变化时保存到本地存储
watch(cartItems, (newCart) => {
localStorage.setItem('shoppingCart', JSON.stringify(newCart))
}, { deep: true })
</script>
<style scoped>
.cart {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.cart-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #eee;
}
.quantity-controls {
display: flex;
align-items: center;
gap: 10px;
}
.quantity-controls button {
width: 30px;
height: 30px;
border: 1px solid #ddd;
background: #f5f5f5;
cursor: pointer;
}
.remove-btn {
background: #ff4757;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.cart-summary {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.final-price {
font-size: 1.2em;
font-weight: bold;
color: #e74c3c;
}
.checkout-btn {
width: 100%;
padding: 12px;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
font-size: 1.1em;
cursor: pointer;
margin-top: 10px;
}
.checkout-btn:hover {
background: #2980b9;
}
.empty-cart {
text-align: center;
padding: 40px;
color: #7f8c8d;
}
</style>
响应式原理小揭秘(知其所以然)
Vue3的响应式用的是Proxy,这比Vue2的Object.defineProperty牛在哪里?
Vue2的局限:
- 无法检测对象属性的添加和删除
- 对数组的变化检测需要hack
- 需要递归遍历整个对象
Vue3的Proxy优势:
- 真正的“全量拦截”,什么操作都逃不过它的法眼
- 性能更好,按需响应
- 支持Map、Set等新数据类型
// 简化的响应式原理
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(`读取了${key}属性`)
return target[key]
},
set(target, key, value) {
console.log(`设置了${key}属性为${value}`)
target[key] = value
// 触发更新...
return true
}
})
}
最佳实践和常见坑点
- ref vs reactive怎么选?
-
- 基本类型用ref
- 对象和数组,简单用reactive,复杂用ref
- 保持一致,别混用得太乱
- 解构会丢失响应式!
const user = reactive({ name: '张三', age: 25 })
// 错误!解构会丢失响应式
const { name, age } = user
// 正确!用toRefs保持响应式
const { name, age } = toRefs(user)
- watch的深度监听
// 默认只监听表层
watch(someObject, callback)
// 深度监听
watch(someObject, callback, { deep: true })
总结
Vue3的组合式API和响应式系统,就像是给你的代码装上了涡轮增压:
- ref:处理基本类型的万能工具
- reactive:处理复杂对象的专业方案
- computed:智能计算,性能优化
- watch:数据变化侦探,副作用处理
最重要的是,组合式API让你能够把相关的逻辑组织在一起,而不是按照选项(data、methods等)来拆分。这就好比从“按物品类型整理房间”(书放书架、衣服放衣柜)变成了“按功能区整理”(工作区、休息区),用起来顺手多了!
现在就去重构你的Vue代码吧,让你的组件变得更加清晰、可维护!如果遇到问题,记得回来看看这些示例,CV大法永远为你准备着~

被折叠的 条评论
为什么被折叠?



