第4章:响应式系统与生命周期

第10节:响应式原理深入

4.10.1 响应式数据声明

Vue.js的响应式系统允许我们声明数据,当数据发生变化时,视图会自动更新。

data函数返回对象

在选项式API中,我们通过data函数返回一个对象来声明响应式数据:

export default {
  data() {
    return {
      message: 'Hello Vue!',
      count: 0,
      user: {
        name: '张三',
        age: 25
      },
      items: [1, 2, 3]
    }
  }
}

ref和reactive对比(组合式API预览)

在组合式API中,Vue 3提供了refreactive两个函数来声明响应式数据:

import { ref, reactive } from 'vue'

// 使用ref声明基本类型和对象类型
const count = ref(0) // 基本类型
const message = ref('Hello') // 字符串
const user = ref({ name: '张三' }) // 对象

// 使用reactive声明对象类型
const state = reactive({
  count: 0,
  message: 'Hello',
  user: { name: '张三' },
  items: [1, 2, 3]
})
特性 ref reactive
支持的类型 基本类型和对象类型 仅对象类型
访问方式 通过.value访问 直接访问
响应式代理 始终返回响应式代理 返回响应式代理
解构支持 不丢失响应性 直接解构会丢失响应性

4.10.2 响应式代理 vs 原始对象

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

Proxy API的优势

  1. 自动处理嵌套对象:Proxy可以自动为嵌套对象创建响应式代理
  2. 支持数组索引和长度变化:可以检测到数组索引赋值和长度修改
  3. 支持新增和删除属性:可以检测到对象属性的添加和删除
  4. 更好的性能:Proxy的性能比Object.defineProperty更好

响应式代理的使用

import { reactive } from 'vue'

const original = { count: 0 }
const proxy = reactive(original)

// 代理是响应式的
proxy.count++ // 触发更新

// 原始对象不是响应式的
original.count++ // 不会触发更新

// 代理和原始对象是不同的
console.log(proxy === original) // false

toRaw函数

可以使用toRaw函数获取响应式代理对应的原始对象:

import { reactive, toRaw } from 'vue'

const proxy = reactive({ count: 0 })
const original = toRaw(proxy)

console.log(proxy === original) // false
original.count++ // 不会触发更新

4.10.3 检测变化的注意事项

虽然Vue 3的响应式系统很强大,但仍有一些需要注意的地方:

对象属性添加/删除

在Vue 2中,直接添加或删除对象属性不会触发响应式更新,需要使用Vue.setVue.delete。在Vue 3中,使用reactive创建的对象可以直接添加或删除属性:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

// Vue 3中可以直接添加属性
state.name = '张三' // 触发更新

// 可以直接删除属性
delete state.count // 触发更新

数组索引修改

在Vue 2中,直接修改数组索引不会触发响应式更新,需要使用Vue.set或数组的变更方法。在Vue 3中,使用reactive创建的数组可以直接修改索引:

import { reactive } from 'vue'

const state = reactive({ items: [1, 2, 3] })

// Vue 3中可以直接修改数组索引
state.items[0] = 100 // 触发更新

数组长度修改

在Vue 2中,直接修改数组长度不会触发响应式更新。在Vue 3中,使用reactive创建的数组可以直接修改长度:

import { reactive } from 'vue'

const state = reactive({ items: [1, 2, 3] })

// Vue 3中可以直接修改数组长度
state.items.length = 2 // 触发更新

4.10.4 响应式API进阶

Vue.set / vm.$set

在Vue 2中,Vue.setvm.$set用于向响应式对象添加响应式属性:

// Vue 2语法
this.$set(this.user, 'age', 25)
Vue.set(this.items, 0, 100)

在Vue 3中,由于Proxy的特性,我们不再需要这些方法,可以直接添加或修改属性。

Vue.delete / vm.$delete

在Vue 2中,Vue.deletevm.$delete用于删除响应式对象的属性:

// Vue 2语法
this.$delete(this.user, 'age')
Vue.delete(this.items, 0)

在Vue 3中,我们可以直接使用delete操作符删除属性。

vm.$watch API使用

vm.$watch用于监听数据变化并执行回调函数:

export default {
  data() {
    return {
      count: 0,
      user: {
        name: '张三',
        age: 25
      }
    }
  },
  mounted() {
    // 监听基本类型
    this.$watch('count', (newVal, oldVal) => {
      console.log(`count从${oldVal}变为${newVal}`)
    })
    
    // 监听对象属性
    this.$watch('user.name', (newVal, oldVal) => {
      console.log(`用户名从${oldVal}变为${newVal}`)
    })
    
    // 深度监听对象
    this.$watch('user', (newVal, oldVal) => {
      console.log('用户信息发生变化')
    }, { deep: true })
    
    // 监听多个属性
    this.$watch(
      () => [this.count, this.user.name],
      ([newCount, newName], [oldCount, oldName]) => {
        console.log(`count: ${oldCount} -> ${newCount}`)
        console.log(`name: ${oldName} -> ${newName}`)
      }
    )
  }
}

在组合式API中,可以使用watch函数:

import { ref, reactive, watch } from 'vue'

const count = ref(0)
const user = reactive({ name: '张三' })

// 监听ref
watch(count, (newVal, oldVal) => {
  console.log(`count从${oldVal}变为${newVal}`)
})

// 监听reactive对象的属性
watch(() => user.name, (newVal, oldVal) => {
  console.log(`用户名从${oldVal}变为${newVal}`)
})

// 深度监听reactive对象
watch(user, (newVal, oldVal) => {
  console.log('用户信息发生变化')
}, { deep: true })

总结

Vue.js的响应式系统是其核心特性之一,通过理解响应式数据声明、响应式代理和原始对象的区别,以及检测变化的注意事项,你可以更好地使用Vue的响应式系统。

Vue 3使用Proxy API实现响应式系统,相比Vue 2的Object.defineProperty有很多优势,包括自动处理嵌套对象、支持数组索引和长度变化、支持新增和删除属性等。

在下一节中,我们将学习Vue组件的生命周期钩子。

« 上一篇 插槽(Slots)系统 下一篇 » 生命周期钩子详解