Vue 响应式原理深度解析:Vue2 vs Vue3 核心差异 + ref/reactive 实战指南

引言:为什么响应式是 Vue 的 "灵魂"?

作为 Vue 开发者,你一定对这样的场景习以为常:

  • 修改data里的变量,页面 DOM 自动同步更新;
  • 表单输入时,绑定的响应式数据实时变化;
  • 数组push新元素后,列表自动渲染新增项。

这背后的 "魔法",正是 Vue 的响应式系统------ 它让数据与视图建立 "自动关联",开发者无需手动操作 DOM,只需关注数据逻辑,极大提升开发效率。

但你是否好奇:

  • 数据变化时,Vue 是如何 "感知" 到的?
  • 为什么 Vue2 中给对象新增属性需要Vue.set,而 Vue3 不需要?
  • refreactive到底该怎么选?它们的底层逻辑是什么?

本文将从 "原理拆解→版本对比→实战应用→避坑指南" 四个维度,彻底讲透 Vue 响应式的核心逻辑:先剖析 Vue2 的Object.defineProperty实现,再详解 Vue3 的Proxy升级方案,最后结合ref/reactive的实战场景,让你不仅 "会用",更能 "懂原理"。

1. 响应式的核心本质:数据劫持 + 依赖收集

在深入版本差异前,我们先明确响应式的核心目标:当数据发生变化时,自动触发依赖该数据的视图更新或逻辑执行

要实现这个目标,需要解决两个关键问题:

  1. 数据劫持:如何 "监听" 数据的读写操作(比如修改属性值、数组新增元素)?
  2. 依赖收集:如何记录 "哪些视图 / 逻辑依赖了这个数据"?

这就像一个 "订阅 - 发布" 系统:

  • 数据是 "发布者",视图 / 逻辑是 "订阅者";
  • 数据劫持负责 "监听发布者的动作"(比如数据被修改);
  • 依赖收集负责 "记录订阅者列表";
  • 当数据变化时,发布者通知所有订阅者执行更新。

Vue2 和 Vue3 的响应式系统,本质上都是围绕这两个核心问题展开,只是数据劫持的实现方式不同------ 这也是两者差异的根源。

2. Vue2 响应式原理:Object.defineProperty 的 "功与过"

Vue2 的响应式核心是Object.defineProperty(以下简称 "defineProperty"),这是 ES5 提供的 API,用于劫持对象的单个属性,实现对属性读写的监听。

2.1 核心原理:劫持属性 + Dep/Watcher 机制

2.1.1 第一步:用 defineProperty 劫持对象属性

Vue2 会遍历data中的所有对象属性,通过defineProperty重写gettersetter方法:

  • getter:当属性被访问时触发,用于 "收集依赖"(记录哪些地方用到了这个属性);
  • setter:当属性被修改时触发,用于 "派发更新"(通知所有依赖该属性的地方执行更新)。
2.1.2 第二步:Dep(依赖收集容器)

每个被劫持的属性,都会对应一个Dep实例(可以理解为 "订阅者列表"):

  • DepaddSub方法:将依赖(Watcher 实例)加入列表;
  • Depnotify方法:遍历列表,通知所有 Watcher 执行更新。
2.1.3 第三步:Watcher(依赖订阅者)

Watcher是 "依赖的具体载体",每个视图组件、计算属性、watch监听,都会对应一个Watcher实例:

  • 当组件渲染时,会触发属性的getter,将当前Watcher加入属性的Dep列表;
  • 当属性被修改时,触发setter,调用Dep.notify(),所有Watcher会执行update方法,触发组件重新渲染或watch回调。

2.2 Vue2 响应式完整流程

2.3 代码示例:手动实现 Vue2 响应式核心

javascript 复制代码
// 1. 依赖收集容器:Dep
class Dep {
  constructor() {
    this.subscribers = []; // 存储依赖(Watcher实例)
  }

  // 添加依赖
  addSub(watcher) {
    this.subscribers.push(watcher);
  }

  // 通知所有依赖更新
  notify() {
    this.subscribers.forEach(watcher => watcher.update());
  }
}

// 2. 订阅者:Watcher
class Watcher {
  constructor(updateFn) {
    this.updateFn = updateFn; // 依赖更新时执行的函数
  }

  // 执行更新
  update() {
    this.updateFn();
  }
}

// 3. 响应式处理函数:劫持对象属性
function observe(obj) {
  // 只处理对象类型
  if (typeof obj !== 'object' || obj === null) return;

  // 遍历对象所有属性
  Object.keys(obj).forEach(key => {
    let value = obj[key];
    const dep = new Dep(); // 每个属性对应一个Dep

    // 重写getter/setter
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 访问属性时,收集依赖(此时Watcher已被激活)
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set(newValue) {
        if (newValue === value) return;
        value = newValue;
        // 修改属性时,通知依赖更新
        dep.notify();
      }
    });

    // 递归处理嵌套对象
    observe(value);
  });
}

// 4. 测试:模拟Vue2响应式
const data = { name: 'Vue2', age: 8 };
observe(data);

// 创建Watcher(模拟组件渲染逻辑)
Dep.target = new Watcher(() => {
  console.log(`视图更新:name=${data.name}, age=${data.age}`);
});
// 首次访问属性,触发getter,收集依赖
console.log(data.name); // 触发getter,Dep收集Watcher
Dep.target = null;

// 修改属性,触发setter,通知更新
data.name = 'Vue2响应式'; // 输出:视图更新:name=Vue2响应式, age=8
data.age = 9; // 输出:视图更新:name=Vue2响应式, age=9

2.4 Vue2 响应式的 "致命缺陷"(实战痛点)

虽然defineProperty实现了响应式核心,但在实际开发中,它的设计限制带来了诸多痛点:

2.4.1 1. 无法监听对象新增 / 删除属性

defineProperty只能劫持已存在的属性 ,如果给对象新增属性(比如data.user = { name: '张三' }),或删除属性(delete data.name),不会触发getter/setter,导致响应式失效。

javascript 复制代码
// Vue2中新增属性,视图不更新
const vm = new Vue({
  data() {
    return { user: { age: 20 } };
  }
});

vm.user.name = '张三'; // 新增属性,视图不更新
delete vm.user.age; // 删除属性,视图不更新

// 解决方案:使用Vue.set/Vue.delete
this.$set(this.user, 'name', '张三');
this.$delete(this.user, 'age');
2.4.2 2. 无法监听数组的 "部分操作"

Vue2 对数组的pushpopshiftunshiftsplicesortreverse7 种方法进行了 "重写",可以触发响应式。但以下场景仍无法监听:

  • 直接修改数组索引:arr[0] = 10
  • 直接修改数组长度:arr.length = 0
javascript 复制代码
// Vue2中数组操作的坑
const vm = new Vue({
  data() {
    return { list: [1, 2, 3] };
  }
});

vm.list[0] = 10; // 直接修改索引,视图不更新
vm.list.length = 0; // 直接修改长度,视图不更新

// 解决方案:用重写的方法或Vue.set
vm.list.splice(0, 1, 10); // 可用
this.$set(vm.list, 0, 10); // 可用
2.4.3 3. 嵌套对象需要递归劫持,性能开销大

Vue2 需要遍历对象的所有属性,并且对嵌套对象进行递归劫持(比如data.user.address.city)。如果对象层级过深、属性过多,会导致初始化时的性能损耗。

2.4.4 4. 只能劫持对象,无法劫持原始值

defineProperty只能作用于对象的属性 ,无法直接劫持numberstringboolean等原始值。Vue2 通过将原始值包装在data对象中(比如data: { count: 0 })间接实现响应式。

3. Vue3 响应式原理:Proxy 的 "全方位升级"

为了解决 Vue2 的痛点,Vue3 放弃了defineProperty,转而采用 ES6 的Proxy(代理)API,配合Reflect(反射),实现了更强大、更灵活的响应式系统。

3.1 核心原理:代理对象 + effect 依赖收集

3.1.1 第一步:Proxy 代理整个对象

Proxy的核心优势是直接代理整个对象,而非单个属性:

  • 无需遍历对象属性(初始化性能更优);
  • 能监听对象的所有操作(新增属性、删除属性、数组索引修改等);
  • 支持嵌套对象的自动代理(访问嵌套属性时才递归代理,懒加载模式)。

Proxy的常用拦截器(针对响应式):

  • get(target, key):拦截属性访问(对应 Vue2 的getter,用于收集依赖);
  • set(target, key, value):拦截属性修改 / 新增(对应 Vue2 的setter,用于派发更新);
  • deleteProperty(target, key):拦截属性删除(Vue2 无法监听)。
3.1.2 第二步:Reflect 反射 API

Reflect是 ES6 提供的内置对象,用于 "反射" 对象的操作,与Proxy配合使用:

  • 替代Object的方法(比如Reflect.get(target, key)等价于target[key]);
  • 统一返回操作结果(比如Reflect.set返回boolean,表示修改是否成功);
  • 避免直接操作目标对象,更安全、更规范。
3.1.3 第三步:effect(依赖收集核心)

Vue3 用effect函数替代了 Vue2 的Watcher,核心作用是:

  • 执行一个函数(比如组件渲染函数、watch回调);
  • 在函数执行过程中,收集依赖(记录哪些响应式数据被访问);
  • 当响应式数据变化时,重新执行这个函数。

3.2 Vue3 响应式完整流程

3.3 代码示例:手动实现 Vue3 响应式核心

javascript 复制代码
// 1. 依赖收集:存储target(目标对象)→ key(属性)→ effects(依赖集合)
const targetMap = new WeakMap();

// 2. 收集依赖:将effect加入对应key的依赖集合
function track(target, key) {
  // 没有当前effect,直接返回
  if (!activeEffect) return;

  // 目标对象的依赖映射(target → Map(key → Set(effects)))
  let depsMap = targetMap.get(target);
  if (!depsMap) targetMap.set(target, (depsMap = new Map()));

  // 属性的依赖集合(key → Set(effects))
  let dep = depsMap.get(key);
  if (!dep) depsMap.set(key, (dep = new Set()));

  // 将当前effect加入依赖集合
  dep.add(activeEffect);
}

// 3. 派发更新:执行所有依赖的effect
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const dep = depsMap.get(key);
  if (dep) {
    // 执行所有effect
    dep.forEach(effect => effect());
  }
}

// 4. 全局变量:存储当前活跃的effect
let activeEffect = null;

// 5. 依赖函数:effect
function effect(fn) {
  // 包装effect函数
  const effectFn = () => {
    activeEffect = effectFn; // 激活当前effect
    fn(); // 执行回调,触发属性访问,收集依赖
    activeEffect = null; // 重置
  };

  effectFn(); // 首次执行
  return effectFn;
}

// 6. 响应式核心:reactive(基于Proxy)
function reactive(target) {
  // 只处理对象/数组类型
  if (typeof target !== 'object' || target === null) return target;

  // 创建Proxy代理
  return new Proxy(target, {
    // 拦截属性访问
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      track(target, key); // 收集依赖
      // 递归代理嵌套对象(懒加载)
      return reactive(result);
    },
    // 拦截属性修改/新增
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const success = Reflect.set(target, key, value, receiver);
      // 只有值变化时才触发更新
      if (success && oldValue !== value) {
        trigger(target, key); // 派发更新
      }
      return success;
    },
    // 拦截属性删除
    deleteProperty(target, key) {
      const success = Reflect.deleteProperty(target, key);
      if (success) {
        trigger(target, key); // 派发更新
      }
      return success;
    }
  });
}

// 7. 测试:模拟Vue3响应式
const data = reactive({ name: 'Vue3', age: 5, list: [1, 2, 3] });

// 创建effect(模拟组件渲染)
effect(() => {
  console.log(`视图更新:name=${data.name}, age=${data.age}, list=[${data.list}]`);
});

// 1. 修改已存在属性
data.name = 'Vue3响应式'; // 输出:视图更新:name=Vue3响应式, age=5, list=[1,2,3]

// 2. 新增属性(Vue2不支持)
data.gender = 'male'; // 输出:视图更新:name=Vue3响应式, age=5, list=[1,2,3](gender默认undefined)

// 3. 删除属性(Vue2不支持)
delete data.age; // 输出:视图更新:name=Vue3响应式, age=undefined, list=[1,2,3]

// 4. 修改数组索引(Vue2不支持)
data.list[0] = 10; // 输出:视图更新:name=Vue3响应式, age=undefined, list=[10,2,3]

// 5. 修改数组长度(Vue2不支持)
data.list.length = 2; // 输出:视图更新:name=Vue3响应式, age=undefined, list=[10,2]

3.4 Vue3 响应式的 "核心优势"(解决 Vue2 痛点)

3.4.1 1. 支持对象新增 / 删除属性

Proxyset拦截器能监听属性新增,deleteProperty能监听属性删除,无需Vue.set/Vue.delete,自然支持响应式。

3.4.2 2. 完美支持数组所有操作

无论是修改数组索引、长度,还是使用数组方法,Proxy都能拦截,彻底解决 Vue2 的数组监听痛点。

3.4.3 3. 嵌套对象懒加载代理

Vue3 不会在初始化时递归劫持所有嵌套对象,而是在访问嵌套属性时才进行代理 (比如data.user.address,只有访问address时才代理它),初始化性能大幅提升。

3.4.4 4. 支持更多数据类型

除了对象 / 数组,Proxy还能代理MapSet等集合类型(Vue2 不支持),响应式覆盖范围更广。

3.4.5 5. 更简洁的代码结构

无需遍历对象属性,Proxy直接代理整个对象,代码更简洁,维护成本更低。

4. Vue3 响应式数据创建:ref vs reactive(实战篇)

Vue3 提供了两种创建响应式数据的 API:refreactive。很多开发者会混淆它们的用法,其实核心区别在于处理的数据类型不同

4.1 reactive:引用类型的 "响应式包装器"

4.1.1 核心定义

reactive用于将引用类型数据 (对象、数组、MapSet等)转为响应式对象,返回一个Proxy代理实例。

4.1.2 典型使用场景
  • 复杂对象(如用户信息、表单数据);
  • 数组(如列表数据、选项数组);
  • 集合类型(如Map存储键值对数据)。
4.1.3 代码示例(实战级)
html 复制代码
<template>
  <div class="user-info">
    <h3>用户信息</h3>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
    <button @click="updateUser">修改用户</button>

    <h3>爱好列表</h3>
    <ul>
      <li v-for="(hobby, index) in hobbies" :key="index">{{ hobby }}</li>
    </ul>
    <button @click="addHobby">新增爱好</button>
  </div>
</template>

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

// 1. 响应式对象(用户信息)
const user = reactive({
  name: '张三',
  age: 25
});

// 2. 响应式数组(爱好列表)
const hobbies = reactive(['篮球', '编程']);

// 修改用户信息
const updateUser = () => {
  user.name = '李四'; // 直接修改属性,响应式生效
  user.age += 1;
};

// 新增爱好
const addHobby = () => {
  hobbies.push('阅读'); // 数组方法,响应式生效
  hobbies[0] = '足球'; // 直接修改索引,响应式生效
};
</script>
4.1.4 关键注意事项
  • ❌ 不能直接替换reactive对象(会丢失响应式):

    javascript 复制代码
    const user = reactive({ name: '张三' });
    user = { name: '李四' }; // 错误:替换后不再是Proxy代理对象,响应式失效
  • ✅ 应修改对象属性而非替换对象:

    javascript 复制代码
    user.name = '李四'; // 正确
    Object.assign(user, { name: '李四' }); // 正确

4.2 ref:基本类型的 "响应式载体"

4.2.1 核心定义

ref用于将基本类型数据numberstringboolean等)转为响应式数据,返回一个 "响应式对象",该对象包含一个value属性,通过value访问 / 修改原始值。

4.2.2 底层原理

ref的底层是reactive!它会创建一个{ value: 原始值 }的响应式对象(用reactive包装),所以修改value时会触发响应式更新。

4.2.3 典型使用场景
  • 基本类型数据(如计数器、开关状态);
  • 单个引用类型数据(如单个用户、单个商品,可简化代码);
  • 组件间传递的基本类型 props(需用ref保持响应式)。
4.2.4 代码示例(实战级)
html 复制代码
<template>
  <div class="counter">
    <h3>计数器:{{ count }}</h3>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>

    <h3>开关状态:{{ isOpen ? '开启' : '关闭' }}</h3>
    <button @click="toggleOpen">切换状态</button>

    <h3>单个商品:{{ product.name }}(价格:{{ product.price }}元)</h3>
    <button @click="discount">打8折</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

// 1. 基本类型:数字
const count = ref(0);

// 2. 基本类型:布尔值
const isOpen = ref(true);

// 3. 引用类型:单个对象(用ref也可,简化代码)
const product = ref({
  name: 'Vue3实战教程',
  price: 99
});

// 计数器+1
const increment = () => {
  count.value += 1; // script中必须通过.value修改
};

// 计数器-1
const decrement = () => {
  count.value -= 1;
};

// 切换开关
const toggleOpen = () => {
  isOpen.value = !isOpen.value;
};

// 商品打折
const discount = () => {
  product.value.price *= 0.8; // 引用类型需通过.value访问属性
};
</script>
4.2.5 关键注意事项
  • ✅ 模板中无需写value:Vue3 会自动解包ref对象,模板中直接访问变量名即可(如{``{ count }}等价于{``{ count.value }});
  • ❌ script 中必须写valueref返回的是响应式对象,必须通过value访问 / 修改原始值;
  • ✅ 引用类型用ref也可:ref支持包装引用类型,底层会转为reactive,用法上与reactive的区别仅在于需要多一层value

4.3 ref vs reactive:核心区别与选择指南

特性 ref reactive
支持数据类型 基本类型 + 引用类型 仅引用类型
访问方式 script 中需.value,模板自动解包 直接访问属性,无需.value
底层实现 基于reactive(包装为{ value: T } 基于Proxy直接代理对象
替换整个对象 支持(count.value = 10product.value = { ... } 不支持(直接替换会丢失响应式)
适用场景 基本类型、单个引用类型 复杂对象、数组、集合类型
选择原则(一句话总结):
  • 基本类型用 refnumberstringboolean等);
  • 引用类型优先用 reactive (对象、数组、Map等);
  • 若需替换整个引用类型对象 (如product = { name: '新商品' }),用ref更方便。

4.4 实用工具:toRef、toRefs、unref

Vue3 提供了三个常用工具函数,用于解决refreactive的转换问题:

4.4.1 toRef:为 reactive 对象的单个属性创建 ref

用于 "提取"reactive对象的单个属性,保持响应式关联:

javascript 复制代码
import { reactive, toRef } from 'vue';

const user = reactive({ name: '张三', age: 25 });
const nameRef = toRef(user, 'name');

nameRef.value = '李四'; // 修改nameRef,user.name也会变化
console.log(user.name); // 输出:李四
4.4.2 toRefs:将 reactive 对象转为 ref 对象集合

用于 "解构"reactive对象,避免解构后丢失响应式:

javascript 复制代码
import { reactive, toRefs } from 'vue';

const user = reactive({ name: '张三', age: 25 });
// 直接解构会丢失响应式
const { name, age } = user; // 非响应式
// 用toRefs解构,保持响应式
const { name: nameRef, age: ageRef } = toRefs(user);

nameRef.value = '李四';
console.log(user.name); // 输出:李四(响应式关联)
4.4.3 unref:自动解包 ref 对象

相当于isRef(val) ? val.value : val,简化ref对象的访问:

javascript 复制代码
import { ref, unref } from 'vue';

const count = ref(0);
console.log(unref(count)); // 输出:0(等价于count.value)

const num = 10;
console.log(unref(num)); // 输出:10(非ref直接返回原值)

5. Vue2 vs Vue3 响应式:全方位对比(表格)

对比维度 Vue2(Object.defineProperty) Vue3(Proxy + Reflect)
核心 API Object.defineProperty Proxy + Reflect
劫持粒度 单个属性 整个对象
对象新增属性 不支持(需 Vue.set) 原生支持
对象删除属性 不支持(需 Vue.delete) 原生支持
数组索引修改 不支持 原生支持
数组长度修改 不支持 原生支持
嵌套对象处理 初始化递归劫持(性能差) 访问时懒加载代理(性能优)
支持数据类型 对象、数组(有限支持) 对象、数组、Map、Set 等
原始值处理 需包装在对象中 用 ref 包装(底层 reactive)
依赖收集核心 Dep + Watcher effect + targetMap

6. 避坑指南:响应式开发常见错误与解决方案

6.1 坑 1:解构 reactive 对象导致响应式丢失

javascript 复制代码
// 错误:解构reactive对象,属性变为非响应式
const user = reactive({ name: '张三', age: 25 });
const { name, age } = user;
name = '李四'; // 视图不更新

// 解决方案:用toRefs解构
const { name: nameRef, age: ageRef } = toRefs(user);
nameRef.value = '李四'; // 视图更新

6.2 坑 2:直接替换 reactive 对象导致响应式丢失

javascript 复制代码
// 错误:替换reactive对象,失去Proxy代理
const user = reactive({ name: '张三' });
user = { name: '李四' }; // 不再是响应式对象

// 解决方案1:修改属性而非替换对象
user.name = '李四';

// 解决方案2:用ref包装(需.value替换)
const user = ref({ name: '张三' });
user.value = { name: '李四' }; // 响应式生效

6.3 坑 3:ref 在 script 中忘记写.value

javascript 复制代码
// 错误:script中直接修改ref变量,不生效
const count = ref(0);
count = 1; // 直接赋值,修改的是变量引用,而非.value

// 正确:通过.value修改
count.value = 1;

6.4 坑 4:认为 ref 只能用于基本类型

javascript 复制代码
// 误区:ref只能处理基本类型
// 正确:ref支持包装引用类型,底层是reactive
const product = ref({ name: 'Vue3', price: 99 });
product.value.price = 88; // 响应式生效

6.5 坑 5:Proxy 无法监听原始值(需用 ref)

javascript 复制代码
// 错误:直接用Proxy代理原始值,无效
const count = 0;
const reactiveCount = new Proxy(count, {}); // Proxy不支持原始值

// 正确:用ref包装原始值
const count = ref(0); // 底层转为reactive({ value: 0 })

7. 总结:响应式的本质与最佳实践

7.1 核心本质

Vue 的响应式系统,本质是 **"数据劫持 + 依赖收集"** 的订阅 - 发布模式:

  • Vue2 用Object.defineProperty劫持单个属性,实现简单但限制多;
  • Vue3 用Proxy代理整个对象,配合Reflecteffect,解决了 Vue2 的所有痛点,功能更强大、性能更优。

7.2 最佳实践

  1. 数据类型决定 API 选择 :基本类型用ref,引用类型优先用reactive
  2. 避免解构 reactive 对象 :如需解构,用toRefs保持响应式;
  3. 不替换 reactive 对象 :修改属性而非替换整个对象,或用ref包装;
  4. script 中牢记 ref 的.value:模板自动解包,但 script 中必须显式访问;
  5. 复杂场景用工具函数toReftoRefsunref简化转换逻辑。

7.3 未来展望

Vue3 的响应式系统基于 ES6 的Proxy,虽然存在 "不支持 IE 浏览器" 的兼容性问题(Vue3 已放弃 IE 支持),但在现代浏览器中,它的优势远大于限制。随着前端技术的发展,Proxy的灵活性和强大功能,将成为响应式系统的主流实现方案。

掌握响应式原理,不仅能让你在开发中避开各种 "坑",更能让你理解 Vue 的设计思想 ------"数据驱动视图" 的核心,正是通过响应式系统,让开发者从繁琐的 DOM 操作中解放出来,专注于业务逻辑。

如果本文对你有帮助,欢迎点赞、收藏、转发!如有疑问或补充,欢迎在评论区交流~

附录:Vue3 响应式 API 速查表

API 作用 适用场景
reactive 将引用类型转为响应式对象 复杂对象、数组、集合类型
ref 将基本类型 / 引用类型转为响应式数据 基本类型、单个引用类型(需替换)
toRef 提取 reactive 对象的单个属性为 ref 单独使用 reactive 对象的某个属性
toRefs 将 reactive 对象转为 ref 对象集合 解构 reactive 对象,保持响应式
unref 自动解包 ref 对象(ref→原始值) 简化 ref 对象的访问
isRef 判断是否为 ref 对象 类型判断
isReactive 判断是否为 reactive 响应式对象 类型判断
shallowRef 浅响应式(只监听.value 的替换) 无需深度响应的引用类型
shallowReactive 浅响应式(只监听顶层属性) 无需深度响应的复杂对象
相关推荐
傲文博一28 分钟前
为什么我的产品尽量不用「外置」动态链接库
前端·后端
Healer91828 分钟前
Promise限制重复请求
前端
chenyunjie30 分钟前
我做了一个编辑国际化i18n json文件的命令行工具
前端
Emma歌小白34 分钟前
移除视觉对象里“行的型号”造成的行级筛选,但不移除用户的 slicer 筛选
前端
玉宇夕落34 分钟前
深入理解 JavaScript 中的 this:从设计缺陷到最佳实践(完整复习版)
javascript
茶杯67535 分钟前
“舒欣双免“方案助力MSI-H/dMMR结肠癌治疗新突破
java·服务器·前端
昔人'35 分钟前
css `svh` 单位
前端·css
刻刻帝的海角36 分钟前
基于UniApp与Vue3语法糖的跨平台待办事项应用开发实践
javascript·vue.js·uni-app
转转技术团队36 分钟前
浏览器自动化革命:从 Selenium 到 AI Browser 的 20 年进化史
前端