了解vue响应式原理对童鞋,想必对defineReactive方法有印象,这是vue响应式的核心方法。在这个方法,给obj的每个key对应的都new了一个dep,这个dep保存在defineReactive闭包中。这个dep的作用非常明显,收集当前watcher,以便在触发obj属性set方法的时候通知watcher更新。
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) return;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
},
});
}
复制代码
但是从上面的defineReactive方法中可以看到,除了obj每个key对应的dep收集依赖(下面称为闭包中dep),还有一个 childOb.dep.depend()在收集依赖。
令人一头雾水的是,在reactiveSetter方法中,只有闭包中的dep在通知watcher, childOb.dep并没有通知watcher,而且这个dep也没必要通知watcher。那 childOb.dep的作用是什么?这里先说作用,是给Vue.set()方法用的。如果还不清楚Vue.set方法的童鞋,建议先了解Vue.set的原理。
我们先看看childOb是什么
从下面代码可以知道,childOb是Observer的实例,在constructor中给childOb添加了dep属性
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, "__ob__", this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
}
复制代码
我们举个实际的例子看看
data(){
return {
a: {
b: 1
}
}
}
复制代码
在vue初始化,会调用observe对data返回obj进行响应式处理(后面称为dataObj)。接着在new Observer中,会通过walk方法,循环♻️遍历拿到dataObj的每个key,进行defineReactive
export function observe(value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return;
}
let ob: Observer | void;
if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
复制代码
在对a属性defineReactive的时候,这里我们称a属性对应的val为aVal。 此时的childOb就是经过Observer包装过的aVal。childOb.value指向aVal,并且多了一个dep属性用来收集依赖。
下面是最核心的一行代码,通过def又给value(这个value是aVal)添加了__ob__属性,指向childOb自己。经过这一步骤,我们就可以通过aVal.ob.dep访问到aVal对应的dep。
def(value, "__ob__", this);
复制代码
众所周知,Vue不能检测到对象属性的添加,为了解决这个问题,Vue添加了一个Vue.set方法。我们看一下set方法
export function set(target: Array<any> | Object, key: any, val: any): any {
if (
process.env.NODE_ENV !== "production" &&
(isUndef(target) || isPrimitive(target))
) {
warn(
`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
);
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val;
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
const ob = (target: any).__ob__;
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== "production" &&
warn(
"Avoid adding reactive properties to a Vue instance or its root $data " +
"at runtime - declare it upfront in the data option."
);
return val;
}
if (!ob) {
target[key] = val;
return val;
}
defineReactive(ob.value, key, val);
ob.dep.notify();
return val;
}
复制代码
到这里想必大家都恍然大悟了,对某个对象进行Vue.set后,还需要通知watcher去更新。这个对象新增的属性虽然通过defineReactive进行了响应式处理,但是它对应dep存在闭包中,根本取不到。所以最后可以通过这个对象身上的__ob__对象,取出dep通知对应的watcher更新。
ob.dep.notify();
复制代码
从上面源码分析我们还可以知道一个注意点⚠️,Vue 不允许动态添加根级响应式属性
this.$set(this.data,”key”,value')
复制代码
这种写法是🈲️的,set方法在非生产环境会给出警告
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== "production" &&
warn(
"Avoid adding reactive properties to a Vue instance or its root $data " +
"at runtime - declare it upfront in the data option."
);
return val;
}
复制代码
原因就是初始化observe(this.data)的时候,我们无法通过返回的ob收集依赖。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。