尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Vue 3 响应式核心:ref 与 reactive 的本质区别与选型指南

Vue 3 响应式核心:ref 与 reactive 的本质区别与选型指南
📅 发布时间:2026/6/24 5:18:46

1. 为什么你写的 ref 总是“不响应”?从一个真实报错说起

提示:[Vue warn] Vue received a component that was made a reactive object. This can lead to unexpected behavior.—— 这不是警告,是系统在拍你肩膀说:“兄弟,你把组件实例塞进 reactive 里了,快停手。”

我第一次在 Vue 3 项目里看到这个警告时,正急着赶一个后台管理系统的权限模块。代码逻辑很清晰:用reactive包了一堆表单字段和按钮状态,其中混进了import { ElButton } from 'element-plus'的组件构造函数,还顺手.push()进了一个ref创建的动态按钮列表。结果控制台红字炸开,页面点击无反应,调试器里console.log(state)显示所有属性都变成了Proxy嵌套,连toString()都被劫持了。

这不是玄学,是响应式系统底层机制在“拒绝合作”。

Vue 3 的响应式核心不是魔法,而是一套精密的对象代理链 + 依赖追踪图 + 触发更新队列三件套。ref和reactive是两套不同入口、不同设计目标、不同适用边界的 API,它们背后调用的是完全不同的createRef和createReactiveObject工厂函数。强行混用,就像把 USB-C 接口硬插进 HDMI 插座——物理上能塞进去,但信号协议根本不通。

很多人以为“只要加了响应式包装,数据就能自动更新”,这是 Vue 2 时代Object.defineProperty留下的思维惯性。Vue 3 改用Proxy后,响应式能力有了质的飞跃,但也带来了更严格的类型契约:reactive只接受 plain object / array / Map / Set;ref则专为“任意值”设计,包括原始类型、函数、Promise、甚至另一个ref。它内部通过.value属性桥接,本质是给非对象类型造了一个“可代理的壳”。

关键词ref和reactive看似只是两个函数名,实则是 Vue 3 响应式哲学的分水岭:前者解决“值的响应性”,后者解决“结构的响应性”。90% 的坑,都源于没看清这个根本差异,而是凭直觉“哪个写起来顺手就用哪个”。

比如,你写const count = reactive({ value: 0 }),再在模板里用{{ count.value }}—— 表面看能跑,但count本身是个 Proxy,.value是个原始 number,它不会触发响应式更新。因为reactive只劫持对象属性的读写,不劫持原始值的变更。你改count.value++,Vue 能捕获到;但如果你const temp = count.value; temp++,再count.value = temp,这就多此一举,且极易在复杂逻辑中漏掉赋值。

再比如,你写const list = ref([]),然后list.value.push(item),这没问题;但若写成const list = reactive([]),再list.push(item),虽然也能更新视图,但list的类型在 TypeScript 中会丢失Array的泛型信息,IDE 无法提示map/filter方法,v-for中item类型推导也会失效。这不是小问题,是工程化协作中的“隐性债务”。

所以,别再问“ref 和 reactive 哪个更好”,要问:“我现在手里这个东西,它的生命周期、使用方式、类型需求,到底适配哪条响应式路径?”——这才是 Vue 3 响应式最佳实践的第一课:以数据本体为锚点,而非以 API 便利性为优先。

2. ref 的真正战场:什么时候你必须用 ref,而不是 reactive?

2.1 原始类型与函数的不可替代性

reactive的设计契约第一条就是:只接受对象(object)或数组(array)。这是由Proxy的限制决定的——你不能对string、number、boolean、null、undefined或function直接new Proxy()。所以,当你需要让一个数字、一个布尔开关、一个字符串搜索关键词实时驱动视图时,ref是唯一合法选择。

// ✅ 正确:原始类型必须用 ref const searchKeyword = ref<string>(''); const isLoading = ref<boolean>(false); const maxRetries = ref<number>(3); // ❌ 错误:reactive 无法代理原始类型 // const searchKeyword = reactive(''); // TS error: Argument of type 'string' is not assignable to parameter of type 'object'

这里有个关键细节常被忽略:ref的类型签名是Ref<T>,它是一个带.value的对象。这意味着searchKeyword.value是string,而searchKeyword本身是Ref<string>。在<template>中,Vue 编译器会自动解包.value,所以你可以直接写{{ searchKeyword }};但在<script setup>中,你必须显式访问.value。

为什么 Vue 不自动解包 script 中的 ref?因为 JavaScript 没有运行时类型系统,编译器无法在所有上下文中安全推断“这里是不是 ref”。强制显式.value,是 Vue 3 在类型安全与开发体验之间做的清醒取舍——它让你时刻意识到“我在操作一个响应式引用”,避免意外的非响应式赋值。

实战中,我见过太多人这样写:

// ❌ 危险:看似在改值,实则创建了新变量 let temp = count.value; temp += 1; // count.value 仍是原值!temp 是独立副本

正确做法永远是:

// ✅ 安全:所有变更必须通过 .value count.value += 1; // 或 count.value = count.value + 1;

2.2 组件实例与 DOM 元素的专属通道

ref的第二大不可替代场景,是绑定组件实例和 DOM 元素。这是 Vue 3 响应式系统为“外部世界”(组件、DOM)预留的专用接口。

<template> <!-- 绑定到 DOM 元素 --> <input ref="inputRef" /> <!-- 绑定到子组件实例 --> <MyComponent ref="childComp" /> </template> <script setup lang="ts"> import { ref, onMounted, nextTick } from 'vue'; import MyComponent from './MyComponent.vue'; // ✅ 必须用 ref,且类型需明确标注 const inputRef = ref<HTMLInputElement | null>(null); const childComp = ref<InstanceType<typeof MyComponent> | null>(null); onMounted(() => { // DOM 操作:聚焦输入框 nextTick(() => { inputRef.value?.focus(); }); // 组件方法调用 childComp.value?.doSomething(); }); </script>

注意两点:第一,ref的初始值必须是null(或undefined),因为 Vue 在挂载前不会赋值;第二,类型标注必须包含| null,否则 TypeScript 会报错。这是ref在 DOM/组件绑定场景的铁律。

有人尝试用reactive({ input: null })来替代,结果发现input.value永远是null,因为reactive不会将ref的绑定逻辑注入到普通对象属性中。Vue 的模板编译器只识别ref()创建的响应式引用,并在挂载时主动赋值。这是ref的“特权”,reactive没有。

2.3 异步状态与 Promise 的响应式封装

当处理异步请求时,ref是管理 loading、data、error 三态的天然容器。reactive在这里反而笨重且易错。

// ✅ 清晰、类型安全、易于解构 const data = ref<User[]>([]); const loading = ref<boolean>(false); const error = ref<Error | null>(null); const fetchData = async () => { loading.value = true; error.value = null; try { const res = await api.getUserList(); data.value = res; } catch (e) { error.value = e as Error; } finally { loading.value = false; } };

如果强行用reactive:

// ❌ 类型混乱、解构困难、易出错 const state = reactive({ data: [] as User[], loading: false, error: null as Error | null }); // 问题1:解构后失去响应性 const { data, loading } = toRefs(state); // 必须 toRefs,否则解构即失活 // 问题2:类型推导弱 state.data.push(...newData); // TS 可能报错,因 reactive 丢失泛型

ref的优势在于:每个状态都是独立的响应式单元,可自由组合、传递、监听,且类型精准。reactive的优势在于结构化组织,但一旦结构变深、类型变复杂,维护成本指数级上升。

2.4 ref 的进阶技巧:shallowRef 与 customRef

ref并非只有基础款。shallowRef是为“大对象”量身定制的轻量级方案。

// 场景:一个包含上千条记录的表格数据 const largeTableData = shallowRef<Record<string, any>[]>([]); // ✅ shallowRef 只代理 .value 本身,不递归代理内部对象 // 修改 largeTableData.value = newData; 会触发更新 // 但 newData[i].name = 'new' 不会触发更新(除非你手动 trigger) // 极大提升性能,避免 Proxy 递归劫持开销

customRef则赋予你完全控制响应式行为的能力,比如实现防抖 ref:

import { customRef } from 'vue'; function useDebouncedRef<T>(value: T, delay = 200) { let timeout: ReturnType<typeof setTimeout> | null = null; return customRef<T>((track, trigger) => { return { get() { track(); // 订阅依赖 return value; }, set(newValue) { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { value = newValue; trigger(); // 手动触发更新 }, delay); } }; }); } // 使用 const debouncedSearch = useDebouncedRef('', 500);

这展示了ref的可扩展性:它不只是一个 API,而是一个响应式契约的入口。当你需要精细控制响应式时机、条件或副作用时,ref的生态比reactive更开放、更灵活。

3. reactive 的黄金地带:何时该用 reactive,以及如何用得更稳?

3.1 对象与数组的结构化响应式管理

reactive的核心价值,在于它能将一个具有明确结构和关系的对象,变成一个“活”的响应式实体。这里的“结构”,指的是属性间存在业务逻辑耦合,修改一个属性往往需要联动更新其他属性。

// ✅ 典型场景:表单对象 const form = reactive({ name: '', email: '', age: 0, isActive: true, tags: [] as string[] }); // ✅ 联动逻辑自然、高效 watch(() => form.email, (newEmail) => { form.isValidEmail = isValid(newEmail); }); // ✅ 数组操作语义清晰 form.tags.push('vue'); form.tags = [...form.tags, 'typescript']; // 替换整个数组也 OK

对比ref版本:

// ❌ 冗余、割裂、难以维护 const form = ref({ name: '', email: '', age: 0, isActive: true, tags: [] as string[] }); // 修改必须 .value form.value.name = 'John'; form.value.tags.push('vue'); // watch 需要 .value watch(() => form.value.email, ...); // 类型上,form.value 是一个对象,但 form 本身是 Ref<...>,IDE 提示不如 reactive 直观

reactive的优势在于:它让对象“回归本体”。你在代码中操作form.name,就像操作一个普通对象,没有.value的干扰,心智负担更低。Vue 编译器在模板中也直接支持{{ form.name }},无需解包。

但这里有个致命陷阱:reactive 返回的对象不能被解构,否则会丢失响应性。

// ❌ 大坑:解构后失去响应性 const { name, email } = reactive({ name: '', email: '' }); name = 'John'; // ✅ 赋值成功,但视图不更新!因为 name 是普通字符串,不是 ref

正确做法是:

// ✅ 方案1:用 toRefs,生成一组 ref const form = reactive({ name: '', email: '' }); const { name, email } = toRefs(form); // name 是 Ref<string>,保持响应性 // ✅ 方案2:用 toRef,只提取需要的属性 const nameRef = toRef(form, 'name'); // ✅ 方案3:直接在模板中用 form.name,不推荐在 script 中解构

toRefs的原理很简单:它遍历reactive对象的每个属性,为每个属性创建一个ref,其.value指向原对象的对应属性。这样,解构出来的name就是一个真正的ref,修改.value会同步更新原对象。

3.2 reactive 的边界:哪些东西绝对不能放进去?

reactive的契约非常严格。以下五类值,一旦塞进去,轻则警告,重则崩溃:

类型示例问题解决方案
组件构造函数reactive({ Comp: ElButton })Vue 报错received a component that was made a reactive object组件名应作为字符串或直接在 template 中使用,不要存入响应式数据
DOM 元素reactive({ el: document.getElementById('app') })el是普通对象,但 Vue 会尝试 Proxy,可能破坏原生方法用ref()绑定 DOM,不要放入 reactive
Date / RegExp / Promise / Functionreactive({ now: new Date() })Date对象被 Proxy 后,getTime()等方法失效用ref(new Date()),或存时间戳ref(Date.now())
Map / Set / WeakMap / WeakSetreactive({ map: new Map() })Vue 3.2+ 支持,但旧版本不支持,且类型推导弱优先用ref(new Map()),更可控
另一个 reactive 对象reactive({ nested: reactive({}) })双重 Proxy,性能浪费,类型混乱直接嵌套,reactive({ nested: {} })即可,Vue 会自动递归

最典型的错误来自 Element Plus 等 UI 库的文档误导。有些教程教你在data里写:

// ❌ 错误示范(常见于过时教程) const state = reactive({ tableData: [], loading: false, dialogVisible: false, // ❌ 下面这行是雷 ElButton: ElButton // 把组件构造函数塞进来 });

结果一运行就报错。ElButton是一个函数,reactive试图给它加 Proxy,但函数的apply、call方法被劫持后,组件渲染就失败了。正确的做法是:UI 组件只在 template 中声明,其 props 和事件通过ref或reactive的属性来驱动,组件本身绝不进入响应式数据流。

3.3 reactive 的性能优化:readonly 与 shallowReactive

reactive默认是“可读可写”的。但在很多场景下,你只需要“只读”视图,比如从父组件传入的 props、从 store 获取的全局配置、API 返回的静态数据。

// ✅ 用 readonly 包裹,防止意外修改,且性能更好 const config = readonly( reactive({ apiUrl: 'https://api.example.com', timeout: 5000, features: ['darkMode', 'i18n'] }) ); // config.apiUrl = 'xxx'; // TS error: Cannot assign to 'apiUrl' because it is a read-only property.

readonly不是简单的类型修饰,它是运行时防护。它返回一个Proxy,拦截所有设置操作(set、deleteProperty),并抛出错误。这比Object.freeze()更彻底,因为它能拦截深层属性。

shallowReactive则是reactive的“浅层”版本,只代理对象第一层属性,不递归代理嵌套对象。

const state = shallowReactive({ user: { profile: { name: 'John', age: 30 }, settings: { theme: 'light' } }, timestamp: Date.now() }); // ✅ 修改第一层属性,触发更新 state.timestamp = Date.now(); // ✅ 修改嵌套对象属性,不触发更新(节省性能) state.user.profile.name = 'Jane'; // 视图不更新! // ✅ 但替换整个嵌套对象,会触发更新 state.user = { profile: { name: 'Jane' }, settings: { theme: 'dark' } };

这在大型表单或树形数据中非常有用。比如一个包含数百个节点的组织架构树,你只想监听“节点是否展开”这个顶层状态,而不关心每个节点内部的name、id是否变化,shallowReactive就能避免海量 Proxy 创建。

3.4 reactive 与 TypeScript 的深度协同

reactive在 TypeScript 中的类型推导,是它区别于ref的一大优势。reactive的类型是UnwrapRef<T>,它会自动“剥开”ref的壳,让你获得最内层的类型。

interface User { id: number; name: string; email: string; } // ✅ reactive 自动推导为 User 类型,IDE 提示完美 const user = reactive<User>({ id: 1, name: 'John', email: 'john@example.com' }); user. // IDE 直接提示 id, name, email,无 .value 干扰 // ✅ 与 ref 混用时,类型依然精准 const users = reactive({ list: ref<User[]>([]), total: ref<number>(0) }); // users.list.value 是 User[],users.total.value 是 number // 但 users.list 和 users.total 本身是 Ref,需要 .value

然而,reactive的类型也有陷阱:它无法推导出ref的响应性。也就是说,如果你在reactive对象里放了一个ref,TypeScript 会认为那个属性就是ref类型,而不是它包裹的值类型。

const state = reactive({ count: ref(0), // TS 推导 count: Ref<number> message: ref('hello') // TS 推导 message: Ref<string> }); // ❌ 错误:state.count 是 Ref<number>,不能直接 ++ state.count++; // TS error // ✅ 正确:必须 .value state.count.value++;

所以,在 reactive 对象中,尽量避免直接存放 ref。如果需要混合,用toRefs解构,或者统一用ref管理所有状态。

4. ref 与 reactive 的协同作战:如何设计一个健壮的响应式状态架构?

4.1 “分层响应式”设计原则:ref 管原子,reactive 管结构

经过上百个 Vue 3 项目的锤炼,我总结出一套被团队验证有效的状态分层模式:

层级数据特征推荐 API示例
原子层(Atom)单一、不可再分的值:数字、字符串、布尔、日期戳、Promise、函数refconst count = ref(0); const token = ref<string | null>(null);
结构层(Structure)具有明确属性和关系的对象/数组:表单、配置、列表项、树节点reactiveconst form = reactive({ name: '', email: '' }); const tableData = reactive([{ id: 1, name: 'A' }]);
集合层(Collection)多个同类型结构的集合:用户列表、订单数组、菜单树ref<Array<T>>或ref<Map<K,V>>const users = ref<User[]>([]); const permissions = ref<Set<string>>(new Set());
逻辑层(Logic)由原子和结构组合而成的业务逻辑状态ref+computed+watchconst canSubmit = computed(() => form.name && form.email && !loading.value);

这个分层不是教条,而是基于数据的“变更粒度”和“使用方式”做出的理性选择。

  • 为什么集合层用ref而不用reactive?因为ref的value是一个可替换的整体。users.value = newUserList是一次性的、高效的更新;而reactive的数组,你users.push(...)是增量更新,但users = newUserList是非法的(会丢失响应性)。对于列表这种“整体刷新”频繁的场景,ref更符合直觉。

  • 为什么逻辑层必须用ref?因为computed返回的就是Ref<T>,watch的回调参数也是Ref<T>。强行用reactive包一层,只会增加.value的嵌套层级,让代码变得晦涩。

4.2 实战案例:重构一个高危的响应式表单

我们来看一个真实项目中“踩坑”的表单代码,然后一步步重构:

原始代码(高危):

// ❌ 问题重重 const state = reactive({ // 1. 原始类型用 reactive(冗余) username: '', password: '', // 2. 组件构造函数混入(致命) ElInput: ElInput, ElButton: ElButton, // 3. 异步状态混杂(难维护) loading: false, error: null, // 4. 数组用 reactive(类型弱) roles: [] as string[], // 5. 没有类型约束 }); const handleSubmit = async () => { state.loading = true; try { await api.login(state.username, state.password); } catch (e) { state.error = e; } finally { state.loading = false; } };

重构后(健壮):

// ✅ 分层清晰,类型精准,职责分明 // 原子层:所有原始值、异步状态 const username = ref<string>(''); const password = ref<string>(''); const loading = ref<boolean>(false); const error = ref<Error | null>(null); // 结构层:表单主体(纯数据,无 UI 逻辑) const formData = reactive({ email: '', phone: '', avatar: '' // URL 字符串 }); // 集合层:角色列表(整体刷新常见) const roles = ref<string[]>([]); // 逻辑层:计算属性与副作用 const isFormValid = computed(() => { return username.value.trim() && password.value.length >= 6; }); const handleSubmit = async () => { if (!isFormValid.value) return; loading.value = true; error.value = null; try { const res = await api.login({ username: username.value, password: password.value, ...formData // 展开结构层数据 }); // 更新集合层 roles.value = res.roles || []; } catch (e) { error.value = e as Error; } finally { loading.value = false; } }; // 模板中使用: // <ElInput v-model="username" /> // <ElInput v-model="formData.email" /> // <div v-if="error"> {{ error.message }} </div> // <ElButton :loading="loading" @click="handleSubmit" />

重构的关键变化:

  • 剥离 UI 组件:ElInput、ElButton回归 template,不再污染数据层。
  • 原子化原始值:username、password、loading、error全部ref,类型明确,.value访问一致。
  • 结构化表单数据:formData用reactive,保持对象语义,v-model直接绑定formData.email。
  • 集合独立管理:roles用ref<string[]>,方便整体赋值roles.value = newRoles。
  • 逻辑集中表达:isFormValid用computed,handleSubmit用async/await,职责单一。

这样的架构,让每个状态的生命周期、变更方式、类型契约都一目了然,极大降低了协作和维护成本。

4.3 高级协同:toRef、toRefs 与 reactive 的无缝衔接

toRef和toRefs是连接ref与reactive的桥梁,它们让两种范式可以安全、高效地共存。

toRef的核心价值,在于创建一个对 reactive 对象某个属性的“响应式引用”。它不是复制值,而是建立一个指向原属性的“指针”。

const state = reactive({ count: 0, name: 'Vue' }); // ✅ toRef 创建一个 ref,其 .value 始终等于 state.count const countRef = toRef(state, 'count'); // 修改 countRef,同步更新 state.count countRef.value++; // state.count 现在是 1 // 修改 state.count,同步更新 countRef.value state.count++; // countRef.value 现在是 2 // ✅ 用途1:将 reactive 的属性传递给子组件(props) // 子组件接收 ref 类型 prop,可双向绑定 <ChildComponent :count="countRef" /> // ✅ 用途2:在组合式函数中暴露 reactive 的部分属性 function useCounter(state: Reactive<any>) { const count = toRef(state, 'count'); const increment = () => count.value++; return { count, increment }; } const { count, increment } = useCounter(state);

toRefs则是toRef的批量版,它把整个reactive对象的所有属性,都转换成ref。

const state = reactive({ name: 'Vue', version: '3.4', isStable: true }); // ✅ toRefs 生成 { name: Ref<string>, version: Ref<string>, isStable: Ref<boolean> } const { name, version, isStable } = toRefs(state); // ✅ 现在可以安全解构,并保持响应性 name.value = 'Vue 3'; // state.name 同步更新

但要注意:toRefs只对reactive对象有效,对ref无效。toRefs(ref(1))会返回{ value: Ref<number> },这通常不是你想要的。

4.4 最佳实践清单:一份可直接抄作业的检查表

最后,给你一份我在团队内部推行的《Vue 3 响应式健康检查表》,每次提交代码前快速过一遍:

检查项合规写法违规写法为什么
原始类型const count = ref(0);const count = reactive({ value: 0 });后者多一层 Proxy,类型不精准,.value访问不一致
DOM/组件绑定const inputRef = ref<HTMLInputElement>(null);const state = reactive({ input: null });reactive不支持模板 ref 绑定,inputRef.value在 mounted 后才有效
对象/数组结构const form = reactive({ name: '' });const form = ref({ name: '' });前者模板中{{ form.name }}更简洁,IDE 提示更准;后者需{{ form.value.name }}
集合(列表/映射)const list = ref<Item[]>([]);const list = reactive([]);前者支持list.value = newList整体替换;后者只能list.push(),整体替换会失活
解构 reactiveconst { name } = toRefs(state);const { name } = state;后者解构后name是普通值,修改不触发更新
嵌套 refconst user = reactive({ profile: ref<User>(defaultUser) });const user = reactive({ profile: defaultUser });如果profile需要独立响应式(如单独 watch),用 ref;否则直接放对象
异步状态const data = ref<Data | null>(null); const loading = ref(false);const state = reactive({ data: null, loading: false });前者类型更精确,data.value可直接判空;后者需state.data,且state.data类型是any
只读数据const config = readonly(reactive({ ... }));const config = reactive({ ... });防止意外修改,提升代码可读性和运行时安全

这份清单不是束缚,而是经验沉淀。它帮你把“90% 的人都踩过坑”的地方,变成“零思考即可执行”的肌肉记忆。

5. 从源码视角看 ref 与 reactive:为什么它们的设计如此不同?

5.1 响应式核心:createReactiveObject 与 createRef 的双轨制

要真正理解ref与reactive的差异,必须下沉到 Vue 3 响应式核心源码。Vue 的响应式系统位于packages/reactivity目录下,其主干是两个工厂函数:

  • createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers):负责创建reactive、readonly、shallowReactive等所有基于Proxy的响应式对象。
  • createRef(rawValue, shallow):负责创建ref、shallowRef、customRef。

它们的根本区别,在于代理的目标(target)和代理的层级(level)。

createReactiveObject的target必须是object或array,它创建的Proxy拦截的是get、set、has、ownKeys等对象基本操作。当你访问state.name,gettrap 被触发,它会调用track()收集依赖;当你赋值state.name = 'Vue',settrap 被触发,它会调用trigger()触发更新。

createRef的target则是任意值。它不直接代理原始值(不可能),而是创建一个包装对象(wrapper):

// 简化版 createRef 源码逻辑 function createRef(rawValue, shallow) { const refObject = { _rawValue: rawValue, _value: shallow ? rawValue : toReactive(rawValue), // 关键:对对象进行 reactive 包装 __v_isRef: true, get value() { track(refObject, TrackOpTypes.GET, 'value'); // 收集对 .value 的依赖 return refObject._value; }, set value(newVal) { if (hasChanged(toRaw(newVal), refObject._rawValue)) { refObject._rawValue = newVal; refObject._value = shallow ? newVal : toReactive(newVal); trigger(refObject, TriggerOpTypes.SET, 'value', newVal); // 触发对 .value 的更新 } } }; return refObject; }

看到了吗?ref的本质,是一个带有get value()和set value()访问器的对象。.value是一个“门面”,它背后连接着_rawValue(原始值)和_value(可能被 reactive 包装后的值)。ref的响应式,是通过劫持.value这个属性的读写来实现的,而不是劫持原始值本身。

这就是为什么ref能支持一切类型:它不试图改变原始值,而是给原始值建一个“响应式门面”。

5.2 响应式依赖收集:track 与 trigger 的精妙配合

无论是ref还是reactive,它们的更新机制都依赖于同一个底层系统:track(追踪依赖)和trigger(触发更新)。

  • track(target, type, key):当读取一个响应式属性时(如state.name或count.value),track会将当前正在执行的effect(副作用函数,如computed、watch、render函数)记录到target的依赖图中。
  • trigger(target, type, key, newValue):当修改一个响应式属性时(如state.name = 'Vue'或count.value++),trigger会从依赖图中找出所有订阅了target的key的effect,并将它们加入更新队列。

ref和reactive的区别,只在于track和trigger的target和key是什么:

APItrack 的 targettrack 的 keytrigger 的 targettrigger 的 key
reactive({ name: '' })Proxy对象'name'Proxy对象'name'
ref('')ref对象(wrapper)'value'ref对象(wrapper)'value'

所以,ref的.value和reactive的.name,在响应式系统眼中,是完全对等的“可追踪属性”。它们共享同一套track/trigger机制,只是入口不同。

这也解释了为什么toRef(state, 'name')能工作:toRef创建的ref,其target就是state,其 `key

相关新闻

  • NWCAD:基于双流置信度门控的RAG幻觉抑制技术详解
  • Hoffman常数与轨迹限制:优化算法收敛加速的理论与实践
  • 希伯来语指代消解:应对形态复杂性的基准构建与评估协议设计

最新新闻

  • Ultralytics YOLO终极指南:从零到一的计算机视觉革命
  • Cocos Creator开发学习路线(个人向)
  • 如何用PyTorch实现Deep Learning Illustrated中的深度学习模型
  • Python虚拟显示神器PyVirtualDisplay:终极无头GUI测试解决方案
  • 深度解析MatchZoo与Awesome Neural Models for Semantic Match的集成应用
  • 如何快速入门Firo:隐私加密货币新手必备的完整指南

日新闻

  • 终极指南:如何用shadPS4在电脑上免费畅玩PS4游戏
  • 打造个性化Instagram Clone:主题定制与用户体验优化技巧
  • 未来展望:RoseTTAFold-All-Atom的发展路线图与社区支持资源汇总

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号