组件初始化渲染
本文以局部组件的注册方式介绍组件的初始化渲染,demo如下
new Vue({
el: '#app',
template:
`<div>
<div>father component!</div>
<my-component></my-component>
</div>`,
components:{
'my-component': {
template: '<div>children component!</div>'
}
}
})
1、根据template函数生成vnode
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
//template生成的render函数vm._render会调用vm._c('my-component')
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
function _createElement(){
//本例tag=‘my-component’,‘my-component’在components属性中注册过,因此以组件的方式生成vnode
if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag);
}
}
//本例Ctor参数{template: '<div>children component1!</div>'}
function createComponent (Ctor){
//Vue构造函数
var baseCtor = context.$options._base;
if (isObject(Ctor)) {
//生成VuComponent构造函数
//此处相当于Ctor = Vue.extend({template: '<div>children component1!</div>'}), Vue.extend后面有介绍;
Ctor = baseCtor.extend(Ctor);
}
//将componentVNodeHooks上的方法挂载到vnode上,组件初次渲染会用到componentVNodeHooks.init
var data = {}
mergeHooks(data);
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 } );
}
//component初始化和更新的方法,此处先介绍init
var componentVNodeHooks = {
init(){
//根据Vnode生成VueComponent实例
var child = vnode.componentInstance = createComponentInstanceForVnode();
//将VueComponent实例挂载到dom节点上,本文是挂载到<my-component></my-component>节点
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
}
2、调用vm._update将vnode渲染为浏览器dom,主要方法是遍历vnode的所有节点,根据节点类型调用相关的方法进行解析,本文主要介绍components的解析方法createComponent:根据vnode生成VueComponent(继承Vue)对象,
调用Vue.prototype.$mount方法渲染dom
new VueComponent和new Vue的过程类似,本文就不再做介绍
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i = i.hook) && isDef(i = i.init)) {
//i就是上面的componentVNodeHooks.init方法
i(vnode, false /* hydrating */, parentElm, refElm);
}
}
function createComponentInstanceForVnode (){
var options = {
_isComponent: true,
parent: parent,
propsData: vnodeComponentOptions.propsData,
_componentTag: vnodeComponentOptions.tag,
_parentVnode: vnode,
_parentListeners: vnodeComponentOptions.listeners,
_renderChildren: vnodeComponentOptions.children,
_parentElm: parentElm || null,
_refElm: refElm || null
};
//上面提到的VueComponent构造函数Ctor,相当于new VueComponent(options)
return new vnodeComponentOptions.Ctor(options)
}
全局注册组件
上文提到过 Vue.extend方法(继承Vue生成VueComponent构造函数)此处单独介绍一下
Vue.extend = function (extendOptions) {
var Super = this;
var Sub = function VueComponent (options) {
this._init(options);
};
//经典的继承写法
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.options = mergeOptions(
Super.options,
extendOptions
);
return Sub
};
通过Vue.component也可以全局注册组件,不需要每次new vue的时候单独注册,demo如下:
var MyComponent = Vue.extend({
name: 'my-component',
template: '<div>children component!</div>'
});
Vue.component('my-component', MyComponent);
new Vue({
el: '#app',
template:
`<div>
<div>father component!</div>
<my-component></my-component>
</div>`
})
组件通信
prop
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息.先看看prop是怎么工作的。demo如下:
new Vue({
el: '#app',
template:
`<div>
<div>father component!</div>
<my-component message="hello!"></my-component>
</div>`,
components:{
'my-component':{
props: ['message'],
template: '<span>{{ message }}</span>'
}
}
})
1、template生成的render函数包含:_c('my-component',{attrs:{"message":"hello!"}})]
2、render => vnode => VueComponent,上文提到的VueComponent的构造函数调用了Vue.prototype._init,并且入参option.propsData:{message: "hello!"}
3、双向绑定中介绍过Vue初始化时会对data中的所有属性调用defineReactive方法,对data属性进行监听;
VueComponent对propsData也是类似的处理方法,initProps后propsData中的属性和data一样也是响应式的,propsData变化,相应的view也会发生改变
function initProps (vm, propsOptions) {
for (var key in propsOptions){
//defineReactive参照Vue源码解析(二)
defineReactive(props, key, value);
//将propsData代理到vm上,通过vm[key]访问propsData[key]
proxy(vm, "_props", key);
}
}
4、propsData是响应式的了,但更常用的是动态props,按官网说法:“我们可以用v-bind来动态地将prop绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件”,那么vue是如何将data的变化传到给自组件的呢,先看demo
var vm = new Vue({
el: '#app',
template:
`<div>
<my-component :message="parentMsg"></my-component>
</div>`,
data(){
return{
parentMsg:'hello'
}
},
components:{
'my-component':{
props: ['message'],
template: '<span>{{ message }}</span>'
}
}
})
vm.parentMsg = 'hello world'
5、双向绑定中介绍过vm.parentMsg变化,会触发dep.notify(),通知watcher调用updateComponent;
又回到了updateComponent,之后的dom更新过程可以参考上文的组件渲染逻辑,只是propsData值已经是最新的vm.parentMsg的值了
//又见到了。。所有的dom初始化或更新都会用到
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
Vue.prototype._update = function (vnode, hydrating) {
var prevVnode = vm._vnode;
vm._vnode = vnode;
//Vue源码解析(一)介绍过dom初始化渲染的源码
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
);
} else {
// 本文介绍dom更新的方法
vm.$el = vm.__patch__(prevVnode, vnode);
}
}
Vue源码解析(一)介绍过vm.__patch__中dom初始化渲染的逻辑,本文再简单介绍下vm.__patch关于component更新的逻辑:
function patchVnode (oldVnode, vnode){
//上文介绍过componentVNodeHooks.init,此处i=componentVNodeHooks.prepatch
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
}
var componentVNodeHooks = {
init(){},
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
//更新组件
updateChildComponent(
child,
//此时的propsData已经是最新的vm.parentMsg
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
}
function updateChildComponent (vm, propsData){
//将vm._props[key]设置为新的propsData[key]值,从而触发view层的更新
var props = vm._props;
props[key] = validateProp(key, vm.$options.props, propsData, vm);
}
emit
子组件向父组件通信需要用到emit,先给出demo
var vm = new Vue({
el: '#app',
template:
`<div>
<my-component @rf="receiveFn"></my-component>
</div>`,
methods:{
receiveFn(msg){
console.log(msg)
}
},
components:{
'my-component':{
template: '<div>child</div>',
mounted(){
this.$emit('rf','hello')
}
}
}
})
本例中子组件mount结束会触发callHook(vm, 'mounted'),调用this.$emit('rf','hello'),从而调用父组件的receiveFn方法
Vue.prototype.$emit = function (event) {
//本例cbs=vm._events['rf'] = receiveFn,vm._events涉及v-on指令解析,以后有机会详细介绍下
var cbs = vm._events[event];
//截取第一位之后的参数
var args = toArray(arguments, 1);
//执行cbs
cbs.apply(vm, args);
}
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。