初始化inject
export function initInjections (vm: Component) {
// resolveInject 通过用户配置的inject,自底向上搜索可用的注入内容,把搜索结果返回
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 设置 接下来shouldObserve 为false 不会设置响应式
toggleObserving(false)
// 把 每一项都 设置 到 vue实例上
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)
}
}
resolveInject 的作用是 通过用户配置的inject,自底向上搜索可用的注入内容,把搜索结果返回。
在循环注入前设置了
toggleObserving(false)循环结束之后,设置
toggleObserving(true)`
其作用是通知 defineReactive 函数不要将 内容转换为响应式。 其原理就是将值转换为响应式之前,判断 observerState.shouldConvert属性即可。
即 能通过访问 vm[key] 得到值,但是 不能是响应的,不然在当前实例 修改 vm[key] 会修改 provide 提供 的值
result[key] 即 provide提供的值 是响应式的,这样再 provide中 值变化的时候, 在当前实例 可以得到 更新。
resolveInject 的原理是 如何 自底向上搜索可用的注入内容的呢?
主要思想是: 读出 用户在当前组件中设置的inject的key, 然后循环key, 将每一个 key 从当前组件起, 不断向父组件查找是否有值,找到了就停止循环,最终将所有的key对应的值一起方法即可。
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)
// 如果支持 symbol, 用 Reflect.ownkeys读取 symbol类型的属性,否则用Object.keys读取
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
// 比那里 indect 列表
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
// inject 已经在 vue的实例上 跳过
if (key === '__ob__') continue
// 从 from 属性 获取 对应的 provide源属性key 。 injdect里面的属性都会处理为injdect:{ key: {from: 'test'}}
const provideKey = inject[key].from
let source = vm
// 循环 向上 查找 ,实例上的 _provided中存在 inject获取的 key时,赋值给result对象
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 如果向上遍历所有的实例 都没有 找到,此时 source 不存在
// 尝试获取默认值 ,默认值如果是函数执行后返回 ,否则直接返回
// 如果没有默认值 ,开发环境警告
if (!source) {
if ('default' in inject[key]) {
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)
}
}
}
// 返回 获取 到 的inject对应的值对象
return result
}
}
其中用户设置 的inject 比如
{inject: ['foo']}
规格化之后是下面这样
{inject: { foo: { from: 'foo'}}}
无论是数值形式还是 对象中使用 from属性的形式,本质上其实是让用户设置原属性名与当前组件中的属性名。 如果用户设置的是数组, 那么就认为用户是让两个属性名保持一致。
初始化 状态
在使用vue开发中, 经常会用到一下状态,比如 props, methods, data, computed, watch. 这些状态在使用之前需要进行初始化。
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// 按顺序初始化状态 props, methods, data, computed, watch
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)
}
}
首先会在 vm上新增一个属性_watchers,用来保持当前组件中所有的watcher实例。 无论是使用 vm.$watch注册的watcher实例 还是使用watch选项添加的watcher实例,都会添加到vm._watchers中。
接下来就是 按顺序判断 是否有props属性,存在就调用initProps初始化props. 是否存在methods。。。。
其中如果data不存在,那么会使用observe观察空对象。
初始化props
props原理是: 父组件提供数据, 子组件通过props字段选择自己需要的内容, Vue.js内部通过 子组件的props选项将需要的数据筛选出来之后添加到子组件的上下文中。
- props格式化
子组件在被实例化时,会对props进行格式化处理。
// 格式化props 每一项都有type,并且 key为驼峰写法
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
// 如果没有props 直接返回
if (!props) return
// 保存props 的临时对象 res
const res = {}
let i, val, name
// props 是数组
if (Array.isArray(props)) {
i = props.length
// 遍历
while (i--) {
val = props[i]
// 如果 数组中 每一项是字符串, 保存对应的props。
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.')
}
}
} else if (isPlainObject(props)) {
// props是对象 ,遍历
for (const key in props) {
val = props[key]
// 将名称 驼峰化 比如 user-name ==> userName
name = camelize(key)
// 如果每一项 对应的值 是 对象直接赋值 ,否则 包装一层
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
// props 不是对象又不是 数组 ,开发环境警告
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
// 把格式化后的 props重新赋值 给props属性
options.props = res
}
- 初始化props
初始化 props内部原理是: 通过规格化之后的 props从 父组件传入的props数据中 或从使用 new 创建实例 时传入的propsData参数中,筛选出 需要的数据 保存在 vm._props中,然后在vm上设置一个代理, 实现通过 vm.x访问 vm._props.x的目的。
function initProps (vm: Component, propsOptions: Object) {
// vm.$options.propsData 是用户通过父组件传入或用户 new Vue 时 传入的 props
const propsData = vm.$options.propsData || {}
// _props 中 会保存 所有设置到 props变量中的属性
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
// 缓存道具键,以便将来更新可以使用数组进行迭代
// 缓存props对象中 的key
const keys = vm.$options._propKeys = []
// 当前实例 是不是 根实例
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
// 只有root实例的props属性应该被转换为响应式 不能被 observe观察 即不能 new Observe
toggleObserving(false)
}
// 循环propsOptions ,将key添加到keys中。 调用validateProp 函数得到 prop的值,通过
// defineReactive 添加到 vm._props中 变为响应式
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 {
// toggleObserving(false) 是为了 能通过 props访问 props[key] 对应的value值
// 但是 不转换为 响应式 (访问和修改 都会 引起 watcher 更新
// 在 validateProp 中 value 已经是响应式的了。通过父组件修改 值,响应变化
// 而不能 在当前组件通过 访问 props[key] 修改 value值
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.
// 最后判断 key是否再实例 vm中,如果不存在调用 proxy,在 vm上设置一个以key为属性的代理
// 这样this.[key] 就可以访问 this._props.[key]了
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
// 重置 可响应式
toggleObserving(true)
}
toggleObserving 函数 的作用是确定 并 控制 defineReactive函数 调用时所传入 的value参数 是否需要转换为响应式。 toggleObserving 是一个闭包函数,所以能通过调用它并 传入一个参数 来控制 observer/index.js文件的作用域 中的变量 shouldObserve。 这样当数据将要被转换Wie响应式数据时,通过变量 shouldObserve来判断时候需要将数据转换为响应式的。
toggleObserving(false) 是为了 能通过 props访问 props[key] 对应的value值
但是 不转换为 响应式 (访问和修改 都会 引起 watcher 更新
在 validateProp 中 value 已经是响应式的了。通过父组件修改 值,响应变化
而不能 在当前组件通过 访问 props[key] 修改 value值
最后判断 key 在 vm中 是否存在, 如果不存在, 则调用 proxy,在vm上设置一个以 key 为属性的代理,当使用vm[key]访问数据时, 其实访问的是vm._props[key]。
重点是validateProp 函数是如何 获取props内容的
export function validateProp (
key: string, // 属性名
propOptions: Object, // 子组件中用户设置的props 选项
propsData: Object, // 父组件或用户提供 的props 数据
vm?: Component // this别名 实例
): any {
const prop = propOptions[key] // 保存当前的prop
const absent = !hasOwn(propsData, key) // 当前的props属性 缺席 ,不存在
let value = propsData[key] //获取 prop具体的值
// boolean casting
// 处理布尔类型
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
// 如果 prop不存在 并没有默认值 ,那么为 false
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (value === '' || value === hyphenate(key)) {
// hyphenate aB驼峰转换回去a-b (normalizeProps初始化时会把key转为驼峰)
// key 存在 ,当 value为空字符 或 value与key相等 (a==a, userName=user-name)
// only cast empty string / same name to boolean if
// boolean has higher priority
// 布尔值具有更高的优先级的情况下, 仅将空字符串/相同名称强制转换为布尔值
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
// check default value
// props 的值 为undefined的情况下
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)
// 把 value默认值 转换为 响应式
observe(value)
// 重置 是否可以响应式的 状态
toggleObserving(prevShouldObserve)
}
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
return value
}
validateProp函数接受4个参数
- key: propsOptions中属性名
- propOPtions: 子组件用户设置的props选项
- propsData: 父组件或用户提供的props数据
- vm: vue实例上下文
首先解决布尔类型prop的特殊情况
如果key不存在, 父组件或用户没有提供这个数据,并且props选项中没有默认值, 这个时候将value设置为 false. 另一种情况,key存在,当value是空字符或value与key相等,将value设置为true
除了布尔值外,其他类型的prop之需要处理一种情况。 如果子组件通过props选项设置的key 在props数据中不存在时, props如果提供了默认值,就是用它,将默认值转换为响应式。
toggleObserving 可以决定observer调用时,是否会将 value转换为响应式的。 最后 toggleObserving(prevShouldObserve) 将状态恢复成最初的状态。
assertProp 在开发环境下,会 断言prop是否有效。
function assertProp (
prop: PropOptions, // props选项
name: string, // props中prop选项的key
value: any, // prop数据 (propData)
vm: ?Component, // 上下文
absent: boolean // prop数据中不存在 key属性
) {
// 如果 设置了必填项 并且 没有 key 属性 ,控制台 警告
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
// 如果value 不存在 并且 没有设置 required 是合法的情况,直接返回 undefined即可
// null == undefined 为 true
if (value == null && !prop.required) {
return
}
let type = prop.type
// valid 默认 为 false , 或者 设置type 为 true时默认为true
let valid = !type || type === true
// 保存 type的列表 ,当校验失败,在控制台打印警告时, 可以将变量 expectedTypes中保存的类型打印出来
const expectedTypes = []
if (type) {
// 把type 转换 数组
if (!Array.isArray(type)) {
type = [type]
}
//
for (let i = 0; i < type.length && !valid; i++) {
// assertType 检验value 。返回一个对象{valid: true, expectedType: "Boolean"}
// valid表示 是否校验成功 expectedType 表示类型
const assertedType = assertType(value, type[i], vm)
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid
}
}
// 循环结束后, haveExpectedTypes 不存在,那么校验失败 警告
const haveExpectedTypes = expectedTypes.some(t => t)
if (!valid && haveExpectedTypes) {
warn(
getInvalidTypeMessage(name, value, expectedTypes),
vm
)
return
}
// 获取 自定义验证函数
// 如果设置了 就执行。猴子 调用warn 打印警告
const validator = prop.validator
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
}