今年,对于从事前端开发的同窗而言,非常期待的一件事就是 Vue3.0
的发布。可是,Vue3.0
离发布仍是有点时间的,而且正式发布也不表明咱们就立刻就能够用于业务开发。它还须要完善相应的生态工具。不过正式使用是一码事,咱们本身玩又是一码事(hh)。javascript
Vue3.0
特意准备了一个尝鲜版的项目供你们体验 Vue3.0
即将会出现的一些 API
,例如 setup
、reactive
、toRefs
、readonly
等等, 顺带附上Composition API文档 的地址,还没看过的同窗赶忙去 Get
,别等到发布才知道(笨鸟要先飞,聪明鸟那更要先飞是吧)。html
一样地,我也 Clone
了下来玩了一会,对这个 reactive API
颇感兴趣。因此,今天咱们就来看看 reactive API
是什么(定义)?怎么实现的(源码实现)?前端
reactive API
的定义为传入一个对象并返回一个基于原对象的响应式代理,即返回一个 Proxy
,至关于 Vue2x
版本中的 Vue.observer
。vue
首先,咱们须要知道在 Vue3.0
中完全废掉了原先的 Options API
,而改用 Composition API
,简易版的 Composition API
看起来会是这样的:java
setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } }
能够看到,没有了咱们熟悉的data
、computed
、methods
等等。看起来,彷佛有点 React
风格,这个提出确实当时社区中引起了不少讨论,说Vue
愈来愈像React
....不少人并非很能接受,具体细节你们能够去阅读 RFC 的介绍。react
回到本篇文章所关注的,很明显 reactive API
是对标 data
选项,那么相比较 data
选项有哪些优势?webpack
首先,在 Vue 2x
中数据的响应式处理是基于 Object.defineProperty()
的,可是它只会侦听对象的属性,并不能侦听对象。因此,在添加对象属性的时候,一般须要这样:git
// vue2x添加属性 Vue.$set(object, 'name', wjc)
reactive API
是基于 ES2015 Proxy
实现对数据对象的响应式处理,即在 Vue3.0
能够往对象中添加属性,而且这个属性也会具备响应式的效果,例如:github
// vue3.0中添加属性 object.name = 'wjc'
使用 reactive API
须要注意的是,当你在 setup
中返回的时候,须要经过对象的形式,例如:web
export default { setup() { const pos = reactive({ x: 0, y: 0 }) return { pos: useMousePosition() } } }
或者,借助 toRefs API
包裹一下导出,这种状况下咱们就可使用展开运算符或解构,例如:
export default { setup() { let state = reactive({ x: 0, y: 0 }) state = toRefs(state) return { ...state } } }
toRefs() 具体作了什么,接下来会和 reactive 一块儿讲解
首先,相信你们都有所耳闻,Vue3.0
用 TypeScript
重构了。因此,你们可能会觉得此次会看到一堆 TypeScript
的类型之类的。出于各类考虑,本次我只是讲解编译后,转为 JS 的源码实现(没啥子门槛,你们放心 hh)。
1.先来看看 reactive
函数的实现:
function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (readonlyToRaw.has(target)) { return target; } // target is explicitly marked as readonly by user if (readonlyValues.has(target)) { return readonly(target); } if (isRef(target)) { return target; } return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers); }
能够,看到先有 3 个逻辑判断,对 readonly
、readonlyValues
、isRef
分别进行了判断。咱们先不看这些逻辑,一般咱们定义 reactive
会直接传入一个对象。因此会命中最后的逻辑 createReactiveObject()
。
2.那咱们转到 createReactiveObject()
的定义:
function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) { if (!isObject(target)) { if ((process.env.NODE_ENV !== 'production')) { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; } // target already has corresponding Proxy let observed = toProxy.get(target); if (observed !== void 0) { return observed; } // target is already a Proxy if (toRaw.has(target)) { return target; } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target; } const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers; observed = new Proxy(target, handlers); toProxy.set(target, observed); toRaw.set(observed, target); return observed; }
createReactiveObject()
传入了四个参数,它们分别扮演的角色:
target
是咱们定义 reactive
时传入的对象toProxy
是一个空的 WeakSet
。toProxy
是一个空的 WeakSet
。baseHandlers
是一个已经定义好 get
和 set
的对象,它看起来会是这样:const baseHandlers = { get(target, key, receiver) {}, set(target, key, value, receiver) {}, deleteProxy: (target, key) {}, has: (target, key) {}, ownKey: (target) {} };
collectionHandlers
是一个只包含 get 的对象。而后,进入 createReactiveObject()
, 一样地,一些分支逻辑咱们此次不会去分析。
看源码时须要保持的一个日常心,先看主逻辑
因此,咱们会命中最后的逻辑,即:
const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers; observed = new Proxy(target, handlers); toProxy.set(target, observed); toRaw.set(observed, target);
它首先判断 collectionTypes
中是否会包含咱们传入的 target
的构造函数,而 collectionTypes
是一个 Set
集合,主要包含 Set
, Map
, WeakMap
, WeakSet
等四种集合的构造函数。
若是 collectionTypes
包含它的构造函数,那么将 handlers
赋值为只有 get
的 collectionHandlers
对象,不然,赋值为 baseHandlers
对象。
这二者的区别就在于前者只有 get,很显然这个是留给不须要派发更新的变量定义的,例如咱们熟悉的 props 它就只实现了 get。
而后,将 target
和 handlers
传入 Proxy
,做为参数实例化一个 Proxy
对象。这也是咱们看到一些文章常谈的 Vue3.0
用 ES2015 Proxy
取代了 Object.defineProperty
。
最后的两个逻辑,也是很是重要,toProxy()
将已经定义好 Proxy
对象的 target
和 对应的 observed
做为键值对塞进 toProxy
这个 WeakMap
中,用于下次若是存在相同引用的 target 须要 reactive,会命中前面的分支逻辑,返回定义以前定义好的 observed
,即:
// target already has corresponding Proxy target 是已经有相关的 Proxy 对象 let observed = toProxy.get(target); if (observed !== void 0) { return observed; }
而 toRaw()
则是和 toProxy
相反的键值对存入,用于下次若是传进的 target
已是一个 Proxy
对象时,返回这个 target
,即:
// target is already a Proxy target 已是一个 Proxy 对象 if (toRaw.has(target)) { return target; }
前面讲了使用 reactive
须要关注的点,说起 toRefs
可让咱们方便地使用解构和展开运算符,实际上是最近 Vue3.0 issue
也有大神讲解过这方面的东西。有兴趣的同窗能够移步 When it's really needed to use toRefs
in order to retain reactivity of reactive
value了解。
我当时也凑了一下热闹,以下图:
能够看到,toRefs
是在原有 Proxy
对象的基础上,返回了一个普通的带有 get
和 set
的对象。这样就解决了 Proxy
对象遇到解构和展开运算符后,失去引用的状况的问题。
好了,对于 reactive API
的定义和大体的源码实现就如上面文章中描述的。而分支的逻辑,你们能够自行走不一样的 case
去阅读。固然,须要说的是此次的源码只是尝鲜版的,不排除以后正式的会作诸多优化,可是主体确定是保持不变的。
推荐阅读下一篇《4k+ 字分析 Vue 3.0 响应式原理(依赖收集和派发更新)》
写做不易,若是你以为有收获的话,能够帅气三连击!!!