完整教程:Ref 和 Reactive 响应式原理剖析与代码实现
文章目录
- 一、概述
- 1. 响应式核心原理
- 二、Ref 和 Reactive对比
- 三、 Reactive 实现原理
- 1. 核心实现
- 2. 完整的 reactive 实现
- 四、 ref 实现原理
- 1. 基础 ref 实现
- 2. 完整的 ref 系统
- 五、 完整的响应式系统实现
- 六、关键点解析
- 七、与真实 Vue 3 的差异
好的,我们来深入剖析 Vue 3 中
ref
和
reactive
的核心原理,并手写一个简化但能体现核心思想的实现。
一、概述
1. 响应式核心原理
Vue3 的响应式系统基于 ES6 的 Proxy 实现,相比 Vue2 的 Object.defineProperty 有以下优势:
- 可以监听数组变化
- 可以监听对象属性的添加和删除
- 性能更好
二、Ref 和 Reactive对比
以下是 ref
和 reactive
的核心概念与区别
特性 | ref | reactive |
---|---|---|
适用类型 | 原始值 (number, string, boolean) 和 对象 | 仅对象 (包括数组、Map、Set 等复杂类型) |
访问方式 | .value 访问/修改值 | 直接访问/修改属性 |
底层实现 | 包装成一个带有 .value 属性的对象,用 Proxy 拦截 .value 的读写 | 直接用 Proxy 包装目标对象本身 |
解构问题 | 解构会失去响应性 | 解构会失去响应性 (... 扩展运算符也会) |
内部转换 | reactive 内部如果遇到 ref ,会自动解包 (unwrap) |
三、 Reactive 实现原理
1. 核心实现
// 响应式处理
function reactive(target) {
return createReactiveObject(target);
}
// 创建响应式对象
function createReactiveObject(target) {
// 如果不是对象,直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 如果已经是代理对象,直接返回
if (target.__v_isReactive) {
return target;
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 内置属性标识
if (key === '__v_isReactive') {
return true;
}
const res = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
// 递归处理嵌套对象
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
return proxy;
}
// 工具函数
function isObject(val) {
return val !== null && typeof val === 'object';
}
function hasOwn(target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
}
2. 完整的 reactive 实现
// 依赖收集和触发
const targetMap = new WeakMap();
let activeEffect = null;
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
// 完整的 reactive 函数
function reactive(target) {
if (!isObject(target)) {
console.warn(`value cannot be made reactive: ${String(target)}`);
return target;
}
if (target.__v_isReactive) {
return target;
}
const handler = {
get(target, key, receiver) {
if (key === '__v_isReactive') {
return true;
}
const res = Reflect.get(target, key, receiver);
track(target, key);
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
};
return new Proxy(target, handler);
}
四、 ref 实现原理
1. 基础 ref 实现
function ref(value) {
return createRef(value);
}
function createRef(rawValue) {
// 如果已经是 ref,直接返回
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue);
}
class RefImpl {
constructor(value) {
this.__v_isRef = true;
this._value = isObject(value) ? reactive(value) : value;
this.dep = new Set();
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
if (newVal !== this._value) {
this._value = isObject(newVal) ? reactive(newVal) : newVal;
triggerRefValue(this);
}
}
}
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
function trackRefValue(ref) {
if (activeEffect) {
ref.dep.add(activeEffect);
}
}
function triggerRefValue(ref) {
ref.dep.forEach(effect => effect());
}
2. 完整的 ref 系统
// 完整的 ref 实现
class RefImpl {
constructor(value) {
this.__v_isRef = true;
this._rawValue = value;
this._value = convert(value);
this.dep = new Set();
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = convert(newVal);
triggerRefValue(this);
}
}
}
// 工具函数
function convert(val) {
return isObject(val) ? reactive(val) : val;
}
function hasChanged(value, oldValue) {
return !Object.is(value, oldValue);
}
function ref(value) {
return createRef(value, false);
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue);
}
// 自动解构 ref
function unref(ref) {
return isRef(ref) ? ref.value : ref;
}
// 在模板中自动解构
function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key, receiver) {
return unref(Reflect.get(target, key, receiver));
},
set(target, key, value, receiver) {
const oldValue = target[key];
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
} else {
return Reflect.set(target, key, value, receiver);
}
}
});
}
五、 完整的响应式系统实现
// 完整的响应式系统
class ReactiveEffect {
constructor(fn) {
this.fn = fn;
}
run() {
activeEffect = this;
return this.fn();
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
return _effect;
}
// 响应式系统核心
const reactiveMap = new WeakMap();
function reactive(target) {
const existingProxy = reactiveMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = createReactiveObject(
target,
baseHandlers,
collectionHandlers
);
reactiveMap.set(target, proxy);
return proxy;
}
const baseHandlers = {
get(target, key, receiver) {
if (key === '__v_isReactive') return true;
const res = Reflect.get(target, key, receiver);
track(target, key);
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (hasChanged(value, oldValue)) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
};
// 测试用例
function testReactiveSystem() {
console.log('=== Testing Reactive System ===');
// 测试 reactive
const state = reactive({ count: 0, user: { name: 'John' } });
effect(() => {
console.log('Count changed:', state.count);
});
effect(() => {
console.log('User name:', state.user.name);
});
state.count = 1;
state.user.name = 'Jane';
// 测试 ref
const count = ref(0);
effect(() => {
console.log('Ref count:', count.value);
});
count.value = 10;
}
// 运行测试
testReactiveSystem();
六、关键点解析
ref
的本质:- 它是一个对象,这个对象有一个
.value
属性。 - 我们对
.value
的读写进行拦截,从而让原始值具备了响应性。 __v_isRef
是一个标志位,告诉reactive
系统“这是一个 ref,请在访问时自动解包”。
- 它是一个对象,这个对象有一个
reactive
的深度响应式:- 在
get
拦截器中,如果发现获取的值是一个对象,会递归调用reactive
,确保嵌套对象也是响应式的。
- 在
自动解包 (Auto-unwrapping):
- 当
ref
被放入reactive
对象中时,在模板或effect
中访问user.age
时,reactive
的get
拦截器检测到age
是一个ref
(通过__v_isRef
),于是返回age.value
,实现了自动解包。这是 Vue 3 模板语法简洁的关键。
- 当
computed
的惰性求值:computed
返回的也是一个ref
。- 它内部有一个
dirty
标志位,只有当依赖的数据变化时才标记为脏。 - 只有当有人读取
.value
且dirty
为true
时,才会重新执行getter
函数。
七、与真实 Vue 3 的差异
- 性能优化:真实的
trigger
有更复杂的调度机制(queueJob
)和flush
时机(pre
,post
,sync
)。 - 边界情况:真实实现处理了
Symbol
、in
操作符、has
拦截器、数组索引变化等。 shallowReactive
/shallowRef
:浅层响应式。toRefs
:解决解构失去响应性的问题。readonly
:只读代理。effectScope
:副作用作用域管理。
这个手写版本虽然简化,但已经完整地体现了 ref
和 reactive
的核心设计思想和交互逻辑,是理解 Vue 3 Composition API 响应式原理的绝佳起点。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/927461.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!相关文章
电子商务型网站建设免费自建网站
Java Web项目的层次结构及常见分包
Web项目中的层次 ControllerServiceDaoController层:表现层(视图)层。用来显示数据和接收用户数据Service层:业务逻辑层,用来处理页面。先写接口,后写实现类Dao层&#…
如何网上快速接网站开发订单手机网站进不去怎么办
场景介绍
小明接到学校老师安排的任务,需要批量将班级里同学们拍的普通照片转换为素描图,供课堂游戏使用,于是求助到程序员老爸,机智的程序员老爸分分钟用几行Python代码解决:在阿里云Serverless函数计算服务中部署普…
临安市住房和建设局网站深圳市网站推广公司
一. 背景 距离上一篇JS文章已经20天,经重新总结发现,上一篇概况的有点浅显,适合初学js的入门了解,但对于已经学习js一段时间的人,或者是想系统的了解JS体系,接下来的文章可能会更有帮助。 该系列博客的书写…
荣县住房和城乡建设厅网站wordpress收费缓存插件
文章目录 一、实验背景与目的二、实验拓扑三、实验需求四、实验解法1. PC 配置 IP 地址2. PC3 属于 Vlan10,PC4 属于 Vlan20,配置单臂路由实现 Vlan10 和 Vlan20 三层互通3. 测试在 PC3 上 Ping PC4 ,可以 Ping 通 PC4 摘要: 本文…
【自然语言处理】文本规范化知识点梳理与习题总结 - 教程
【自然语言处理】文本规范化知识点梳理与习题总结 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas…
Rocky Linux 8 远程管理配置指南(宿主机 VNC + KVM 虚拟机 VNC) - 指南
pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …
公司做网站能抵扣进项税吗西安便宜做网站
思路:无论vue还是react打包都会有dist文件夹,内部有index.html。我是想根据index.html中的script src地址是否改变,判断项目是否有新内容。
具体代码如下
首先先拿到生产环境中index.html文本,由于是单页面应用使用fetch(/?_st…
西安企业网站制作公司wordpress 前端优化
接上一篇:企业实战_04_MyCat常用配置文件详解 https://gblfy.blog.csdn.net/article/details/100112080 文章目录1. 加密简述2. 加密目录3. 执行加密4. 添加加密属性5. 添加密文6. 测试是否可用声明:需要提前安装mysql Linux centos7 安装 MySQL5.7.x 1. 加密简述
…
网站云空间大小flatsome wordpress
MySQL 删除操作和连接类型详细讲解和案例示范
DDL(Data Definition Language,数据定义语言)是用于创建和修改数据库结构的语句,包括创建表、索引、视图,以及修改这些结构。本文将详细介绍MySQL DDL语句的常见用法&…
邮票收集问题正推证明
参考文献。
(题目:有一个 \(n\) 面的骰子,扔到各面的概率相等。求期望扔几次可以使每一面都被扔到。)
设 \(f_i\) 表示已经扔到过 \(i\) 个不同的面时,期望的扔的次数。
称事件 \(A\) 为扔到了已经扔过的面,事件…
深入解析:Playwright MCP浏览器自动化详解指南
深入解析:Playwright MCP浏览器自动化详解指南2025-10-04 18:59
tlnshuju
阅读(0)
评论(0) 收藏
举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display:…
分布式加载网站的静态seo搜索引擎优化内容
外卖系统源码解读:校园外卖APP开发全攻略
今天,小编将深入解读外卖系统的源码,详细介绍如何开发一款功能齐全的校园外卖APP,帮助开发者快速上手,打造出高质量的外卖应用。
一、需求分析 应具备以下基本功能ÿ…
大作设计网站好玩的网页传奇游戏
标题:Redis缓存一致性难题:如何让数据库和缓存不“打架”?(附程序员脱发指南) 导言:当数据库和缓存成了“异地恋”
想象一下:你刚在美团下单了一份麻辣小龙虾,付款后刷新页面&#…
2025多校冲刺CSP模拟赛2 2025.10.4 模拟炸
rt:炸了
T1 查询
题面
赛时
疯狂排序!!疯狂贪心!!疯狂分讨!!疯狂星期四六!!(大雾)
无果。死了。
打了暴力32pts遗憾离场
正解
二分答案!闪亮登场!
考虑比较元素为\(a_i+b_i*c_j\)形如一次函数\(y=kx+b\),
…
2025 年 9 月习题集
2025年9月习题集P5933 [清华集训 2012] 串珠子。简单的图计数。
P8329 [ZJOI2022] 树。DP。
P6646 [CCO 2020] Shopping Plans。堆,最优化。
P7470 [NOI Online 2021 提高组] 岛屿探险。分治,01-Trie。
P4809 [CCC 2…
实用指南:Linux整个系统权限玩坏了怎么办
pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …
工业云网站建设为什么网站打开是空白
题意
给定两个人相互打电话,如果a打给b,b打给c,c打给a,则说a,b,c在同一电话圈中。给出n个人的m次通话,输出所有的电话圈
思路
用graph[u][v]1表示u和v之间有打电话。在使用floyd算法计算所有的点对之间的值。graph[u][v]1表示u,v之间有直接…
asp.net 网站开发 教程网页翻译器在线翻译
Amundsen 是一个用于数据发现和元数据管理的开源平台。Amundsen是一个用于提高数据分析师、数据科学家和工程师在与数据交互时的生产力的数据发现和元数据引擎。目前,它通过索引数据资源(表、仪表板、流等)并基于使用模式(例如,高频查询的表会比低频查询的表更早显示)提供…
做足球采集软件和预测软件的网站网站建设新闻发布注意什么
无向图概念时间戳\(dfn[x]\),在深度优先遍历中,按照每个节点第一次被访问的顺序,依次做整数标记追溯值\(low[x]\),通过非搜索边能到达的最小时间戳割边判定法则无向边\((x,y)\)是割边/桥,当且仅当存在x的一个子节点满足\(dfn[x] < low[y]\…