functionnormalizeProps (options: Object, vm: ?Component) { const props = options.props if (!props) return const res = {} let i, val, name if (Array.isArray(props)) { i = props.length while (i--) { val = props[i] if (typeof val === 'string') { name = camelize(val) res[name] = { type: null } } elseif (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.') } } } elseif (isPlainObject(props)) { for (const key in props) { val = props[key] name = camelize(key) res[name] = isPlainObject(val) ? val : { type: val } } } elseif (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "props": expected an Array or an Object, ` + `but got ${toRawType(props)}.`, vm ) } options.props = res }
functioninitProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } defineReactive(props, key, value, () => { 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 ) } }) } else { defineReactive(props, key, value) } // 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) } } toggleObserving(true) }
functiongetType (fn) { const match = fn && fn.toString().match(/^\s*function (\w+)/) return match ? match[1] : '' }
functionisSameType (a, b) { returngetType(a) === getType(b) }
functiongetTypeIndex (type, expectedTypes): number { if (!Array.isArray(expectedTypes)) { returnisSameType(expectedTypes, type) ? 0 : -1 } for (let i = 0, len = expectedTypes.length; i < len; i++) { if (isSameType(expectedTypes[i], type)) { return i } } return -1 }
getTypeIndex 函数就是找到 type 和 expectedTypes 匹配的索引并返回。
第一种情况没有写属性的值,满足 value === '',第二种满足 value === hyphenate(key) 的情况,另外 nickName 这个 prop 的类型是 Boolean 或者是 String,并且满足 booleanIndex < stringIndex,所以对 nickName 这个 prop 的 value 为 true。
接下来看一下默认数据处理逻辑:
1 2 3 4 5 6 7 8 9 10
// check default value if (value === undefined) { value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldObserve = shouldObserve toggleObserving(true) observe(value) toggleObserving(prevShouldObserve) }
functiongetPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { // no default, return undefined if (!hasOwn(prop, 'default')) { returnundefined } const def = prop.default // warn against non-factory defaults for Object & Array if (process.env.NODE_ENV !== 'production' && isObject(def)) { warn( 'Invalid default value for prop "' + key + '": ' + 'Props with type Object/Array must use a factory function ' + 'to return the default value.', vm ) } // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined ) { return vm._props[key] } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context returntypeof def === 'function' && getType(prop.type) !== 'Function' ? def.call(vm) : def }
if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } defineReactive(props, key, value, () => { 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 ) } }) }
关于 prop 的响应式有一点不同的是当 vm 是非根实例的时候,会先执行 toggleObserving(false),它的目的是为了响应式的优化,我们先跳过,之后会详细说明。
// 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) }
Vue.extend = function (extendOptions: Object): Function { // ... constSub = functionVueComponent (options) { this._init(options) } // ...
// For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) }
// ... returnSub }
functioninitProps (Comp) { const props = Comp.options.props for (const key in props) { proxy(Comp.prototype, `_props`, key) } }
// check default value if (value === undefined) { value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldObserve = shouldObserve toggleObserving(true) observe(value) toggleObserving(prevShouldObserve) }