本文已参与「新人创作礼」活动,一起开启掘金创作之路。
props
props是组件间通讯,经常用到的特性,下面我们去从源码角度了解一下原理:
在vue 2.x中,props的逻辑整体上可以分为3个流程,规范化、初始化、更新数据时,下面我们也分成这三个过程去分析:
规范化
在初始化props之前,会对props做一次规范化,规范化过程是在mergeOptions函数中进行的,不清楚mergeOptions逻辑的,可以点击这里。
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
......
// 第二个参数如果是函数,则取函数上面的options
if (typeof child === 'function') {
child = child.options
}
// 规范化props属性
normalizeProps(child, vm)
......
return options
}
可以看到mergeOptions的时候执行了normalizeProps:
**
* Ensure all props option syntax are normalized into the
* Object-based format.
*/
function normalizeProps(options: Object, vm: ?Component) {
// 拿到props,没有这个属性直接返回
const props = options.props
if (!props) return
const res = {}
let i, val, name
// props如果是数组,遍历props属性,如果其中的值不是string类型,会报错,否则将名称驼峰化,并赋值为{ type: null }
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') {
// 不是string类型则报错
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 如果是对象,遍历key值,对key值驼峰化处理,并赋值规范化
for (const key in props) {
val = props[key]
// 名称驼峰化
name = camelize(key)
// 对象直接赋值,非对象生成{ type: String } 这种格式
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (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
}
经过上面这源码的解释可能不够直观,举个例子,props可以像如下这样定义:
1.数组形式
export default {
props: ['name', 'first-name']
}
规范化后:
props: {
name: {
type: null
},
firstName: {
type: null
}
}
数组形式我们平时使用的比较少,但是也是需要清楚的啊!
2.对象形式
export default {
props: {
name: String,
first-name: {
type: String,
}
}
}
规范化后:
props: {
name: {
type: String
},
firstName: {
type: String
}
}
但对象形式,我们可以定义更多的属性配置,所以推荐更多的使用对象形式的props。
初始化
props的初始化过程发生在initState阶段:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
......
}
function initProps (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.
// 缓存props键,以便以后的prop更新可以使用数组迭代,而不是动态对象键枚举。
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
// 如果是根实例,执行toggleObserving
toggleObserving(false)
}
for (const key in propsOptions) {
// 遍历props的键值
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 (vm.$parent && !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)
}
可以看到,props初始化的逻辑主要有3个,校验、响应式、以及代理。
下一节我们来继续分析。