文章转载自:https://zhuanlan.zhihu.com/p/85978064
我们阅读源码的原因是什么?无非是1:学习;2:更好的使用这个库。如果只是想大致的了解下原理,倒不必花时间阅读源码,几句话,几张图就能搞清楚,网上搜搜应该就有很多。因此,阅读源码的过程一定是要对不明白的地方深入了解,肯定是很费时间的。
在这过程中,有些知识点,跟库本身可能没什么关系,但如果不懂,又难继续理解。对于这些知识点,我会尽量少的解释,但会贴上尽量完善的文档,方便不了解的同学先阅读学习。
前言
在上篇文章中说道,ref是最影响源码阅读的文件。但如果不先搞明白它,看其他的只会更晕。我先帮大家理清ref的逻辑跟概念。
由于现在(2019/10/9)vue@3还未正式发版,大家还不熟悉其相关的用法。上篇文章虽然介绍了不少,但其实还是有不少疑问。在阅读本篇文章之前,如果有时间,建议先阅读Vue官方对Composition API的介绍: 1. Vue Composition API 2. Ref Vs Reactive
读完关于Composition API的介绍,会对了解本库有更多认识,便于更好的理解源码。
ref跟reactive是整个源码中的核心,通过这两个方法创建了响应式数据。要想完全吃透reactivity,必须先吃透这两个。
Ref
ref最重要的作用,其实是提供了一套Ref类型,我们先来看,它到底是个怎么样的数据类型。(为了更好的做解释,我会调整源码中的接口、类型、函数等声明顺序,并会增加一些注释方便阅读)
要想了解UnwrapNestedRefs与UnwrapRef,必须先要了解ts中的infer。如果之前不了解,请先阅读相关文档。看完文档,再建议去google一些案例看看加深下印象。
现在我们假设你了解了infer概念,也了解了它的日常用法。再来看源码:
如果还是懵,建议后续再去看看infer的相关介绍。在这我们直接抛结果:
Ref是这样的一种数据结构:它有个key为Symbol的属性做类型标识,有个属性value用来存储数据。这个数据可以是任意的类型,唯独不能是被嵌套了Ref类型的类型。 具体来说就是不能是这样 Array 或者这样 { [key]: Ref }。但很奇怪的是,这样Ref 又是可以的。具体为什么也不知道,所以我勇敢地提了个PR...
(果然Ref 是不够完美的,2019.10.10晚,我这PR被合并了。大家遇到疑问时,也可以勇敢的提PR,说不定就被合了....)
另外,Map、Set、WeakMap、WeakSet也是不支持解套的。说明Ref数据的value也有可能是Map这样的数据类型。
说回Ref,从上篇文章中,我们已经了解到,Ref类型的数据,是一种响应式的数据。然后我们看其具体实现:
其实最难理解的就在于这个ref函数。我们看到,这里也定义了get/set,却没有任何Proxy相关的操作。在之前的信息中我们知道reactive能构建出响应式数据,但要求传参必须是对象。但ref的入参是对象时,同样也需要reactive做转化。那ref这个函数的目的到底是什么呢?为什么需要有它?
在文章开头,我贴了这份官方介绍Ref vs Reactive,这其中其实已经说的很明白。
However, the problem with going reactive-only is that the consumer of a composition function must keep the reference to the returned object at all times in order to retain reactivity. The object cannot be destructured or spread:
对于基本数据类型,函数传递或者对象解构时,会丢失原始数据的引用,换言之,我们没法让基本数据类型,或者解构后的变量(如果它的值也是基本数据类型的话),成为响应式的数据。
// 我们是永远没办法让`a`或`x`这样的基本数据成为响应式的数据的,Proxy也无法劫持基本数据。const a = 1;const { x: 1 } = { x: 1 }
但是有时候,我们确实就是想一个数字、一个字符串是响应式的,或者就是想利用解构的写法。那怎么办呢?只能通过创建一个对象,也即是源码中的Ref数据,然后将原始数据保存在Ref的属性value当中,再将它的引用返回给使用者。既然是我们自己创造出来的对象,也就没必要使用Proxy再做代理了,直接劫持这个value的get/set即可,这就是ref函数与Ref类型的由来。
不过单靠ref还没法解决对象解构的问题,它只是将基本数据保持在一个对象的value中,以实现数据响应式。对于对象的解构还需要另外一个函数:toRefs。
通过遍历对象,将每个属性值都转成Ref数据,这样解构出来的还是Ref数据,自然就保持了响应式数据的引用。但是源码中有一点要注意,toRefs函数中引用的是toProxyRef而不是ref,它并不会在get/set中注入track跟trigger,也就是说,向toRefs传入一个正常的对象,是不会返回一个响应式的数据的。必须要传递一个已经被reactive执行返回的对象才能有响应式的效果。感觉这点可以优化,暂时也不知道小右这样做的原因是什么。由于这里会牵扯到track跟trigger,而这两个在我写本文时还没研究,就没胆子提PR了。
到这,我们就把ref的源码给看完了。