这是我参与11月更文挑战的第30天,活动详情查看:2021最后一次更文挑战
前文通过normalizeProps
函数,已经完成了数组和对象两种形式定义的props
统一格式化为对象格式,接下来看一下如何初始化props
的
在初始化过程中执行initProps
函数对props
进行初始化,传递当前实例vm
和全部props
,初始化initProps
函数:代码如下,核心部分参考注释
const initProps = (vm, propsOptions) => {
const propsData = vm.$options.propsData || {};
const props = (vm._props = {});
// 将props的key进行缓存
const keys = (vm.$options._propKeys = []);
// 是否是根元素
const isRoot = !vm.$parent;
// 根实例对象的props应该被转变
if (!isRoot) {
toggleObserving(false);
}
// 遍历经过 normalizeProps 格式过的对象
for (const key in propsOptions) {
// 将key存储
keys.push(key);
// 获取props的值
const value = validateProp(key, propsOptions, propsData, vm);
// 需要将props进行数据劫持
defineReactive(props, key, value);
// 代理props
if (!(key in vm)) {
proxy(vm, `_props`, key);
}
}
toggleObserving(true);
};
toggleObserving作用
toggleObserving
函数做了什么事情
let shouldObserve = true;
const toggleObserving = value => {
shouldObserve = value;
}
在initProps
中调用toggleObserving
两次,当是根元素时设置shouldObserve
为false
,否则为true
想要搞明白toggleObserving
的作用,需要看一下在数据劫持入口observe
函数的核心流程
const observe = (value) => {
let ob;
if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (shouldObserve) {
ob = new Observer(value);
}
};
可以看到shouldObserve
为true
时才会进行新的数据劫持流程
proxy作用
在initProps
过程中,使用proxy
对所有的属性进行代理,为什么需要代理每一个prop
呢?
若是不使用proxy
,那么当我们想要使用props
时需要这么使用
const vm = new Vue({
props: ["fooProps"],
});
// 通过 $options 获取
vm.$options.fooProps
// 通过 _props 获取
vm._props.fooProps
对于使用者而言是十分不便利的,因此需要使用proxy
对所有的props
进行代理,可以直接通过组件实例获取
vm.fooProps
proxy
函数的实现也是非常简单,本质是对所有的属性进行数据劫持,和对于data
、methods
等处理一致,因为他们也和props
存在一样获取方式的问题
const proxy = (target, sourceKey, key) => {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
};
validateProp作用
通过validateProp
函数可以将传递的属性值取出,然后在defineReactive
进行数据劫持时使用,在开始之前,先一起看看几个使用到的工具函数
- 对象是否拥有该key
const hasOwn = (obj, key) => {
return Object.prototype.hasOwnProperty.call(obj, key);
};
- 判断是否为同一数据类型
// 获取数据的类型
const getType = (fn) => {
const match = fn && fn.toString().match(functionTypeCheckRE);
return match ? match[1] : "";
};
// 是否为同一数据类型
const isSameType = (a, b) => {
return getType(a) === getType(b);
};
// 获取目标数据类型的索引:存在返回其索引,不存在则返回-1
const getTypeIndex = (type, expectedTypes) => {
if (!Array.isArray(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1;
}
for (let i = 0, len = expectedTypes.length; i < len; i++) {
if (isSameType(expectedTypes[i], type)) {
return i;
}
}
return -1;
};
kebab-case
格式转变为camelCasec
格式
const hyphenateRE = /\B([A-Z])/g;
export const hyphenate = cached((str) => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
});
validateProp
函数实现,核心逻辑结合注释
const validateProp = (key, propOptions, propsData, vm) => {
// 属性的配置项
const prop = propOptions[key];
// 对象是否拥有key
const absent = !hasOwn(propsData, key);
let value = propsData[key];
// 属性的类型是否为 boolean 类型
const booleanIndex = getTypeIndex(Boolean, prop.type);
// boolean 类型处理
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, "default")) {
// 默认设置为false
value = false;
} else if (value === "" || value === hyphenate(key)) {
// 不是string类型 设置为true
const stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0) {
value = true;
}
}
}
// 默认值处理
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key);
}
return value;
};
至此props
的整个初始化流程便已完成