一、 reactve
- 定义
reactive API的定义为传入一个对象返回一个基于原对象的响应式代理,即返回一个proxy,相当于Vue2.x中的Vue.Observer
。
优点
在Vue2.x中数据的响应式处理是基于
Object.defindProperty()
的,但他只会侦听对象的属性,不会侦听对象,在添加对象属性的时候需要:Vue.$set(object, 'name', 'lock li')
reactive
API则是基于ES2015 proxy
实现了对对象的响应式处理,在vue3.0中往对象中添加属性,并且这个属性也会具有响应式的效果:object.name = 'lock li'
注意点
① 使用
reactive
API时,setup
中返回的reactive
需要通过对象的形式:export default definComponent({ name: 'example', setup(props) { const obj = reactive({foo: 'bar'}) return { obj } } })
defineComponent
definComponent主要是用来帮助Vue在TS下正确推断出setup()组件的参数类型
export function defineComponent(options: unknown) { return isFunction(options) ? { setup: options } : options }
② 或者借助
toRefs
API包裹一下导出,(使用toRefs
包裹导出的我们可以使用展开运算符或解构接收):export default defineComponent({ setup() { let obj = { foo: 'bar' } obj = toRefs(obj) return { ...obj } } })
二、源码实现
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个判断逻辑,对
readonlyToRaw
、readonlyValues
、isRef
分别进行了判断,先不看这些判断逻辑,通常我们对reactive()
传入一个对象,可以直接命中createReactiveObject()
。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()
传入了4个参数,分别是:target
:我们定义reactive
时传入的对象toProxy
:一个空的WeakSet
toRaw
:判断传入的reactive
是否已经被代理baseHandlers
:一个已经被定义好的的具有get
和set
的对象,它看起来会是这样子:const baseHandlers = { get(target, key, value, 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); return observed;
首先判断
collectionTypes
中是否包含我们传入的target
的构造函数(构造器),而collectionTypes
是一个Set
集合主要包含:Set
Map
WeakSet
WeakMap
等4种集合的构造函数。如果
collectionTypes
中包含我们传入的构造函数,则将handlers
赋值为仅包含get
属性的collectionHanders
否则 赋值为baseHandlers
。两者的区别在于
collectionHandlers
仅含有get
属性,是用来留给不需要派发更新的变量使用的,例如我们常用的props
属性然后将
target
handlers
作为两个参数传入,使用替换了vue 2.x中的Object.defineProperty
的Proxy
代理函数,并实例化接下来两步也非常重要,将
target
和observed
作为键值对赋值到toProxy
,用于下次检测传入的target
是否已经被代理,并返回被代理的observed
对象:// target already has corresponding Proxy let observed = toProxy.get(target); if (observed !== void 0) { return observed; }
将
observed
和target
作为键值对赋值到toRaw
,用于下次检测传入的target
是否是一个代理对象,并返回代这个target
:// target is already a Proxy if (toRaw.has(target)) { return target; }
二、
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
使用
toRefs
其实是为了方便我们使用解构和展开运算符,具体代码:function toRefs(object) { if ((process.env.NODE_ENV !== 'production') && !isReactive(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`); } const ret = {}; for (const key in object) { ret[key] = toProxyRef(object, key); } return ret; } function toProxyRef(object, key) { return { _isRef: true, get value() { return object[key]; }, set value(newVal) { object[key] = newVal; } }; }
可以看到
toRefs
是在原有Proxy
的基础上,返回了一个所有属性带有get
、set
的对象,这样就解决了Proxy
对象遇到解构和展开运算符之后,失去响应性的问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。