初始化输入属性。主要包含两个过程
- 规范化props。normalizeProps。
- 初始化props。initProps
1、规范化props。
function normalizeProps(options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
// props声明有两种方式,列表和对象形式
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
// 如果是属性不是对象,构造出一个对象,含有type属性,
// 比如:props:{propA:Boolean} 最终转化为 props:{propA:{type:Boolean}}
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else {
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}
规范化props,最终转化成每个属性都是对象的大对象。便于后面的初始化。
2、初始化props值。
function initProps(vm, propsOptions) {
var propsData = vm.$options.propsData || {};
var props = vm._props = {};
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
var keys = vm.$options._propKeys = [];
var isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
var loop = function (key) {
keys.push(key);
// 校验输入属性
var value = validateProp(key, propsOptions, propsData, vm);
{
// 判断props的属性(小驼峰或者烤串模式经过转换)是否是html等关键字,不允许是关键字
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
// props属性的响应式注册
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 代理vue实例的所有属性,并添加到_props
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop(key);
toggleObserving(true);
}
初始化props的关键方法是validateProp
function validateProp(
key,
propOptions,
propsData,
vm
) {
var prop = propOptions[key];
var absent = !hasOwn(propsData, key);
var value = propsData[key];
//
var booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false;
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
var stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true;
}
}
}
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key);
// since the default value is a fresh copy,
// make sure to observe it.
var prevShouldObserve = shouldObserve;
toggleObserving(true);
observe(value);
toggleObserving(prevShouldObserve);
}
{
assertProp(prop, key, value, vm, absent);
}
return value
}
validateProp方法主要有两步:
- 通过propsData取值。
- 如果获取不到,则获取自身的default属性值。
那么propsData是什么?可以看如下源码:
function initInternalComponent(vm, options) {
var opts = vm.$options = Object.create(vm.constructor.options);
// doing this because it's faster than dynamic enumeration.
var parentVnode = options._parentVnode;
opts.parent = options.parent;
opts._parentVnode = parentVnode;
var vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
...
}
propsData是父组件的属性值,这也就解释了父组件向子组件通信的机制。
言归正传,validateProps里面有些细节需要说明一下:
var booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false;
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
var stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true;
}
}
}
关于这一段,大致是这个意思:
1、首先判断prop是否布尔类型或者是否存在布尔类型(因为prop可以声明多种类型)
2、如果存在,进入下一个判断
3、如果父组件没有该属性,且当前组件没有默认值,把值设置为false
4、如果值为空字符串,或者值(烤串形式的值)等于当前属性,进入下一判断
5、判断该属性是否存在字符串类型,如果不存在,或者字符串类型优先级小于布尔类型优先级,把值设置为true
6、不是以上情况的,使用原值
以上第4步,空字符串的情况实际上是这种写法:
<IButton disabled></IButton>
这里的disabled只写属性的时候,就是这种情况,注意这不是directive。
当获取默认值的时候,也有细节需要注意,如果默认值是一个对象,那么需要给对象做响应式处理,也就是下面源码:
...
observe(value);
...
而对每一个prop的响应式处理是在initProps方法的源码里面:
function initProps(vm, propsOptions) {
...
// props属性的响应式注册
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
...
}
3、props校验 其实可以在源码种看到,prop还有个校验的过程:
function validateProp(
key,
propOptions,
propsData,
vm
) {
...
{
assertProp(prop, key, value, vm, absent);
}
...
}
assertProp就是校验prop,这个方法里面先根据prop.type注册校验器,最后再执行校验器,prop里面可以设置validator属性作为校验器的。
4、总结
初始化prop主要就是这些过程,有些细节可能还没理解到位,以后继续完善。