# 问题
在 vue 组件中,对于 prop 多类型的写法 (opens new window)是:
export default {
props: {
text: {
type: [String, Number],
default: ''
}
}
}
最近看到一种写法:
export default {
props: {
text: {
type: String | Number,
default: ''
}
}
}
当以上面的写法进行 prop 类型校验时,竟然没有报错。
# 定位目标源码
在 main.js 替换 vue 引入为:import Vue from '../node_modules/vue/dist/vue.common.dev.js'
当 vue 实例化的时候,会调用_init
方法,该方法在initMixin
中挂载到 Vue 原型上;在执行_init
方法时会调用initState
方法,该方法主要对 vue 对象中的 props、data、methods等进行初始化处理;在initState
方法中有调用initProps
方法。
# 分析 initProps 源码
主要源码如下:
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);
/* istanbul ignore else */
{
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
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.
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop( key );
toggleObserving(true);
}
上面代码主要执行:
- 获取当前 vue 实例的 props 属性;
- 判断是否为根实例节点,是的话则调用
toggleObserving
将 shouldObserve 设置为 false,否则设置为 true; - 遍历 props 属性
- 调用
validateProp
进行 props 属性类型校验; - 将每个 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;
}
}
}
// 检验默认值
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
}
上面代码主要执行:
- 判断 type 是否为布尔值,是的话做特殊处理;
- 检验值。
回到最开始的例子,调试输出validateProp
方法中的 propOptions,会得到:
{
text: {
type: 0
}
}
可以看到,当前 prop 的 type 已经变成 0 了。
回到validateProp
函数中,当 type 为 0 的时候,代码逻辑不会进行布尔值校验,当前的值不为 undefined,自然也不会进行值的校验。最后,进入assertProp
函数。
# 分析 assertProp 源码
主要源码如下:
function assertProp (
prop,
name,
value,
vm,
absent
) {
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
);
return
}
if (value == null && !prop.required) {
return
}
var type = prop.type;
var valid = !type || type === true;
var expectedTypes = [];
if (type) {
if (!Array.isArray(type)) {
type = [type];
}
for (var i = 0; i < type.length && !valid; i++) {
var assertedType = assertType(value, type[i], vm);
expectedTypes.push(assertedType.expectedType || '');
valid = assertedType.valid;
}
}
var haveExpectedTypes = expectedTypes.some(function (t) { return t; });
if (!valid && haveExpectedTypes) {
warn(
getInvalidTypeMessage(name, value, expectedTypes),
vm
);
return
}
var validator = prop.validator;
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
);
}
}
}
该方法主要用于校验 prop 的值是否是正确的类型。
该方法对获取当前 prop 的类型列表进行遍历,然后判断是否为预定义的期望类型,获取 expectedTypes 列表;接着对 type 进行valid = !type || type === true;
处理,通过 valid 和 expectedTypes 对当前类型进行校验判断。
回到上面的例子,前面调试知道 type 为 0。那么在 assertProp 方法中,所有的校验都不会执行。
其实,造成 type 变成 0 是因为:String | Number
是二进制位或进行赋值运算 (opens new window)。
The operator ToInt32 converts its argument to one of 232 integer values in the range -231 through 231-1, inclusive. This operator functions as follows:
- Call ToNumber (opens new window) on the input argument.
- If Result(1) is NaN, +0, -0, +∞, or -∞, return +0.
按位或|
会将操作数转为 32 位带符号整型二进制序列(小数部分会去掉)进行按位或计算,返回十进制的数值。
转化 32 位带符号整型二进制序列逻辑为:
- 数值调用
Number
转化 - 字符串和 undefined 转为 NaN
- null 和 false 转为 0
- true 转为 1
回到上面的例子,String | Number
转化为 32 位带符号整型二进制序列都为 NaN,根据 2 可知返回结果为 0。