Vue基础教程(129)组件和组合API之响应式API:别硬肝了!Vue3的响应式API让你代码爽到飞起,附实战代码直接CV

还记得刚开始学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 - 处理“复杂对象”的专车

当你要处理一个对象时,reactiveref更香:

<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
    }
  })
}

最佳实践和常见坑点

  1. ref vs reactive怎么选
    • 基本类型用ref
    • 对象和数组,简单用reactive,复杂用ref
    • 保持一致,别混用得太乱
  1. 解构会丢失响应式
const user = reactive({ name: '张三', age: 25 })

// 错误!解构会丢失响应式
const { name, age } = user

// 正确!用toRefs保持响应式
const { name, age } = toRefs(user)
  1. watch的深度监听
// 默认只监听表层
watch(someObject, callback)

// 深度监听
watch(someObject, callback, { deep: true })

总结

Vue3的组合式API和响应式系统,就像是给你的代码装上了涡轮增压:

  • ref:处理基本类型的万能工具
  • reactive:处理复杂对象的专业方案
  • computed:智能计算,性能优化
  • watch:数据变化侦探,副作用处理

最重要的是,组合式API让你能够把相关的逻辑组织在一起,而不是按照选项(data、methods等)来拆分。这就好比从“按物品类型整理房间”(书放书架、衣服放衣柜)变成了“按功能区整理”(工作区、休息区),用起来顺手多了!

现在就去重构你的Vue代码吧,让你的组件变得更加清晰、可维护!如果遇到问题,记得回来看看这些示例,CV大法永远为你准备着~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值