# 其他选项的初始化及实现
再看下initState
,还有初始化props,methods
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
# props 初始化/实现
# props初始化
首先会对props 规范化
props: ["data1"]
//规范
props: {
data1:{
type: null
}
}
props: {
data2: Number
}
//规范
props: {
data2: {
type: Number
}
}
再看 initProps
函数
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}//传递的props data
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//是否根组件 无$parent
// root instance props should be converted
/**
props 是来自来自父组件的数据, 这个数据如果是一个对象(包括纯对象和数组),
那么它本身可能已经是响应式的,以不再需要重复定义
*/
if (!isRoot) {
toggleObserving(false) //在定义 props 数据时,不将 prop 值转换为响应式数据
}
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) //hyphenate 将 prop 的名字转为连字符加小写的形式
if (isReservedAttribute(hyphenatedKey) || //是否是保留的属性
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
//第四个参数是自定义setter ,在你尝试修改 props 数据时触发,并打印警告信息提示你不要直接修改 props 数据
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`something: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
//件实例对象上定义与 props 同名的属性,先判断是否已有同名属性
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true) //再开启 不影响其他功能
}
export function validateProp (
key: string,
propOptions: Object,//props 选项对象
propsData: Object, //props 数据来源对象
vm?: Component
): any {
const prop = propOptions[key]
const absent = !hasOwn(propsData, key) //是否传递的prop
let value = propsData[key] //没传就undefined
// boolean casting
//查找第一个参数所指定的类型构造函数是否存在于第二个参数所指定的类型构造函数数组中
//如果 getTypeIndex 函数第一个参数所指定的类型构造函数存在于第二个参数所指定的类型构造函数数组中,
//那么 getTypeIndex 函数将返回第一个参数在第二个参数数组中的位置,否则返回 -1
function isSameType (a, b) {//给定的两个类型构造函数是否相同
return getType(a) === getType(b)
}
function getType (fn) {
const match = fn && fn.toString().match(/^\s*function (\w+)/)
return match ? match[1] : ''
}
function getTypeIndex (type, expectedTypes): number {
if (!Array.isArray(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1 //类型相同 0,
}
for (let i = 0, len = expectedTypes.length; i < len; i++) {
if (isSameType(expectedTypes[i], type)) {
return i
}
}
return -1
}
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {// props 时指定了 Boolean
if (absent && !hasOwn(prop, 'default')) { //外界没有为组件传递该 prop,并且该 prop 也没有指定默认值
value = false
} else if (value === '' || value === hyphenate(key)) { //驼峰转连字符后与值为相同字符串的 prop
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {//没有为该 prop 指定 String 类型,虽然定义了 String 类型,但是 String 类型的优先级没有 Boolean 高
value = true //设为true boolean
}
}
}
// 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) //将 value 定义为响应式数据
toggleObserving(prevShouldObserve)
}
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))//跳过weex
) {
assertProp(prop, key, value, vm, absent)//校验
}
return value
}
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// no default, return undefined 没设置默认值
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== 'production' && isObject(def)) {//prop 默认值是对象类型 合法的为 default(){return {a:1}}
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 && //当前组件处于更新状态,且没有传递该 prop 数据给组件
vm.$options.propsData[key] === undefined && //上一次组件更新或创建时外界就没有向组件传递该 prop 数据
vm._props[key] !== undefined //prop 存在非未定义的默认值
) {
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
//由于默认值可能是由工厂函数执行产生的,所以如果 def 的类型是函数则通过执行 def.call(vm) 来获取默认值,否则直接使用 def 作为默认值
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
assertProp
才是真正校验prop
function assertProp (
prop: PropOptions,//该prop的定义对象
name: string,
value: any,
vm: ?Component,
absent: boolean //代表外界是否向组件传递了该 prop 数据
) {
if (prop.required && absent) {//必须传,而没有传,报警告
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
if (value == null && !prop.required) {//非必须
return
}
let type = prop.type
let valid = !type || type === true //代表着类型校验成功与否
const expectedTypes = []
if (type) {
if (!Array.isArray(type)) {//不是数组,包装成数组
type = [type]
}
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i])
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid //用验证结果的valid重写valid变量
}
}
if (!valid) {//未通过验证
warn(
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}` +
`, got ${toRawType(value)}.`,
vm
)
return
}
const validator = prop.validator//自定义校验规则
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
}
const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
function assertType (value: any, type: Function): {
valid: boolean;
expectedType: string;
} {
let valid
const expectedType = getType(type)
if (simpleCheckRE.test(expectedType)) {//String|Number|Boolean|Function|Symbol 可以通过typeof判断
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {//不符
valid = value instanceof type //instanceof 再判断
}
} else if (expectedType === 'Object') {
valid = isPlainObject(value)//使用 isPlainObject 函数检查该 prop 值的有效性
} else if (expectedType === 'Array') {
valid = Array.isArray(value)
} else {
valid = value instanceof type
}
return {
valid,
expectedType
}
}
# methods 初始化及实现
打开src/core/instance/state.js
,initMethods
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {//方法为null
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {//已经在props中定义 props 选项的初始化要先于 methods 选项
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {// 保留关键字 _或者$开头的方法,与Vue 原生提供的内置方法冲突
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)//为null 则添加一个空函数,否则bind(methods[key], vm)
}
}
# provide 初始化及实现
在_init
中
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
//发现 initInjections 函数在 initProvide 函数之前被调用,inject 选项的数据需要从父代组件中的 provide 获取
打开src/core/instance/inject.js
,找到 initProvide
//组件实例对象上添加了 vm._provided 属性,并保存了用于子代组件的数据
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function' //provide 选项可以是对象,也可以是一个返回对象的函数
? provide.call(vm) //如果是函数则执行该函数获取数据
: provide //provide 本身作为数据
}
}
再看 'initInjections`
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
//响应式定义的开关,不会将该属性的值转换为响应式
toggleObserving(false)
//遍历 result 常量并调用 defineReactive 函数在当前组件实例对象 vm 上定义与注入名称相同的变量,并赋予取得的值
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
//根据当前组件的 inject 选项去父代组件中寻找注入的数据,并将最终的数据返回
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol// Symbol 类型与 Reflect.ownKeys 可用且为宿主环境原生提供
? Reflect.ownKeys(inject).filter(key => {
/* istanbul ignore next */
return Object.getOwnPropertyDescriptor(inject, key).enumerable
})
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = inject[key].from //在选项合并中会规范为 'data1': { from: 'data1' },
let source = vm
while (source) {
//检测了 source._provided 属性是否存在,并且 source._provided 对象自身是否拥有 provideKey 键
//inject 选项查找数据时 provide 提供的数据还没有被初始化,所以当一个组件使用 provide 提供数据时,该数据只有子代组件可用。
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent//引用父组件,以及类推就完成了向父代组件查找数据的需求
}
if (!source) {
if ('default' in inject[key]) { //default
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}