一、 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
对象遇到解构和展开运算符之后,失去响应性的问题。
网友评论