资讯专栏INFORMATION COLUMN

vue@2.0源码学习---组件究竟是什么

PiscesYE / 2718人阅读

摘要:定义一个组件如下打印如下再回过头看,可以发现他做的工作就是扩展一个构造函数,并将这个构造函数添加到现在我们已经可以回答最开始的问题的组件是什么的组件其实就是扩展的构造函数,并且在适当的时候实例化为实例。

vue@2.0源码学习---组件究竟是什么

本篇文章从最简单的情况入手,不考虑prop和组件间通信。

Vue.component

vue文档告诉我们可以使用Vue.component(tagName, options)注册一个组件

Vue.component("my-component", {
  // 选项
})

毫无疑问这是一个全局API,我们顺着代码最终可以找到Vue.component是这样的

Vue.component = function(id, definition) {
    definition.name = definition.name || id
    definition = Vue.extend(definition)
    this.options[type + "s"][id] = definition
    return definition    
}

Vue.component实际上是Vue.extend的封装,Vue.extend如下:

Vue.extend = function (extendOptions: Object): Function {
  extendOptions = extendOptions || {}
  const Super = this
  const isFirstExtend = Super.cid === 0
  if (isFirstExtend && extendOptions._Ctor) {
    return extendOptions._Ctor
  }
  let name = extendOptions.name || Super.options.name
  const Sub = function VueComponent (options) {
    this._init(options)
  }
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub["super"] = Super
  // allow further extension
  Sub.extend = Super.extend
  // create asset registers, so extended classes
  // can have their private assets too.
  config._assetTypes.forEach(function (type) {
    Sub[type] = Super[type]
  })
  // enable recursive self-lookup
  if (name) {
    Sub.options.components[name] = Sub
  }
  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super"s options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  // cache constructor
  if (isFirstExtend) {
    extendOptions._Ctor = Sub
  }
  return Sub
}

可以看到Vue.extend返回的实际上是一个构造函数Sub,并且此构造函数继承自Vue。里面有这么几行代码

  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )

那么Super.options(即Vue.options)是什么呢?

  Vue.options = Object.create(null)
  //  包含components directives filters
  config._assetTypes.forEach(type => {
    Vue.options[type + "s"] = Object.create(null)
  })

  util.extend(Vue.options.components, builtInComponents)

Vue.options事实上存放了系统以及用户定义的component、directive、filter,builtInComponents为Vue内置的组件(如keep-alive),打印看下:

所以Sub构造函数的options不仅包含components、directives、filters,还包含预先定义的实例化时所需的选项。定义一个组件如下:

let MyComponent = Vue.extend({
    data() {
        return {
            msg: "this"s compoennt"
        }
    },
    render(h) {
        return h("p", this.msg)
    }
})

打印MyComponent.options如下:

再回过头看Vue.component,可以发现他做的工作就是扩展一个Vue构造函数(VueComponent),并将这个构造函数(VueComponent)添加到Vue.options.components

现在我们已经可以回答最开始的问题---vue的组件是什么?vue的组件其实就是扩展的Vue构造函数,并且在适当的时候实例化为Vue实例。

组件对应的vnode

组件对应的vnode是什么样子?从一个简单的例子入手:

let MyComponent = Vue.component("my-component", {
    data() {
        return {
            msg: "this"s component"
        }
    },
    render(h) {
        return h("p", this.msg)
    }
})

window.app = new Vue({
    render(h) {
        return h("my-component")
    }
}).$mount("#root")

上篇文章已经说道在initRender的时候会初始一个系统watcher,如下:

vm._watcher = new Watcher(vm, () => {
  vm._update(vm._render(), hydrating)
}, noop)

上篇文章提到vm._render()返回的是一个虚拟dom(vnode),具体到本篇,那么组件标签会被解析成什么样的虚拟节点呢?

事实上render的时候会首先调用createElement,根据传入的tag(html标签或者组件标签)不同,vnode可以分为以下两种:

platform built-in elements

这种就是普通的html标签(p、div、span等)对应的vnode

component

当tag是组件标签的时候,会调用createComponent,如下:

    else if ((Ctor = resolveAsset(context.$options, "components", tag))) {
      // component
      
      return createComponent(Ctor, data, context, children, tag)
    }

这里的Ctor就是我们扩展的组件构造函数,createComponent最终返回的vnode如下:

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

需要注意的是data有一个操作:

  // merge component management hooks onto the placeholder node
  mergeHooks(data)

merge之后data.hook会添加四个方法:

init 实例化组件时调用

prepatch patch之前调用

insert 真实节点插入时调用

destory 组件实例销毁时调用

实例化组件

前文看到组件构造函数实际上是存在组件对应vnode的componentOptions中,那么究竟是什么时候实例化组件呢?

顺着vm._update(vm._render(), hydrating)往下看发现最终调用的是patch操作,而对于组件实例化而言并不存在与之对应的oldVnode(因为oldVnode是在组件更新后产生的),所以最终的逻辑归到根据组件对应的vnode创建真实dom节点,即

createElm(vnode, insertedVnodeQueue)

我们还记得组件的构造函数是vnode.componentOptions.Ctor,其实最终调用的也是这个构造函数。

createElm函数中与组件初始化相关的关键代码如下:

    const data = vnode.data
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode)

      if (isDef(i = vnode.child)) {
        initComponent(vnode, insertedVnodeQueue)
        return vnode.elm
      }
    }

init的代码如下:

function init (vnode: VNodeWithData, hydrating: boolean) {
  if (!vnode.child || vnode.child._isDestroyed) {
    const child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance)
    child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  }
}


export function createComponentInstanceForVnode (
  vnode: any, // we know it"s MountedComponentVNode but flow doesn"t
  parent: any // activeInstance in lifecycle state
): Component {
  const vnodeComponentOptions = vnode.componentOptions
  const options: InternalComponentOptions = {
    _isComponent: true,
    parent,
    propsData: vnodeComponentOptions.propsData,
    _componentTag: vnodeComponentOptions.tag,
    _parentVnode: vnode,
    _parentListeners: vnodeComponentOptions.listeners,
    _renderChildren: vnodeComponentOptions.children
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (inlineTemplate) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }

  return new vnodeComponentOptions.Ctor(options)
}

经过init之后可以看到组件vnode.child对应的就是组件的实例,且child.$el即为组件对应的真实dom,但是实际上createElm返回的是vnode.elm,怎么回事?事实上initComponent
中做了处理

vnode.elm = vnode.child.$el

综上,组件实例化是在由虚拟dom映射为真实dom时完成的。

写到这里已经对组件机制有了初步的认识,数据的传递、父子组件通信本文并没有涉及,留到以后再看。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90375.html

相关文章

  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0
  • 有价值的前端技术点

    摘要:借着产品层面的功能和视觉升级,我们用对它进行了一次技术重构。前端优化是一个让人技术提升的,希望你也能从这里学到一些东西。年最流行的前端链接我们每周会给多名前端开发者发送新闻邮件。 面试 -- 网络 HTTP 现在面试门槛越来越高,很多开发者对于网络知识这块了解的不是很多,遇到这些面试题会手足无措。本篇文章知识主要集中在 HTTP 这块。文中知识来自 《图解 HTTP》与维基百科,若有错...

    microelec 评论0 收藏0
  • Vue原理】Component - 白话版

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理白话版从模板上使用到挂载到页面 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于...

    liuyix 评论0 收藏0
  • Vue 2.0 升(cai)级(keng)之旅

    摘要:前言这节净是些唠叨,只想看升级的可直接跳过。在不久之前,如约发布了版本。正如计划之初,博客的版本也将升级到。升级之旅首先,升级依赖。那该怎么做哪再一次谷哥和查阅文档,然而一无所获。返回的是整个项目路由的实例,它是只读的。 Troubleshooting of upgrading Vue from 1.0 to 2.0 系列文章: Vue 2.0 升(cai)级(keng)之旅 (本...

    lidashuang 评论0 收藏0
  • 前端经典文章

    摘要:上周末看这篇文章时,偶有灵光,所以,分享出来给大家一起看看前端面试四月二十家前端面试题分享请各位读者添加一下作者的微信公众号,以后有新的文章,将在微信公众号直接推送给各位,非常感谢。 前端切图神器 avocode 有了这个神器,切图再也腰不酸,腿不疼了。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,...

    lowett 评论0 收藏0

发表评论

0条评论

PiscesYE

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<