平时写vue的时候知道 props 有很多种用法,今天我们来看看vue内部是怎么处理 props 中那么多的用法的。
vue提供的props的用法
1. 数组形式
props: ['name', 'value']
2. 对象形式
对象形式内部也提供了三种写法:
props: { // 基础的类型检查 name: String, // 多个可能的类型 value: [String, Number], // 对象形式 id: { type: Number, required: true } }
props实现的原理
function normalizeProps (options: Object, vm: ?Component) { const props = options.props if (!props) return const res = {} let i, val, name if (Array.isArray(props)) { ... } else if (isPlainObject(props)) { ... } else if (process.env.NODE_ENV !== 'production') { ... } options.props = res }
normalizeProps 函数就是vue实际处理 props 的地方,从函数名的翻译我们可以看出该函数的功能就是标准化 props 的值。该函数主要分成3部分:① 从 options 对象中获取 props 的值并且定义一个res空对象;②几个 if ... else ,分别根据 props 值的不同类型来处理 res 对象;③ 用处理后的 res 对象覆盖原来 options 对象的 props 属性的值。
接下来看看那几个 if ... else 的代码:
if (Array.isArray(props)) { i = props.length while (i--) { val = props[i] if (typeof val === 'string') { name = camelize(val) res[name] = { type: null } } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.') } } }
这个代码实际就是处理props的值为数组的情况,例如: props: ['name', 'value'] 。使用while遍历该数组,如果数组内元素的类型不是字符串并且不是生产环境,那么就抛错:‘props的值类型为数组时,数组里面的元素的类型就必须是字符串'。如果是字符串的情况下,使用 camelize 函数处理一下 val 的值,并且赋值给 name 变量。这里的 camelize 函数的实际作用就是将 '-' 转换为驼峰。 camelize 函数具体的实现方式在后面分析。然后在 res 对象上面添加一个为 name 变量的属性,该属性的值为空对象 { type: null } 。
props: ['name', 'value'] 这种写法经过上面的处理后就会变成了下面这样:
props: { name: { type: null }, value: { type: null } }
接下来看看下面这个 else if(isPlainObject(props)) ,这里的 isPlainObject 函数实际就是返回 props 的值是否为 object , isPlainObject 函数的具体实现我们也在后面分析。
else if (isPlainObject(props)) { for (const key in props) { val = props[key] name = camelize(key) res[name] = isPlainObject(val) ? val : { type: val } } }
使用 for...in 遍历props对象,和上面一样使用 camelize 函数将 '-' 转换为驼峰。这里有个三目运算:
res[name] = isPlainObject(val) ? val : { type: val }
判断了一下 val 如果是 object ,那么在res对象上面添加一个为name变量的属性,并且将该属性的值设置为val。这个其实就是处理下面这种props的写法:
props: { // 对象形式 id: { type: Number, required: true } }
如果 val 不是 object ,那么也在res对象上面添加一个为name变量的属性,并且将该属性的值设置为{ type: val }。这个其实就是处理下面这种props的写法:
props: { // 基础的类型检查 name: String, // 多个可能的类型 value: [String, Number], }
经过处理后props会变成了下面这样:
props: { name: { type: String }, value: { type: [String, Number] } }
所以不管我们使用vue提供的 props 哪种写法,最终vue都会帮我们转换成下面这种类型:
props: { name: { ..., type: '类型' } }
接下来看看上面提到的util函数 isPlainObject ,先把源码贴出来。
const _toString = Object.prototype.toString export function isPlainObject (obj: any): boolean { return _toString.call(obj) === '[object Object]' }
其实 Object.prototype.toString.call(obj) 的值为obj对象的类型。例如:
Object.prototype.toString.call({a: 1}) // [object Object] Object.prototype.toString.call(new Date) // [object Date] Object.prototype.toString.call([1]) // [object Array] Object.prototype.toString.call(null) // [object Null]
接下来看看上面提到的util函数 camelize ,还是先把源码贴出来。
export function cached<F: Function> (fn: F): F { const cache = Object.create(null) return (function cachedFn (str: string) { const hit = cache[str] return hit || (cache[str] = fn(str)) }: any) } const camelizeRE = /-(\w)/g export const camelize = cached((str: string): string => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') })
这里定义了两个函数,分别是 cached 和 camelize ,其中 camelize 就是我们上面调用的, cached 是在 camelize 函数内部调用的。