- 7.2 initProps
- 7.2.1 props的命名规范
- 7.2.2 响应式数据props
7.2 initProps
简单回顾一下props的用法,父组件通过属性的形式将数据传递给子组件,子组件通过props属性接收父组件传递的值。
// 父组件<child :test="test"></child>var vm = new Vue({el: '#app',data() {return {test: 'child'}}})// 子组件Vue.component('child', {template: '<div>{{test}}</div>',props: ['test']})
因此分析props需要分析父组件和子组件的两个过程,我们先看父组件对传递值的处理。按照以往文章介绍的那样,父组件优先进行模板编译得到一个render函数,在解析过程中遇到子组件的属性,:test=test会被解析成{ attrs: {test: test}}并作为子组件的render函数存在,如下所示:
with(){..._c('child',{attrs:{"test":test}})}
render解析Vnode的过程遇到child这个子占位符节点,因此会进入创建子组件Vnode的过程,创建子Vnode过程是调用createComponent,这个阶段我们在组件章节有分析过,在组件的高级用法也有分析过,最终会调用new Vnode去创建子Vnode。而对于props的处理,extractPropsFromVNodeData会对attrs属性进行规范校验后,最后会把校验后的结果以propsData属性的形式传入Vnode构造器中。总结来说,props传递给占位符组件的写法,会以propsData的形式作为子组件Vnode的属性存在。下面会分析具体的细节。
// 创建子组件过程function createComponent() {// props校验var propsData = extractPropsFromVNodeData(data, Ctor, tag);···// 创建子组件vnodevar 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.2.1 props的命名规范
先看检测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-bvar 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.2.2 响应式数据props
刚才说到分析props需要两个过程,前面已经针对父组件对props的处理做了描述,而对于子组件而言,我们是通过props选项去接收父组件传递的值。我们再看看子组件对props的处理:
子组件处理props的过程,是发生在父组件_update阶段,这个阶段是Vnode生成真实节点的过程,期间会遇到子Vnode,这时会调用createComponent去实例化子组件。而实例化子组件的过程又回到了_init初始化,此时又会经历选项的合并,针对props选项,最终会统一成{props: { test: { type: null }}}的写法。接着会调用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方法进行重写,具体的原理可以参考数据代理章节的内容,在这小节后半段也会有一个基本的实现。
