@component 构造函数_vue怎么创建component

本文深入探讨Vue.js的组件化思想,分析Vue在创建和渲染组件时的内部机制。从VNode阶段的_createElement方法,到createComponent实现组件实例,再到Patch阶段的组件初始化,详细阐述了Vue如何构建和更新组件VNode,以及componentOptions在其中的作用。
摘要由CSDN通过智能技术生成

Vue.js 的一个核心思想是组件化,就是把页面拆分成多个组件 (component)。我今天将从源码的角度来分析 Vue 是如何创建,工作的。

<script type="x-template" id="aaa">
	<div>
		子组件->{{msg}}
	</div>
</script>

<div id="box">
	<my-aaa></my-aaa>
</div>

<script>
	var vm = new Vue({
		el: '#box',
		components: {
			'my-aaa': {
				template: '#aaa',
				data() {
					return {
						msg: 'welcome vue'
					}
				},

			}
		}
	});
</script>

在vue mount的时候,会首先调用 render函数生成vnode,

export function mountComponent ( vm,  el, hydrating ){
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  
}

上面vue的创建过程,会生成如下的render函数

ƒ anonymous() {
	with(this) {
		return _c('div', {
			attrs: {
				"id": "box"
			}
		}, [_c('my-aaa')], 1)
	}
}

VNode 阶段

在调用render函数生成VNode的时候,_c(即 createElement),它最终会调用_createElement 方法

127bd9ac718b1430aaa7b88c3da9cc47.png

_createElement 会调用resolveAsset 验证 tag 是不是组件。如果是组件,那么通过createComponent 方法创建一个组件的 VNode。这 createComponent 是创建子组件的关键

else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
  vnode = createComponent(Ctor, data, context, children, tag);
}


function resolveAsset(
	options, // options = {components: { my-aaa:{}  }}}
	type, // type = "components"
	id, // id = "my-aaa"
	warnMissing
) {
	if(typeof id !== 'string') { return }
	var assets = options[type];

	if(hasOwn(assets, id)) {
		return assets[id]
	}
}

createComponent

 function createComponent (
    Ctor,   //  Ctor = {template: "#aaa", data: ƒ}
    data,    // data = undefined
    context,  // content = vm
    children,  // children = undefined
    tag     //  tag = "my-aaa"
  ) {
    if (isUndef(Ctor)) {
      return
    }

    var baseCtor = context.$options._base;

    if (isObject(Ctor)) {
      Ctor = baseCtor.extend(Ctor);
    }


    if (typeof Ctor !== 'function') {
      return
    }

    var propsData = extractPropsFromVNodeData(data, Ctor, tag);

    var listeners = data.on;

    installComponentHooks(data);

    var name = Ctor.options.name || tag;
    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
    );

    return vnode
  }

context.$options._base 会指向 Vue的构造函数,Vue.extend 会生成一个新的构造函数。可以看我写的 Vue.extend 是怎样实现的

    var baseCtor = context.$options._base;
    if (isObject(Ctor)) {
      Ctor = baseCtor.extend(Ctor);
    }

installComponentHooks就是把 componentVNodeHooks的钩子函数合并到data.hook中,,在合并过程中,如果某个时机的钩子已经存在data.hook中,那么通过执行mergeHook函数做合并勾子。

  function installComponentHooks (data) {
    var hooks = data.hook || (data.hook = {});
    for (var i = 0; i < hooksToMerge.length; i++) {
      var key = hooksToMerge[i];
      var existing = hooks[key];
      var toMerge = componentVNodeHooks[key];
      if (existing !== toMerge && !(existing && existing._merged)) {
        hooks[key] = existing ? mergeHook$1(toMerge, existing) : toMerge;
      }
    }
  }

  var componentVNodeHooks = {
    init: function init (vnode, hydrating) {

    },

    prepatch: function prepatch (oldVnode, vnode) {

    },

    insert: function insert (vnode) {

    },

    destroy: function destroy (vnode) {

    }
  };

最终生成的VNode 如下

{
	children: [{
		children: undefined,
		componentInstance: undefined,
		componentOptions: {
			propsData: undefined,
			listeners: undefined,
			tag: "A",
			children: undefined,
			Ctor: ƒ
		},
		data: {
			hook: {
				init: ƒ,
				prepatch: ƒ,
				insert: ƒ,
				destroy: ƒ
			}
			on: undefined
		},
		elm: undefined,
		parent: undefined,
		tag: "vue-component-1-A",
		text: undefined
	}],
	componentInstance: undefined,
	componentOptions: undefined,
	data: undefined,
	elm: undefined,
	isStatic: false,
	parent: undefined,
	tag: "div",
	text: undefined
}

Patch 阶段

function patch(oldVnode, vnode, hydrating, removeOnly) {
	const isRealElement = isDef(oldVnode.nodeType)
	if(!isRealElement && sameVnode(oldVnode, vnode)) {
		patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
	} else {

		createElm()
	}
}

cf0f544c79513218bd04a3ca61feffd6.png

createComponent

在执行createComponent 方法的时候,会先执行 componentVNodeHooks 钩子里面的 init 方法,这会为 vnode.componentInstance 赋值。 执行createComponentInstanceForVnode 会返回一个新的 VueComponent

const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
}  

export function createComponentInstanceForVnode (  vnode , parent ){
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}




 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }

      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.

      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

在调用init钩子函数之后,如果vnode对应的是一个子组件,然后把 vnode.elm 插到父Dom中。

componentOptions

在创建vnode的时候会传人一个componentOptions

  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

componentOptions里面会有以下几个属性

{
Ctor: ƒ VueComponent(options)
children: (3) [VNode, VNode, VNode]
listeners: undefined
propsData: undefined
tag: "my-aa"
}

Ctor 其实是一个构造函数

const Sub = function VueComponent (options) {
     this._init(options)
} 

children其实来自于render函数

595602342b286a069202d2b9ff3e35ee.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值