Skip to content

响应式状态

状态:指的是页面渲染过程中所需的数据。

响应式状态:指的是状态的变化,会导致页面自动的重新渲染,体现了“数据驱动视图”的编程范式。在 vue3 中声明响应式状态的核心 API 有两个:

  1. reactive() 函数
  2. ref() 函数

reactive()

语法格式

reactive() 用来将普通对象包装成响应式对象,从而使得响应式对象中数据的变化自动触发模板的更新渲染

语法格式如下:

js
import { reactive } from 'vue'
// 使用 reactive 定义响应式的数据对象
const state = reactive({ count: 0 })

在模板中使用:

html
<p>count 的值是:{{ state.count }}</p>
<button @click="state.count++">+1</button>

复杂对象

reactive() 支持复杂对象(深层嵌套)的响应式更新。

示例代码如下:

js
// 复杂对象
const obj = reactive({
  a: {
    b: {
      c: 1
    }
  }
})

模板中的代码:

html
<p>a.b.c 的值是:{{obj.a.b.c}}</p>
<button @click="obj.a.b.c++">+1</button>

DOM 更新的时机

当修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:

js
// 从 vue 中按需导入 nextTick 函数
import { reactive, nextTick } from 'vue'

// 定义响应式的数据对象
let state = reactive({ count: 0 })

const add = async () => {
  state.count++

  // 在 nextTick() 之后,才能访问到更新后的 DOM
  await nextTick()
  console.log(document.querySelector('#counter').textContent)
}

reactive() 的局限性

  1. 有限的值类型

    reactive() 只能用于对象类型(对象、数组、Map、Set)。不能作用于 string、number 或 boolean 这些值类型的数据。

    js
    let state = reactive({ count: 0 })
    
    // 下面这种用法是错误的
    // 因为 reactive() 不支持值类型:
    const name = reactive('zs')
  2. 不能替换整个对象

    直接替换整个响应式对象,会导致响应性的丢失。

    js
    let state = reactive({ count: 0 })
    
    // 上面的 ({ count: 0 }) 引用将不再被追踪
    // (响应性连接已丢失!)
    state = reactive({ count: 1 })
  3. 对解构操作不友好

    当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接。

    js
    const state = reactive({ count: 0 })
    
    // 当解构时,count 已经与 state.count 断开连接
    let { count } = state
    // 不会影响原始的 state
    count++
    
    // 该函数接收到的是一个普通的数字
    // 并且无法追踪 state.count 的变化
    // 我们必须传入整个对象以保持响应性
    callSomeFunction(state.count)

TIP

由于这些限制,Vue3 官方建议使用 ref() 作为声明响应式状态的主要 API。

ref()

语法格式

ref() 支持把值类型的数据包装为响应式的数据对象ref() 接收参数值,并将其包裹在一个带有 .value 属性的 ref 对象中返回。示例代码如下:

js
// 从 vue 中导入 ref 函数
import { ref } from 'vue'

const app = createApp({
  setup() {
    // 定义响应式的数据,命名为 count
    const count = ref(0)

    // count 是一个带有 .value 属性的对象
    console.log(count) // { value: 0 }
    // ref.value 才是真正的值
    console.log(count.value) // 0

    // 按钮的点击事件处理函数
    // 注意:在 js 中访问 ref 的值时,必须使用 .value
    const add = () => (count.value += 2)

    return {
      count,
      add
    }
  }
})

在模板中使用 ref 时,不需要附加 .value,因为模板中的 ref 会自动解包:

html
<div id="app">
  <!-- 注意:在模板中访问 ref 的值时,会自动解包,不必使用 .value -->
  <h3>count 的值是:{{ count }}</h3>
  <button @click="count++">+1</button>
  <button @click="add">+2</button>
</div>

复杂对象

ref() 支持复杂对象。传递给 ref() 的复杂对象会在内部通过 reactive() 转为响应式数据对象。只不过需要使用 .value 才能访问到响应式对象。

示例代码如下:

js
// 使用 ref() 把对象转为响应式数据
const user = ref({ age: 0 })
console.log(user) // { value: { age: 0 } }
console.log(user.value.age) // 0

在模板中使用:

html
<h3>姓名:{{ user.age }}</h3>
<button @click="user.age++">age+1</button>

正确使用 ref() 的核心口诀

  1. 在 js 中必须使用 .value 访问 ref 的值;
  2. 在模板中会自动解包,不必使用 .value;
  3. Vue3 官方建议使用 ref() 作为声明响应式状态的主要 API,因为它同时支持值类型对象类型

天不生夫子,万古长如夜