vue3 响应式 API:ref() 和 reactive()

在 Vue 3 中,响应式系统是其核心特性之一,它使得数据的变化能够自动触发视图的更新。

官方文档:
响应式 API:核心

要更好地了解响应式 API,推荐阅读官方指南中的章节:
响应式基础 (with the API preference set to Composition API)
深入响应式系统

reactive()

基本概念

  • 作用 :用于创建一个响应式的对象。如果对这个对象的属性进行修改,会自动触发视图的更新。
  • 可接收的数据类型 :只能定义对象类型的响应式数据。使用reactive()定义基本类型会报错,要用ref
  • 语法let 响应式对象= reactive(源对象)
  • 返回值 :一个Proxy的实例对象,简称:响应式对象
  • 类型
javascript 复制代码
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

使用reactive()创建对象类型的响应式数据:

javascript 复制代码
<template>
  <div>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
  </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';

const person = reactive({
  name: '张三',
  age: 36
})
console.log(person)

// 报错:类型"number"的参数不能赋给类型"object"的参数。
let count = reactive(0)

</script>

控制台打印:

直接修改响应式对象的属性值,会触发依赖这些属性的组件重新渲染:

javascript 复制代码
// 直接修改,立即响应
person.name = '李四'
person.age = 24

// 使用方法,调用方法后再响应
const changePerson = () => {
  person.name = '李四'
  person.age = 24
}

reactive()深层响应性

  • 对象的嵌套属性也具有响应式
    • 响应式转换是"深层"的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
html 复制代码
<template>
  <div>
    <p>b: {{ obj.a.b }}</p>
    <p>d: {{ obj.a.c.d }}</p>
    <button @click="changeB">修改b</button>
    <button @click="changeD">修改d</button>
  </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';

const obj = reactive({
  a: {
    b: 10,
    c: {
      d: 20
    }
  },
});
const changeB = () => {
  obj.a.b++
}
const changeD = () => {
  obj.a.c.d++
}
</script>

在这个例子中,修改嵌套对象的属性也会触发响应式更新。

不管数据嵌套的有多深,reactive()一定会把数据变成响应式的。

  • 数组的响应式
    • 对响应式数组进行添加、删除、修改等操作都会触发依赖这个数组的组件重新渲染。
html 复制代码
<template>
  <div>
    <ul>
      <li v-for="item in personArr" :key="item.id">
        姓名:{{ item.name }} , 年龄:{{ item.age }} 岁
      </li>
    </ul>
    <button @click="addPerson">添加</button>
  </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';

const personArr = reactive([
  {id: 1, name: '张三', age: 17 },
  {id: 2, name: '李四', age: 18 },
  {id: 3, name: '王二', age: 16 },
]);

console.log(personArr); 
const addPerson = () => {
  personArr.push({ id: 4, name: '张麻子', age: 16 });
};

</script>

控制台打印:

  • 直接赋值整个响应式对象不会触发响应式更新
javascript 复制代码
let person = reactive({
  name: '张三',
  age: 36,
});

// 这样不会触发响应式更新
const changePerson = () => {
  person = {
    name: 'john',
    age: 30
  }
}

// 使用Object.assign()等方法来更新属性
const changePerson = () => {
  Object.assign(person, {
    name: '李四',
    age: 24,
  });
}
  • 响应式对象的属性必须在创建时存在
javascript 复制代码
const person = reactive({
  name: '王二',
});

// 报错:类型"{ name: string; }"上不存在属性"age"。
person.age = 30;  

ref()

基本概念

ref()接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

  • 作用:定义响应式的变量。
  • 可接收的数据类型 :基本类型、对象类型的响应式数据。若ref接收的是对象类型,内部其实也是调用了reactive函数。
  • 语法let xxx = ref(初始值)。
  • 返回值 :一个RefImpl的实例对象,简称ref对象或refref对象的value属性是响应式的。
  • 类型
javascript 复制代码
function ref<T>(value: T): Ref<UnwrapRef<T>>

interface Ref<T> {
  value: T
}

ref 对象是可更改的,也就是说可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪。

使用 ref() 创建基本类型的响应式数据

html 复制代码
<template>
  <div>
    <p>count: {{ count  }}</p>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'

// 在组合式 API 中,推荐使用 ref() 函数来声明响应式状态
const count = ref(0)
consoe.log(count)    // count 是一个RefImpl的实例对象
console.log('count.value:', count.value) // count.value:0

count.value = 1
console.log('count.value:', count.value) // count.value:1
</script>

控制台打印:

注意:

  • JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用{``{xxx}}
  • 对于const count = ref(0)来说,count不是响应式的,count.value是响应式的。

ref()深层响应性

ref()包裹的是一个对象时,对这个对象的属性进行修改也会触发响应式更新。

html 复制代码
<template>
  <div>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
    <button @click="changePerson">修改信息</button>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';

const person = ref({
  name: '张三',
  age: 36
})
console.log(person)

const changePerson = () => {
  person.name = '李四'
  person.age = 24
}
</script>

使用ref()定义一个对象类型的响应式数据,页面是正常展示数据的:

数据详细信息解析:

  • RefImpl {... }:这表示这是一个由ref函数创建的响应式对象的内部实现结构展示。
  • __v_isShallow:表示是否是浅层响应式,这里为false,说明不是浅层响应式。
  • dep:这是一个依赖收集器,用于跟踪哪些部分的代码依赖于这个响应式对象。当响应式对象的值发生变化时,会通知依赖它的部分进行更新。这里显示为一个包含一个ReactiveEffectMap,说明有一个依赖项。
  • __v_isRef:为true,表明这是一个由ref创建的响应式引用。
  • _rawValue:存储了原始的值,这里是一个包含nameage属性的对象。
  • _valuevalue:都是代理对象,通过代理可以实现对原始对象的响应式追踪。
  • [[Handler]][[Target]]:是与代理对象相关的内部属性,[[Handler]]是处理程序,用于定义对目标对象的各种操作的拦截行为,[[Target]]是被代理的原始对象。

从控制台打印的数据结构可以看出:如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。

表面上它返回来的是一个RefImpl的实例对象,但是在这个实例对象的_valuevalue属性里,是 reactive() 的返回值:一个Proxy的实例对象。

直接调用changePerson修改信息,person没有被修改。编译器会报错:

javascript 复制代码
const changePerson = () => {
  // 报错:类型"Ref<{ name: string; age: number; }>"上不存在属性"name"。
  person.name = '李四'
  // 报错:类型"Ref<{ name: string; age: number; }>"上不存在属性"age"。
  person.age = 24
  console.log(person)
}

ref定义的数据,如果要修改,要用.value来更改。

javascript 复制代码
const changePerson = () => {
  console.log('修改前打印person.value:', person.value)
  person.value.name = '李四'
  person.value.age = 24
  console.log(person)
}

可以使用Vue - Official插件自动添加.value

ref()reactive()的区别

  1. 从数据类型看
    • ref()用来定义:基本类型数据对象类型数据
    • reactive()用来定义:对象类型数据
  2. 返回值类型
    • ref()返回值: 一个RefImpl的实例对象。
    • reactive()返回值:一个Proxy的实例对象,简称:响应式对象。
  3. 响应式更新方式
  • ref()通过修改.value属性来触发响应式更新。
    ref()跳过 .value属性直接修改变量的值,不会出发响应式更新。
javascript 复制代码
import { ref } from 'vue';
let person = ref({name: '张三', age: 36});
const changePerson = () => {
  // 重新分配一个对象,触发响应式更新
  person.value = { name: '李四', age: 24 }
  // ref 跳过 .value,不会出发响应式更新
  person = { name: '李四', age: 24 }
}

let count = ref(0)
const changeCount = () => {
  // 可以触发响应式更新
  count.value ++;

  // 不会触发响应式更新
  count = ref(10)
}
  • reactive()直接修改对象的属性即可触发响应式更新。
javascript 复制代码
import { reactive } from 'vue';
let person = reactive({name: '张三', age: 36});
const changePerson = () => {
  person.name = '李四'   // 触发响应式更新
  person.age = 30        // 触发响应式更新
}
  • reactive()重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。
javascript 复制代码
import { reactive } from 'vue';
let person = reactive({name: '张三', age: 36});

// 不会触发响应式更新
const changePerson = () => {
  // 重新分配一个对象
  person = { name: '李四', age: 24 }
  
  // person = reactive({name: '张三', age: 36});
  // 与person = reactive({ name: '李四',age: 24 });
  // 是完全不同的2个完全不同的person, 根本不是同一个东西
  person = reactive({ name: '李四', age: 24 });
}

// 使用Object.assign()等方法来更新属性
const changePerson = () => {
  Object.assign(person, {
    name: '李四',
    age: 24,
  });
}
  1. 深层响应性

    • reactive()可以自动保持对象的深层响应性,即嵌套对象的属性修改也会触发响应式更新。
    • 对于ref()包裹的对象,直接修改嵌套对象的属性可能不会触发响应式更新,需要特殊处理。
  2. 使用场景

    • 若需要一个基本类型的响应式数据,必须使用ref()
    • 若需要一个响应式对象,层级不深,ref()reactive()都可以。
    • 若需要一个响应式对象,且层级较深,推荐使用reactive()
相关推荐
_AaronWong9 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode9 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户54330814419410 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo10 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭10 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木10 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮10 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati10 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉10 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n10 小时前
双端 Diff 算法详解
前端·javascript·vue.js