refresh和reload,retreat和refuge

ref 和 reactive 可以称做是 Vue 响应式 API 中帝国双璧,一时瑜亮。但问题也来了,为什么整俩?怎么选?最近在开发中遇到了这个问题,来不及看

Ref 和React 堪称Vue 响应式API 的孪生宝石。

但这也引出了一个问题:为什么是这两个?如何选择?

26e8e65c13454f1c815709612506e44c~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=KfXtrukhv4hjjNOuFoUMt6cjJm4%3D

我最近在开发过程中遇到了这个问题。我没有时间阅读Vue 文档,因此我查找了前4 个“ref reactive”主题来寻找答案。

473adfe8c79f44f58a253e181949ff9e~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=8%2BE8UWUJvSkJBTN4ndDg5H%2Bkzss%3D

从广义上讲, ref 工作得很好,但没有清楚地解释它为什么工作。列出几个场景对比,主要是基于我对Vue文档的理解和使用经验。 这显然还不够。我们可以知道发生了什么,但没有人能告诉我们原因。

当时时间很紧,所以我只能随机选择一个ref并推送代码。

然而,“自然是夺不走的,人心是有疑虑的!”如果我们不尝试更深入地理解它,说不定有一天我们又会掉进坑里,我们不能只是加班加点,慢慢地爬出来。长期解决方案。 请利用这种先进的杀戮能力并研究它。

acae13fbc1234245b2087aebf7575244~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=p1EkVYNHaBSN2kLUbUyIkzYD5R4%3D

当我遇到问题追求速度的时候,我只能直接而嚣张地寻找答案。现在事情已经平静下来了,是时候回到老派了,先阅读官网的文档,然后使用调试器分析源代码。

我仔细阅读了Vue官方文档关于响应式基础知识和详细响应式系统三遍,并在本地运行了内部演示。

看完这两篇文章,最大的好处就是我现在可以自己写参考代码和计算代码了。这是Vue 文档威力的第一次展示。竖起大拇指。

等一下,回到你的初衷:“引用还是反应?”本文档的标准答案是:

这是因为reactive() API有一些限制。

仅支持有限的值类型,即对象类型;不支持基本类型(数字、字符串、布尔值、未定义、空)。 您无法替换整个对象。如果不更换,响应将无效。 这不适合分解操作,分解后响应将会失败。

因此,建议使用ref() 作为声明反应式状态的主要API。

Vue官方文档《响应式基础》

显然这个答案并不能解决我的困惑。

如果Reactive不支持基本的数据类型,那有什么难的呢?只要包裹一层ref这样的对象,写成({value})就可以了。说到无法替换整个对象,reactive返回的Proxy本身就是一个reactive对象,所以替换ref返回的RefImpl实际上是行不通的。引用已经被替换了,但是我们不能禁用响应吗?与2类似,拆除不友好。综上所述,如果基于这种差异,我们必须将一个响应式功能强行放入两个API 中,那么Vue 就不可能达到目前全球排名前二的前端框架的地位。

这个问题没有办法解决,我来解决。

96835cc904804e39833a0d120b48eb45~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=CVtdczHJPuiKleLP4ZmF%2BSifb5Y%3D

要解开铃铛,你需要系上它。唯一破译的答案必须在Vue 的源代码实现中找到。不过,源代码与短文不同,它不是像流水一样从上到下流动,而是像网络一样来回流动,所以直接阅读它会让你怀疑人生。

首先,为什么不打印ref 和reactive return 日志来感受一下它们有什么区别?

49233593efbb417b9e76f1f378265c00~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=Rw5ngHe1%2F0bnOf%2B4heNH%2FmstXqw%3D

对象数据类型

基本数据类型

参考

RefImpl {_value: 代理(对象)}

RefImpl {_value: 值}

反应性的

代理(对象)

值,无响应

如果该值是对象数据类型,则ref API 返回RefImpl。这里,_value是对象代理Proxy,与反应式API返回值相同。

如果该值是基本数据类型,则ref 返回RefImpl。这里,_value是对应的基本数据类型,与reactive返回的值相同。请注意,reactive 返回的值当前是无响应的。

此时,你可以说“ref底层是reactive的实现,如果reactive更强大,就用reactive”或者“reactive是ref的子集,如果ref很大,就用ref”,有些朋友可能会想得出这样的结论那。 其实很多前端朋友都这么说,网上也有一些文章说同样的话,但总体来说赞成ref的声音比较高。

再说一次,我仍然不相信Vue 没有两个反应式API 能够为广大前端开发爱好者找到一些东西,就是因为这个原因。

784a69def7d245aca59416b27b77992f~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=Ow7KTppiWdt3FeVvj%2BzHPe99ILs%3D

基本型没什么好说的。反应式使用对象代理,并不是为了将基本类型包装在对象层中而设计的。如果你想使用reactive,只需手动将其包装成一个对象即可。使用反应式({count})。

可以看到需要扩展对象类型。从日志输出中可以看到,ref返回的RefImpl除了_value之外,还多了四个值,指向代理对象Proxy:dep、__v_isRef、__v_isShallow、_rawValue。这些是做什么用的?

要回答这些问题,您需要调试器源代码。

研究过源码的朋友都知道,如果只是看源码的话,很可能就要从头到尾看一遍。 我的做法是,“为了避免在大量代码中迷失,首先想出一个想法,明确方向,设定一个目标。通过设定目标,你可以达到事半功倍的效果。” ‘ 你可以得到它。” 而且,不要只阅读静态源代码,要有想法,在运行和调试时理解和想象它们。 “读”万卷书,不如“行”万里路。

为了理解这个想法,让我们继续上面的内容,首先看看我们如何从详细的反应式系统创建引用和计算代码实现。

让我们先举一个同时使用ref 和Computed 的小栗子。

133c22167c4241ee9870f429d738a817~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=2JFSm5Vn9QHCif4Q8guD516RTX0%3D

单击上方按钮将显示已累加1 的值,单击下方按钮将显示上述累加值的2 倍的值。

好消息是,Vue 官网介绍了直接从html 导入vue.global.js csn 代码的方法,可以直接下载到本地导入。 这非常有用。那么此时,Vue源码分析就像放在口袋里一样简单。

这个例子就不多说了。继续查看代码。

3bef051ad2224974ba76303205ecbb96~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=sKPUXqRt9gABbthi3vuBp6iHgZ4%3D

注:为了节省空间,我直接去掉了样式标签。有关源代码详细信息,请参阅diyVueV0RefCompulated.html。

上面的例子中,需要完成两个特征点:

如何更新DOM,以便在单击按钮时出现新数据? countA0Ref 数据更改后,如何监控countA1CompulatedRef 并重新调用compute 中的函数来更新值?问题1官方文档没有提到这一点。我在vue.global.js源码中搜索了关键字mount和render,并尝试了几次。 我首先使用app.unmount() 和app.mount() 发现错误。然后我尝试了context.reload() 并发现它不起作用。然后我尝试了_instance.update()。我真的很幸运。

如何通过直接访问代码来获取_instance我就不详细说了。运行日志或调试器以查看更多信息。

b0c6a2bfa79e43cd912d0f43b01c2360~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=nLo8GHLBZpyqzs7wYBrutGTDn6o%3D

问题2 深入探讨了响应式系统,因此必须运行文档中的示例以进一步加深您的理解和想象力。

Refs 很容易实现,只需包装一层对象并通过覆盖set 和get value 属性来监听它们即可。

a8ecb826ca0946fe8a2bf78d4b4e7bd3~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=vK7VdQZFqEaU8mqLxIiBwJP8MiA%3D

这里有趣的是第三行。这意味着refObj 不需要在模板中尾随花哨的.value ,但在JS 代码中需要。你知道为什么吗?

1dac8038310f44788438adf38452acb5~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=v0EzbooIUW7m7rktzqd6idwwn7Y%3D

当Vue 将模板转换为DOM 操作时,当遇到__v_isRef 为true 的对象时,它会自动添加一个可爱的.value 尾部。这相当于一些语法糖,给程序员枯燥的生活增添了一点甜蜜。

countA1CompatedRef 是如何重新计算的?

在提出问题之前,请先考虑一下。如果我要做的话,我会怎么做? 第一个想法是找到目标函数的源并重新触发执行,就像在调试器的堆栈中一样,但我到底该怎么做呢?函数执行还需要上下文参数,但是更改如何找到并重新分配给定的值?价值?

46e895f17af34c82983f479f5d4eecc1~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=8a8T37o63jg55zM%2B8w%2B2WfEzyKg%3D

这是Vue 响应式API 的最后一步。在运行函数时主动记录副作用,因为它们在调用时很难发现。全局变量activeEffect用于记录当前执行的副作用函数(即计算函数对应的函数)。副作用函数执行完毕后,activeEffect被清零。

调用refObj.value时拦截判断当前activeEffect是否非空。如果activeEffect不为空,则表示activeEffect的副作用函数访问当前的refObj.value并记录activeEffect。

当重新分配refObj.value 时,访问阻塞期间记录的所有副作用函数都会再次执行,完成响应跟踪触发。

这应该可以帮助您理解为什么计算函数的输入函数中没有参数。事实上,这主要是为了方便直接执行副作用函数,而不是传递外部记录函数的上下文。使用参数时,建议直接在函数内传递“闭包”。

1236e4812e774b9ba1c83db15ca48942~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=uMkglth%2Bq4lyRxD1L1o5HsB7Wec%3D

如果您细心的话,您会注意到有一个您以前从未见过的queueFlush() 函数。这里,副作用并不是在设定值截距内立即触发,而是在下一个微任务中执行。执行集重复数据删除过滤可防止副作用函数多次执行并浪费性能。

716b93516baa417bbfff30d874f21ba4~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=s8vI5zIYvSKZ2t153%2BZRS%2FJyOgQ%3D

有关计算实现的初步参考和完整源代码,请参阅diy-vue.v0.global.js。

请运行并检查。

387d7710667948b99580d310e5af312c~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=nASAE0WBbsX7JESXMgumaZXp46A%3D

看到这里,我认为Vue 中的响应性原则应该已经很完整了。 再次回到原来的问题。 “ref还是reactive?”上面只描述了ref的实现,还没有反应。 但按理说,两者都是响应式API,它们的实现应该是相似的。

让我们使用小栗子继续我们的逐步方法,并分别定义ref(count) 和re。

active({count}),绑定到 <div> 标签展示,并且点击 count++。 点击断点 debugger 走一遍。
bf6313f793a84f5280449a09e1ca9ec5~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=gW2kbJ7q0p2tg1mViKb96X24FpU%3D
7d96083feed04a9b8d71185a55d99a2b~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=VlGBWIs1zR5ck7hP1xx02qtT58o%3D
理解了 ref 和 computed 实现,再 debugger Vue 源码,会事半功倍,不用费多大劲就能猜出个七七八八,关键也就 3 点:
reactive 使用对象代理 Proxy 来监听属性访问变化,最大的优势是所有属性访问都能监听到,哪怕是深层对象属性变化,ref 对于基本数据类型只能监听到 value 属性变化,如果是对象数据类型,则直接调用 reactive 创建对象代理赋值给 value 属性。这也是大家所说的 ref 底层也是 reactive 实现的。reactive 因为用的是对象代理,不方便在原对象上记录额外的副作用记录信息,所以存了个全局变量 const targetMap = new WeakMap() ,数据结构是官方文档中提到的 WeakMap<target, Map<key, Set<effect>>>。ref 因为自身包了一层对象,所以副作用直接可以存储在该对象的 dep 属性里面,数据结构是 Set<effect>。如果 ref 参数是对象,如 const countObjRef = ref({count: 0}) ,即支持直接 countObjRef.value 访问修改(将会被 countObjRef 的 value 属性的 set 和 get 监听),也支持 countObjRef.value.count++(将会被 countObjRef.value 的对象代理 Proxy 监听)。 这点将非常关键,是回答 “ref 还是reactive?” 问题的突破口。其他逻辑不再赘述了,上 ref 和 reactive 实现代码
944d96b2fef542238019982b40bc9e49~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=TGLQsx6E3Bm0pTSps1cqcjEXpMo%3D
整个 ref、reactive 和 computed 完全源码详见 diy-vue.v1.global.js。
回到最初的问题,“ref 还是 reactive?”
与其说“ref 还是 reactive?”,不如说 “基础数据类型监听还是对象数据类型监听?” 这才是我一直苦苦寻找的“为什么 Vue 非要整俩响应式 API”的根源。 ref 因为内部做了两者兼顾,自然而然成了 Vue 官方推荐写法。
对这个问题,我的答案是:
如果响应数据是基本数据类型,建议使用 ref,即使多了个迷人的 .value 小尾巴,也还行。
如果是对象类型且仅内部修改这种简单场景,特别是深层对象,建议使用 reactive,毕竟 refObj.value.xxx 显然没有 reactiveObj.xxx 用的爽,使用 ref 本身也只是个壳,里面用的就是reactive,杀鸡焉用牛刀。
如果是对象类型且有重置清空这种复杂场景(常见表单和筛选项),那必须使用 ref 了,因为 reactiveObj 被重新赋值后,对象代理 Proxy 被清掉,此时已再无监听可能,最终导致响应失效。 但是对 refObj.value 重新赋值响应依旧有效,因为此时 refObj 对 value 属性的 set 和 get 监听依旧在。
7b557c59c4f54c779571565b63793fa2~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1717812595&x-signature=k%2BQvhgCzEwCnS4rGUeJZprFsbqQ%3D
当然了,如果你嫌混用麻烦且不想纠结这些技术细节,使用 ref 是相对较好的选择,一方面不会出问题,另外因为 ref 是 Vue 官网推荐写法,在多人协作开发情况下,随大流能提升代码可读性,有效降低学习成本和协作运维成本。
作者:盛书强链接:https://juejin.cn/post/7313549609179447306

原创文章,作者:小条,如若转载,请注明出处:https://www.sudun.com/ask/84996.html

(0)
小条's avatar小条
上一篇 2024年6月1日 上午10:09
下一篇 2024年6月1日 上午10:16

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注