Skip to content

Vue.js 响应式数据

什么是响应式数据?

响应式数据是 Vue.js 的核心特性之一。当数据发生变化时,视图会自动更新,无需手动操作 DOM。这种数据驱动的方式让开发变得更加简单和高效。

响应式原理

Vue 3 使用 Proxy 来实现响应式系统,而 Vue 2 使用的是 Object.defineProperty

Vue 3 响应式系统

javascript
import { reactive, ref } from 'vue'

// 使用 reactive 创建响应式对象
const state = reactive({
  count: 0,
  message: 'Hello Vue!'
})

// 使用 ref 创建响应式基本类型
const count = ref(0)

ref 和 reactive 的区别

ref

ref 用于创建响应式的基本类型数据(字符串、数字、布尔值等)。

vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
const message = ref('Hello')

function increment() {
  count.value++ // 注意:需要使用 .value 访问
}
</script>

<template>
  <div>
    <p>计数:{{ count }}</p> <!-- 模板中自动解包,不需要 .value -->
    <p>消息:{{ message }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

reactive

reactive 用于创建响应式对象。

vue
<script setup>
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: '张三',
    age: 25
  }
})

function updateUser() {
  state.user.name = '李四'
  state.count++
}
</script>

<template>
  <div>
    <p>计数:{{ state.count }}</p>
    <p>用户:{{ state.user.name }},年龄:{{ state.user.age }}</p>
    <button @click="updateUser">更新</button>
  </div>
</template>

响应式数据的使用场景

1. 表单输入

vue
<script setup>
import { ref } from 'vue'

const username = ref('')
const password = ref('')

function handleSubmit() {
  console.log('用户名:', username.value)
  console.log('密码:', password.value)
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button type="submit">提交</button>
  </form>
</template>

2. 列表数据

vue
<script setup>
import { reactive } from 'vue'

const todos = reactive([
  { id: 1, text: '学习 Vue', done: false },
  { id: 2, text: '写代码', done: false }
])

function toggleTodo(id) {
  const todo = todos.find(t => t.id === id)
  if (todo) {
    todo.done = !todo.done
  }
}

function addTodo(text) {
  todos.push({
    id: Date.now(),
    text,
    done: false
  })
}
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <input type="checkbox" :checked="todo.done" @change="toggleTodo(todo.id)" />
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
    </li>
  </ul>
</template>

<style scoped>
.done {
  text-decoration: line-through;
  color: #999;
}
</style>

toRef 和 toRefs

当需要将响应式对象的属性转换为独立的 ref 时,可以使用 toReftoRefs

vue
<script setup>
import { reactive, toRef, toRefs } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello'
})

// 转换单个属性
const count = toRef(state, 'count')

// 转换所有属性
const { message } = toRefs(state)

// 现在可以独立使用这些 ref
count.value++
message.value = 'Hi'
</script>

响应式数据的注意事项

1. ref 需要 .value

在 JavaScript 中访问 ref 的值时,必须使用 .value

javascript
const count = ref(0)
console.log(count.value) // 正确
console.log(count) // 错误:这是一个 ref 对象

2. reactive 的限制

reactive 只能用于对象类型,不能用于基本类型:

javascript
// ❌ 错误
const count = reactive(0)

// ✅ 正确
const count = ref(0)
// 或
const state = reactive({ count: 0 })

3. 解构会失去响应性

直接解构 reactive 对象会失去响应性:

javascript
const state = reactive({ count: 0 })

// ❌ 失去响应性
let { count } = state

// ✅ 保持响应性
const { count } = toRefs(state)

实战示例:购物车

vue
<script setup>
import { reactive, computed } from 'vue'

const cart = reactive({
  items: [
    { id: 1, name: '苹果', price: 5, quantity: 2 },
    { id: 2, name: '香蕉', price: 3, quantity: 3 }
  ]
})

const total = computed(() => {
  return cart.items.reduce((sum, item) => {
    return sum + item.price * item.quantity
  }, 0)
})

function updateQuantity(id, quantity) {
  const item = cart.items.find(i => i.id === id)
  if (item) {
    item.quantity = quantity
  }
}

function removeItem(id) {
  const index = cart.items.findIndex(i => i.id === id)
  if (index > -1) {
    cart.items.splice(index, 1)
  }
}
</script>

<template>
  <div class="cart">
    <h2>购物车</h2>
    <div v-for="item in cart.items" :key="item.id" class="cart-item">
      <span>{{ item.name }}</span>
      <span>¥{{ item.price }}</span>
      <input 
        type="number" 
        :value="item.quantity" 
        @input="updateQuantity(item.id, $event.target.value)"
        min="1"
      />
      <button @click="removeItem(item.id)">删除</button>
    </div>
    <div class="total">
      <strong>总计:¥{{ total }}</strong>
    </div>
  </div>
</template>

<style scoped>
.cart {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.cart-item input {
  width: 60px;
  padding: 5px;
}

.total {
  margin-top: 20px;
  text-align: right;
  font-size: 1.2em;
}
</style>

总结

  • Vue 3 使用 Proxy 实现响应式系统
  • ref 用于基本类型,reactive 用于对象
  • 在 JavaScript 中访问 ref 需要使用 .value
  • 使用 toRefs 可以保持解构后的响应性
  • 响应式数据让视图自动更新,简化了开发流程

下一步

接下来学习 计算属性和侦听器,了解如何基于响应式数据进行派生计算和监听变化。