7.1 initProps
简单回顾一下props
的用法,父组件通过属性的形式将数据传递给子组件,子组件通过props
属性接收父组件传递的值。
// 父组件
<child :test="test"></child>
// 子组件
{
props: ['test']
}
因此分析props
需要分析父组件和子组件的两个过程,我们先看父组件对传递值的处理。父组件会优先进行实例的挂载,render
解析过程中,遇到子组件的占位符节点<child :test="test"></child>
时,会创建子类构造器,遇到传递给子组件的属性时,会解析成 { attrs: {test: test}}
的形式并作为render
函数存在。再对attrs
属性进行规范校验后,会将校验的结果以propsData
属性的形式来创建子的Vnode
。总结来说,props
传递给占位符组件的写法,会以propsData
的形式作为子组件Vnode
的属性存在。
// 创建子组件过程
function createComponent() {
// props校验
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
···
// 创建子组件vnode
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
}
7.1.1 props的命名规范
回过头看检测props
规范性的过程。其中attrs
前面分析过,是编译生成render
函数针对属性的处理,而props
是针对用户自写render
函数的属性值。接下来会对命名规则做判断。
function extractPropsFromVNodeData (data,Ctor,tag) {
// Ctor为子类构造器
···
var res = {};
// 子组件props选项
var propOptions = Ctor.options.props;
// data.attrs针对编译生成的render函数,data.props针对用户自定义的render函数
var attrs = data.attrs;
var props = data.props;
if (isDef(attrs) || isDef(props)) {
for (var key in propOptions) {
// aB 形式转成 a-b
var altKey = hyphenate(key);
{
var keyInLowerCase = key.toLowerCase();
if (
key !== keyInLowerCase &&
attrs && hasOwn(attrs, keyInLowerCase)
) {
// 警告
}
}
}
}
}
有必要说一下这一部分的处理,HTML对大小写是不敏感的,所有的浏览器会把大写字符解释为小写字符,因此我们在使用DOM中的模板是,cameCase(驼峰命名法)的props名需要使用其等价的 kebab-case (短横线分隔命名) 命代替。即: <child :aB="test"></child>
需要写成<child :a-b="test"></child>
7.1.2 响应式数据props
刚才说到分析props
需要两个过程,这里我们再看看子组件对props
的处理,在创建子组件createComponent
的过程中,和初始化根组件一样,会经历子组件的选项合并和初始化过程,在深入剖析Vue源码 - 选项合并(上)一节,我们知道,子组件props
选项最终会统一成{props: { test: { type: null }}}
的写法。选项合并和写法规范是初始化props
的前提,接下来会执行initProps
, initProps
做的事情,简单概括一句话就是,将组件的props
数据设置为响应式数据。
function initProps (vm, propsOptions) {
var propsData = vm.$options.propsData || {};
var loop = function(key) {
···
defineReactive(props,key,value,cb);
if (!(key in vm)) {
proxy(vm, "_props", key);
}
}
// 遍历props,执行loop设置为响应式数据。
for (var key in propsOptions) loop( key );
}
其中proxy(vm, "_props", key);
为props
做了一层代理,用户通过vm.XXX
可以代理访问到vm._props
上的值。针对defineReactive
,本质上是利用Object.defineProperty
对数据的getter,setter
方法进行重写,具体的原理可以参考深入剖析Vue源码 - 数据代理,关联子父组件这一节的内容,在这小节后半段也会有一个基本的实现。