详解vue 组件注册


Posted in Vue.js onNovember 20, 2020

一、了解组件注册的两种方式

1.1 全局组件的注册方法

//main.js 
import Vue from 'vue' 
import App from './App' 
import router from './router' 
Vue.config.productionTip = false 

let Hello = { 
  name: 'hello', 
  template: '这是全局组件hello' 
} 
Vue.component('hello', Hello) 
new Vue({ 
  el: '#app', 
  router, 
  components: { App }, 
  template: '' 
})

上面我们就通过Vue.component()注册了一个全局组件hello,接下来分析源码实现的时候也是基于这个例子来进行的。

1.2 局部组件的注册

<template>
 <div id="app">
  <img src="./assets/logo.png">
  <HelloWorld/>
 </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
 name: 'App',
 components:{
  HelloWorld
 }
}
</script>

像这样就注册了一个HelloWorld的局部组件。

二、全局组件注册的源码

1.Vue初始化的时候,会调用initGlobalAPI()

//【代码块1】
//代码所在文件:src/core/global-api/index.js
export function initGlobalAPI(Vue: GlobalAPI){
  //...省略其他无关代码
  initAssetRegisters(Vue)
  //这个方法就是用于组件注册的方法
}

2.在initAssetRegisters()方法中执行组件的定义

//【代码块2】
//代码所在文件:src/core/global-api/assets.js
export function initAssetRegister(Vue){
  ASSET_TYPES.forEach(type=>{
    //ASSET_TYPES包括component、directive、filter
    Vue[type] = function(id, definition){
      //...一些条件判断
      if(type === 'component' && isPlainObject(definition)){
        definition.name = definition.name || id 
        definition = this.options._base.extend(definition) 
        //将definition转换为一个继承于Vue的构造函数
      }
      //...其他类型的处理
      
      this.options[type+'s'][id] = definition //将这个构造函数挂载到Vue.options.components上
      return definition
    }
  })
}

此时,我们可以单步调试一下我们上面的例子,来看一下definition一开始是什么,以及执行挂载后Vue.options变成了什么样子:

a.definition: 其实传入的时候就是我们一开始定义的全局组件的具体内容

详解vue 组件注册

b.Vue.options: 可以看到我们定义的全局组件hello已经存在在Vue.options.components上了

详解vue 组件注册

3.实例化组件的时候,代码会执行到Vue.prototype._init()上面

//【代码块3】
//代码所在文件:src/core/instance/init.js
Vue.prototype._init = function(options){
  //..省略其他无关代码
  if(options && options._isComponent){ //组件
    initInternalComponent(vm, options)
  }else{ //非组件
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options||{},
      vm
    )
  }
}

这里将自己定义的组件的options与Vue.options做了一个合并,并且赋值给了vm.$options,而通过【代码块2】我们可以知道全局组件的构造函数已经被放在了Vue.options.components上,所以经过这一步,vm.$options.components上面也有了全局组件的构造函数。所以现在在任意组件都能拿到全局组件,因为任何组件初始化的时候都会执行这个合并。

我们可以通过单步调试上面的例子看一下现在的vm.$options上面有些什么

详解vue 组件注册

4.在创建vnode的过程中,会执行_createElement方法

//【代码块4】
//代码所在文件:src/core/vdom/create-element.js
export function _createElement(context, tag, data, children, normalization){
  if(typeof tag === 'string'){
    //...
    if(config.isReservedTag(tag)){
      //...保留的html标签
    }else if(isDef(Ctor = resolveAsset(context.$options, 'component', tag))){
      //已经注册过的全局组件
      vnode = createComponent(Ctor, data, context, children, tag)
    }else{
      //不是内置标签也不是已经注册过的组件,就创建一个全新的vnode
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
       )
    }
  }
}

上面代码中有一个比较重要的方法resolveAsset(),用于判断在context.$options.compononts(即vm.$options.components)上面是否能找到这个组件的构造函数,如果能找到,返回这个构造函数,(具体方法见【代码块5】)根据【代码块3】我们可以知道如果这个组件是全局注册的组件,那么我们就可以得到这个构造函数,并进入这个else if判断,通过createComponent()得到vnode。

5.上面四步已经实现了整个流程,现在补充看一下resolveAsset()

//【代码块5】
//代码所在文件:src/core/utils/options.js
export function resolveAsset(options, type, id, warnMissing){
   //options即上面调用的时候传入的context.$options, 
   //由【代码块3】,vm.$options是由我们自定义的options以及Vue上的options合并而来的
   //type现在是components
    
   const assets = options[type]
   // check local registration variations first
   if (hasOwn(assets, id)) return assets[id]
   const camelizedId = camelize(id)
   if (hasOwn(assets, camelizedId)) return assets[camelizedId]
   const PascalCaseId = capitalize(camelizedId)
   if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
   // fallback to prototype chain
   const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
   if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
     'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
     options
    )
   }
   return res
 }

先通过 const assets = options[type] 拿到 assets,然后再尝试拿 assets[id],这里有个顺序,先直接使用 id 拿,如果不存在,则把 id 变成驼峰的形式再拿,如果仍然不存在则在驼峰的基础上把首字母再变成大写的形式再拿,如果仍然拿不到则报错。这样说明了我们在使用 Vue.component(id, definition) 全局注册组件的时候,id 可以是连字符、驼峰或首字母大写的形式。

三、局部组件的注册

1.extend()

组件在执行render()的时候,会执行createComponent函数,在这个函数里面会执行extend()函数生成一个构造函数,也是在这个extend()函数中,执行了一个options的合并

//【代码块5】
//代码所在文件:src/core/global-api/extend.js
Vue.entend = function(extendOptions){
  //...
  Sub.options = mergeOptions(
     Super.options, //Vue的options
     extendOptions //定义组件的那个对象
  )
  //...
}

可以看出这里是将自己传入的options(即定义组件的那个对象)与Vue.options合并,然后放到Sub.options上,同时,因为Sub.options上面合并了Vue的options,所以组件里面也可以拿到全局注册的组件。

2.组件初始化

//【代码块6(同代码块3)】
//代码所在文件:src/core/instance/init.js
Vue.prototype._init = function(options){
  //..  
  if(options && options._isComponent){
    initInternalComponent(vm, options)
  }else{
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options||{},
      vm
    )
  }
}

组件初始化的过程中会进入if判断语句,执行initInternalComponent()

3.initInternalComponent()

//【代码块7】
//代码所在文件:src/core/instance/init.js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
 const opts = vm.$options = Object.create(vm.constructor.options) 
 //vm.constructor即为Sub,在代码块5中,我们已经将局部组件放在了Sub.options上
 //所以这里将局部组件的构造函数放在了vm.$options上
 //这样在执行【代码块4】的时候同样也能通过resolveAsset得到局部注册组件的构造函数
 const parentVnode = options._parentVnode
 opts.parent = options.parent
 opts._parentVnode = parentVnode

 //将componentOptions里面的别的属性赋值给opts
 const vnodeComponentOptions = parentVnode.componentOptions
 opts.propsData = vnodeComponentOptions.propsData
 opts._parentListeners = vnodeComponentOptions.listeners
 opts._renderChildren = vnodeComponentOptions.children
 opts._componentTag = vnodeComponentOptions.tag

 if (options.render) {
  opts.render = options.render
  opts.staticRenderFns = options.staticRenderFns
 }
}

四、总结

由于全局注册的组件是将组件的构造函数扩展到了Vue.options.components上,而组件在初始化的时候都会将自身options与Vue.options合并,扩展到当前组件的vm.$options.components下,所以全局组件能在任意组件被使用。而局部注册的组件是将组件的构造函数扩展到了当前组件的vm.$options.components下,所以只能在当前组件使用。

以上就是详解vue 组件注册的详细内容,更多关于vue 组件注册的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
Vue 简单实现前端权限控制的示例
Dec 25 Vue.js
vue3.0自定义指令(drectives)知识点总结
Dec 27 Vue.js
Vue实现简易购物车页面
Dec 30 Vue.js
Vue+scss白天和夜间模式切换功能的实现方法
Jan 05 Vue.js
vscode自定义vue模板的实现
Jan 27 Vue.js
如何封装Vue Element的table表格组件
Feb 06 Vue.js
Vue实现跑马灯样式文字横向滚动
Nov 23 Vue.js
Vue的列表之渲染,排序,过滤详解
Feb 24 Vue.js
vue使用localStorage持久性存储实现评论列表
Apr 14 Vue.js
vue特效之翻牌动画
Apr 20 Vue.js
vue elementUI批量上传文件
Apr 26 Vue.js
关于vue-router-link选择样式设置
Apr 30 Vue.js
vue-drawer-layout实现手势滑出菜单栏
Nov 19 #Vue.js
Vue 打包的静态文件不能直接运行的原因及解决办法
Nov 19 #Vue.js
如何使用 vue-cli 创建模板项目
Nov 19 #Vue.js
深入了解Vue3模板编译原理
Nov 19 #Vue.js
vue 获取到数据但却渲染不到页面上的解决方法
Nov 19 #Vue.js
vue 插槽简介及使用示例
Nov 19 #Vue.js
详解Vue的mixin策略
Nov 19 #Vue.js
You might like
判“新”函数:得到今天与明天的秒数
2006/10/09 PHP
php生成文件
2007/01/15 PHP
php 防止单引号,双引号在接受页面转义
2008/07/10 PHP
php学习之流程控制实现代码
2011/06/09 PHP
学习php设计模式 php实现单例模式(singleton)
2015/12/07 PHP
php命令行模式代码实例详解
2021/02/26 PHP
jQuery的控件及事件(输入控件及回车事件)使用示例
2013/07/25 Javascript
给ListBox添加双击事件示例代码
2013/12/02 Javascript
js实现照片墙功能实例
2015/02/05 Javascript
js实现对ajax请求面向对象的封装
2016/01/08 Javascript
javascript 内置对象及常见API详细介绍
2016/11/01 Javascript
ES6 javascript中class类的get与set用法实例分析
2017/10/30 Javascript
Vue中render方法的使用详解
2018/01/26 Javascript
jQuery+koa2实现简单的Ajax请求的示例
2018/03/06 jQuery
vue+mousemove实现鼠标拖动功能(拖动过快失效问题解决方法)
2018/08/24 Javascript
VUE脚手架具体使用方法
2019/05/20 Javascript
js实现点赞效果
2020/03/16 Javascript
微信小程序实现日历小功能
2020/11/18 Javascript
Python易忽视知识点小结
2015/05/25 Python
Ubuntu安装Jupyter Notebook教程
2017/10/18 Python
Python生命游戏实现原理及过程解析(附源代码)
2019/08/01 Python
画pytorch模型图,以及参数计算的方法
2019/08/17 Python
如何使用Python抓取网页tag操作
2020/02/14 Python
使用python 计算百分位数实现数据分箱代码
2020/03/03 Python
Django 删除upload_to文件的步骤
2020/03/30 Python
Python 实现劳拉游戏的实例代码(四连环、重力四子棋)
2021/03/03 Python
CSS3实现滚动条动画效果代码分享
2016/08/03 HTML / CSS
新西兰便宜隐形眼镜购买网站:QUICKLENS New Zealand
2019/03/02 全球购物
璀璨的珍珠、密钉和个性化珠宝:Lily & Roo
2021/01/21 全球购物
护理专业个人求职简历的自我评价
2013/10/13 职场文书
我与祖国共奋进演讲稿
2014/09/13 职场文书
学生实习证明范文
2014/09/28 职场文书
党支部评议意见
2015/06/02 职场文书
户外拓展训练感想
2015/08/07 职场文书
2016年社区国庆节活动总结
2016/04/01 职场文书
解决goland 导入项目后import里的包报红问题
2021/05/06 Golang