Vue3 响应式核心 API

一、ref:基础类型的响应式

ref 是 Vue3 中最常用的响应式 API 之一,专门用于将基础类型(Number、String、Boolean 等) 转为响应式数据;同时也支持处理引用类型(Object、Array),但底层会自动用 reactive包裹。

1. 核心特性

  • 本质是一个“响应式容器”:把一个简单类型包装成一个对象,使它可以被追踪(响应式)。ref 返回的是一个包含 .value 属性的对象,模板中直接使用,操作时需要访问.value
  • 支持基础类型与引用类型:基础类型直接存于 value,引用类型会被 reactive 转为响应式对象

2. 实战示例

<template>
  <div>
    <!-- 模板中使用 ref 数据,无需 .value -->
    <p>计数器:{{ count }}</p>
    <p>用户名:{{ user.name }}</p>
    <button @click="updateData">更新数据</button>
  </div>
</template>

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

// 1. 基础类型转响应式
const count = ref(0) // 初始值为 0,返回 ref 对象
// 2. 引用类型转响应式(底层自动用 reactive 处理)
const user = ref({ name: '张三', age: 20 })

// 修改 ref 数据:需通过 .value
const updateData = () => {
  count.value += 1 // 基础类型修改
  user.value.name = '李四' // 引用类型修改(.value 后等同于 reactive 对象)
  
}
</script>

二、reactive:引用类型的响应式代理

reactive 用于将引用类型(Object、Array) 转为响应式数据,底层通过 ES6 Proxy 创建对象的代理,拦截数据的读写操作,从而实现响应式更新。

1. 核心特性

  • 仅支持引用类型:若传入基础类型,会直接警告(需用 ref`处理基础类型);
  • 无需 .value 访问:创建的响应式对象可直接读写属性(如 user.name),无需像 ref 那样通过 .value;
  • 深层响应式:默认对嵌套对象/数组进行“深度代理”,无论嵌套多少层,修改内部属性都会触发视图更新。

2. 实战示例

<template>
  <div>
    <p>用户信息:{{ user.name }}({{ user.age }}岁)</p>
    <p>爱好:{{ hobbies.join(', ') }}</p>
    <button @click="updateData">更新数据</button>
  </div>
</template>

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

// 1. 对象转响应式
const user = reactive({
  name: '张三',
  age: 20,
  address: { city: '北京' } // 嵌套对象,默认深层响应式
})

// 2. 数组转响应式
const hobbies = reactive(['篮球', '游戏'])

// 直接修改属性,无需 .value
const updateData = () => {
  user.name = '李四' 
  user.address.city = '上海' 
  hobbies.push('读书') 
}
</script>

3. 注意事项

  • 避免“解构丢失响应式”:直接解构 reactive对象的属性(如 const { name } = user),解构出的 name 会变成普通值,若需解构需用 toRefs`辅助;
  • 当数据存在多层嵌套时,reactive的深层响应式特性可简化代码,无需手动处理嵌套层级的响应式。

三、shallowReactive:浅层响应式

shallowReactive是 reactive的“浅层版本”,仅对顶层属性进行响应式代理,嵌套属性不会转为响应式(即“浅代理”)。

1. 核心特性

  • 浅层响应式:仅顶层属性修改会触发视图更新,嵌套属性修改无响应;
  • 适用引用类型:与 reactive 一致,仅支持引用类型,不支持基础类型。

2. 实战示例

<template>
  <div>
    <p>{{ shallowObj.topProp }}({{ shallowObj.nestedProp.val }})</p>
    <p>{{ deepObj.topProp }}({{ deepObj.nestedProp.val }})</p>
    <button @click="updateData">更新嵌套属性</button>
  </div>
</template>

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

// 1. shallowReactive:仅顶层属性响应式
const shallowObj = shallowReactive({
  data: '顶层值',
  anotherData: { val: '111' } 
})

// 2. reactive:深层响应式
const deepObj = reactive({
  data: '顶层值',
  anotherData: { val: '111 }

const updateData = () => {
  // 修改 shallowObj 无响应(不更新)
  shallowObj.nestedProp.val = '222'
  // 修改 deepObj :有响应(更新)
  deepObj.nestedProp.val = '222'
}
</script>

四、readonly:只读响应式

readonly 用于创建一个只读的响应式对象,无论是 ref`还是 reactive 对象,经过 readonly 处理后,其属性无法被修改,但仍保持响应式(若原对象更新,只读对象也会同步更新)。

1. 核心特性

  • 只读不可改:无论是顶层属性还是嵌套属性,都无法直接修改;
  • 保持响应式:若原响应式对象(如 reactive/ref)更新,readonly`对象会同步更新视图;
  • 深层只读:默认对嵌套对象进行“深层只读处理”

2. 实战示例

<template>
  <div>
    <p>只读用户信息:{{ readonlyUser.name }}({{ readonlyUser.age }}岁)</p>
    <p>原用户信息:{{ user.name }}({{ user.age }}岁)</p>
    <button @click="updateData">尝试修改</button>
  </div>
</template>

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

// 1. 创建原响应式对象
const user = reactive({ name: '张三', age: 20 })
// 2. 转为只读响应式对象
const readonlyUser = readonly(user)

const updateData = () => {
  // 尝试修改 readonly 对象:开发环境警告,修改无效
  readonlyUser.name = '李四' // 无效(只读拦截)
  
  // 修改原对象:readonly 对象会同步更新(保持响应式)
  user.name = '李四' // 原对象修改后,readonlyUser 视图同步更新
}
</script>

3. 适用场景

  • 保护“不可修改的全局配置”:如项目的主题配置、接口基础地址等,用 readonly 防止误修改;
  • 组件间传递“只读数据”:父组件向子组件传递数据时,若不希望子组件修改数据,可将数据用 readonly`包装后传递,避免子组件破坏父组件状态(遵循单向数据流)。

五、watchEffect:“自动追踪依赖”的响应式副作用

watchEffect 是 Vue3 中用于处理“响应式副作用”的 API,它会自动追踪函数内部的响应式数,当这些数据更新时,函数会重新执行。相比 watch,watchEffect 无需手动指定监听的数据源,更简洁。

1. 核心特性

  • 处理所有输入框作为监听源,无需手动列举所有输入框作为监听源;

2. 实战示例(基础用法 + 清理副作用)

<template>
  <div>
    <p>计数器:{{ count }}</p>
    <button @click="count++">+1</button>
  </div>
</template>

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

const count = ref(0)

// 1. 基础用法:自动追踪 count 依赖
watchEffect(() => {
  console.log('count 更新了:', count.value) // 初始执行一次,count 变化时再执行
})

// 2. 清理定时器
watchEffect((onCleanup) => {
  // 每次 count 更新时,先执行清理函数,再执行副作用
  const timer = setTimeout(() => {
    console.log('count 延迟打印:', count.value)
  }, 1000)
  
    onCleanup(() => {
    clearTimeout(timer) // 清除上一次的定时器,避免多个定时器叠加
  })
})
</script>

六、toRefs:创建结构后的响应式连接

将响应式对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的 ref,保持对其源的响应式连接。

1. 核心特性

  • 与toRef相似,可以用来为响应式对象上的多个属性新创建多个 ref ,从而保持对其原来属性的响应式连接。

2. 实战示例

<template>
  <div>id:{{id}}</div>
  <div>name:{{name}}</div>
</template>
setup() {
  const state = reactive({
    id: 1,
    name: 'front-refiend'
  });

  return {
    ...toRefs(state)
  };
}
//原理
function toRefs(object) {
  const ret = {};
  for (const key in object) {
    ret[key] = toRef(object, key);
  }
  return ret;
}

3.ref 与 reactive 解构问题

  • reactive直接解构会丢失响应式,需用 toRefs(如 const { name } = toRefs(user));
  • ref 解构时,若用 const { value: countVal } = count,countVal是非响应式的,需直接使用 toRef。

更多推荐