ref

在组合式API中,推荐使用ref()函数来声明响应式状态:

import { ref } from 'vue'
const count = ref(0)

ref函数中接收的参数为需要包装为响应式对象的数据,函数会将数据包装在value属性中返回对应的响应式对象。修改该属性,将会触发页面更新。

const increment = () => count.value++

template中使用ref响应式对象并不需要使用value属性,模板会自动进行解包。

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

深层响应性

ref可以持有任何类型的值,包括深层嵌套的对象、数组或者Map之类的。ref会使它的值具有深层响应性,所以在修改内部数据时,这个变化仍旧能被检测到:

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

const count = ref({
data: 0
})

const increment = () => count.value.data++
</script>

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

使用shallowRef函数可以放弃深层响应性,这种情况下对于浅层ref,只有.value的变动响应会被捕获。

reactive

与将数据包装在内部响应式对象的ref不同,reactive将会使对象本身具有响应性。

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

const countObj = reactive({count: 0})
const incrementReactive = () => countObj.count++
</script>

<template>
<button @click="incrementReactive">CountObj is: {{ countObj.count }}</button>
</template>

reactive创建的响应式对象不需要使用.value属性来访问,而是直接访问本身属性即可。与ref一样的是reactive也会深层转换对象为响应式对象,所以对应的也有shallowReactive函数来放弃深层响应性。

reactive的局限性

  1. 有限的值类型,只能用于对象类型的数据,不能持有stringnumber或者boolean之类的基本类型数据。
  2. 不能替换整个对象,Vue的响应式是通过属性访问实现的,所以必须始终保持对响应式对象的同一引用,一旦修改整个对象,那么引用就断开,页面就不会更新了。
let state = reactive({count: 0})

state = reactive({ count: 1}) //响应性连接已经丢开
  1. 对解构操作不友好,当对响应式对象进行解构时,解构后属性已经丢失了响应性连接:
const state = reactive({count: 0})

//已经丢失了响应性连接
let { count } = state
//修改count不会对state产生影响
count++

由于这些限制,建议使用ref作为声明响应式的主要API

模板中的解包注意事项

在模板渲染上下文中,只有顶级的ref属性才会被解包。

//count和object是顶级ref属性,object.id不是
const count = ref(0)
const object = {id: ref(1)}

//会正常工作
{{ count+1 }}

//不会正常工作
{{ object.id+1 }}

渲染的结果将会是[object object]1,因为在计算表达式时object.id没有被解包,仍然是一个ref对象。

DOM更新时机

当修改了响应式状态,DOM会被自动更新。DOM的更新是异步的,Vue会在”next tick”更新周期中缓冲所有状态的修改,来确保不管进行了多少次状态修改,每个组件只会被更新一次。

要等待DOM更新完再执行额外的代码,可以使用nextTick()全局API:

import { nextTick } from 'vue'

async function increment() {
count.value++
await nextTick()

//现在DOM已经更新结束了
}