You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 模板<divid="app"><child-component :message="val"></child-component></div>// jsVue.component('child-component',{props: ['message']template: '<div>this is child component, I have {{message}}</div>',})newVue({el: '#app',data(){return{val: 'parent val'}},mounted(){setTimeout(()=>{this.val='parent val which has been changed after 2s'},2000)}})
Vue.prototype.$mount=function(el,hydrating){el=el&&inBrowser ? query(el) : undefined;returnmountComponent(this,el,hydrating)};varmount=Vue.prototype.$mount;Vue.prototype.$mount=function(el,hydrating){el=el&&query(el);varoptions=this.$options;// resolve template/el and convert to render functionif(!options.render){vartemplate=options.template;if(template){
...
}elseif(el){template=getOuterHTML(el);}if(template){
...
varref=compileToFunctions(template,{shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments},this);// 生成render函数varrender=ref.render;varstaticRenderFns=ref.staticRenderFns;options.render=render;options.staticRenderFns=staticRenderFns;}}returnmount.call(this,el,hydrating)};functionmountComponent(vm,el,hydrating){vm.$el=el;if(!vm.$options.render){
...
}// 挂载前callHook(vm,'beforeMount');varupdateComponent;/* istanbul ignore if */if("development"!=='production'&&config.performance&&mark){
...
}else{updateComponent=function(){vm._update(vm._render(),hydrating);};}newWatcher(vm,updateComponent,noop,null,true/* isRenderWatcher */);hydrating=false;// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif(vm.$vnode==null){vm._isMounted=true;callHook(vm,'mounted');}returnvm}
父子组件通讯
在
Vue
中,父子组件基本的通讯方式就是父组件通过props
属性将数据传递给子组件,这种数据的流向是单向的,当父props
属性发生了改变,子组件所接收到的对应的属性值也会发生改变,但是反过来却不是这样的。子组件通过event
自定义事件的触发来通知父组件自身内部所发生的变化。Vue props 是如何传递以及父 props 更新如何使得子模板视图更新
还是从一个实例出发:
最终页面渲染出的内容为:
接下来我们就来看下父子组件是如何通过
props
属性来完成数据的传递的。首先根组件开始实例化,完成一系列的初始化的内容。首先将
val
转化为响应式的数据,并调用Vue.prototype.$mount
方法完成vnode
的生成,真实dom
元素的挂载等功能:Vue.prototype.$mount
方法内部:完成模板的编译,同时生成
render
函数,这个render
函数在实际实行生成vnode
时,会将作用域绑定到对应的vm
实例的作用域下,即在创建vnode
的环节当中,始终访问的是当前这个vm
实例,子vnode
创建时是没法直接访问到父组件中定义的数据的。除非通过props
属性来完成数据由父组件向子组件的传递。完成模板的编译生成
render
函数后,调用_c
方法,对应访问vue
实例的_c
方法,开始创建对应的vnode
,注意这里val
变量,即vue
实例上data
属性定义的val
,在创建对应的vnode
前,实例已经调用initState
方法将val
转化为响应式的数据。因此在创建vnode
过程中,访问val
即访问它的getter
。在访问过程中
Dep.target
已经被设定为当前vue
实例的watcher
(具体见mountComponent
方法内部创建watcher
对象),因此会将当前的watcher
加入到val
的dep
当中。这样便完成了val
的依赖收集的工作。在创建
VNode
时,又分为:built in VNode
)component VNode
)其中内置标签的
VNode
的没有需要特别说明的地方,就是调用VNode
的构造函数完成创建过程。但是在创建自定义标签元素的
VNode
时,完成一些重要的操作(因为本文是讲解 props 传递,所以挑出和 props 相关的部分):在
createComponent
创建VNode
的过程中需要注意的是:Vue
的父子组件传递props
属性的时候都是在子组件上直接写自定义的dom attrs
:但是在模板编译后,统一将
dom
节点(不管是built in
的节点还是自定义component
节点)上的属性转化为attrs
对象(见代码片段 111),在创建VNode
过程中调用了extractPropsFromVNodeData
这个方法完成从attrs
对象上获取到这个component
所需要的props
属性,获取完成后还会将attrs
对象上对应的key
值删除。因此这个key
值对应的是要传入子component
的数据,而非原生dom
属性,最终由VNode
生成真实dom
的时候是不需要这些自定义数据的,因此需要删除。当然如果你在子组件中传入了
props
数据,但是在子组件中没有定义相关的props
属性,那么这个 props 属性最终会渲染到子组件的真实的dom
元素上,不过控制台也会出现报错:当完成了
my-component
的VNode
创建后,开始创建它的父VNode
,即根VNode
。vm._render()
方法调用完成后,即所有的VNode
都创建完成,开始递归将VNode
渲染成真实的dom
节点,同时挂载到document
当中(见上方调用的mountComponent
内部vm.update(vm._render())
)。在将
VNode
递归渲染成真实的dom
节点过程当中:对于**自定义标签元素(即组件)**的渲染,首先完成组件
vue实例
的初始化。又重复到上文一开始的Vue.prototype._init
方法。在实例化my-component
组件的过程中,还是通过调用initState
方法,将定义的props
属性中的message
属性转化为响应式的数据。� 在此之前,my-component
组件上的message
属性已经被初始化为从父组件传递过来的值。因此在页面初次渲染的时候,my-component
通过定义的props
属性从父组件上获取到的值为parent val
(上面的例子中定义的)这样便完成了父组件通过
props
属性向子组件传递数据。父 props 的改变是如何影响到子 component 的视图的更新
在子组件生成 VNode 的过程中会对应创建 render watcher,通过 props 从父组件传递给子组件的数据是在父作用域下获取得到的。因此,props 的 Dep 中会将这个 watcher 作为依赖添加进去。那么当父组件中的数据发生了改变,便会调用这个响应式数据
Dep.notify()
方法去通知相关的订阅者去完成更新,其中就包括子组件的 render watcher。Vue 父子组件如何传递/绑定自定义事件的
那么在子组件需要和父组件进行通讯的时候,所使用的
events
事件又是如何实现的呢?当点击
<child-component>
时,会在控制台输出this is child component
。那我们来看下整个过程是如何进行的:首先在模板编译的过程:
在创建
my-component
的component VNode
过程中,通过传入data
数据上定义的on
属性。这个时候test
访问的还是在父组件上定义的test
方法。接下来在将这个
VNode
实例成vue component
的时候:在将
VNode
实例化过程当中,调用initEvents
方法,获取在这个VNode
上绑定的从父组件传递下来的方法,并缓存至对应事件的回调函数数组当中。当你在子组件当中去$emit
对应的事件的时候,便会执行对应的回调函数。这里父子间的event
事件机制实际上是利用了发布订阅的设计模式。这个是有关父子组件自定义事件的机制。这里也顺带讲下 Vue 是如何绑定原生 DOM 事件的。
在代码片段 xxx 当中,生成 VNode 的环节当中,会将 nativeOn 赋值给
data.on
(data 上保存了将 VNode 渲染成真实 DOM 节点的数据)。当开始渲染真实 DOM 元素的时候:当 data 有值的时候,那么就开始执行 DOM 相关属性更新的工作。即执行在 cbs 上有关 create 阶段所有的回调函数,其中包括:
其中我们来看下有关 events,即原生 dom 事件是如何绑定到 DOM 元素上的。
在初次渲染DOM节点的时候,传入的 oldVNode 为一个空的 VNode,即拿这个空的 VNode 和即将要渲染的 VNode 进行原生DOM事件的 diff 工作。在
updateDOMListeners
方法当中还是继续调用updateListeners
方法去进行事件的绑定,这个时候绑定事件的函数使用的是add$1
,即调用DOM提供的addEventListener
方法去完成原生DOM事件的绑定工作。在这里我们也可以看出去 Vue 提供的事件修饰符在这里进行配置生效。这样便完成了原生的DOM事件的绑定。.sync修饰符-数据双向绑定
在 2.3.0+ 版本,Vue 提供了一种可以对 props 进行数据双向绑定的语法糖。基本的使用方法为:
事实上是 Vue 在将模板编译成渲染函数时,会将带有
.sync
标识符的 props 自动添加一个自定义的事件update:message
事件:那么当你在子组件当中去调用
update:message
方法的时候,并传入值的时候即会更新 message 的值。这个 message 的值即在父组件当中的数据。这样便完成了数据的双向绑定。// vm._update(vm._render())
// patch
// prepatch 方法
// updateChildComponent 完成 props 等属性的 setter 操作
_props
是在实例初始化过程中定义的一个内部属性,同时调用defineReactive
方法完成将响应式数据存放到_props
属性上。在VNode
的patch
过程中,如果有属性发生了变化,那么会调用这个属性的setter
方法完成值的变更操作,继而完成视图的更新。当然了,如果组件在定义的过程,没有定义props
属性,那么在实例初始化的过程中,_props
属性也不会被创建。只有组件上定义过props
属性,在初始化的过程中才会定义这个内部属性。The text was updated successfully, but these errors were encountered: