zoukankan      html  css  js  c++  java
  • 【Vue】Vue源码解读之Component组件注册

    Vue可以有全局注册和局部注册两种方式来注册组件。

    一、全局注册

    注册方式

    全局注册有以下两种注册方式:

      • 通过Vue.component 直接注册。
    Vue.component('button-counter', {
            //data选项必须是一个函数
            data: function () {
                return {
                    count: 0
                }
            },
            template:'#clickBtn'
        })
      • 通过Vue.extend来注册。
     var buttonComponent = Vue.extend({
            name:'button-counter',
            data: function () {
                return {
                    count: 0
                }
            },
            template:'#clickBtn'
        });
     Vue.component('button-counter', buttonComponent);

    源码实现过程

    Vue初始化时,initGlobalAPI通过调用initAssetRegisters()进行组件注册。(源码位置:src/core/global-api/assets.js)

    function initAssetRegisters (Vue: GlobalAPI) {
      /**
       * Create asset registration methods.
       */
      ASSET_TYPES.forEach(type => {
        Vue[type] = function (
          id: string,
          definition: Function | Object
        ): Function | Object | void {
          //这里的definition指的是定义(Function或Object),是函数或者对象
          //如果definition不存在,直接返回options内type和id对应的
          //这里的options是指全局的组件,指令和过滤器,见图一
          if (!definition) {
            return this.options[type + 's'][id]
          } else {
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && type === 'component') {
              validateComponentName(id)
            }
           // 如果是component(组件)方法,并且definition是对象
            if (type === 'component' && isPlainObject(definition)) {
              definition.name = definition.name || id
              definition = this.options._base.extend(definition)
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition }
            }
            // 将构造器赋值给 this.options[‘component’+ 's'][id]
            //全局的组件,指令和过滤器,统一挂在vue.options上。在init的时候利用mergeOptions合并策略侵入实例,供实例使用。
            this.options[type + 's'][id] = definition
            return definition
          }
        }
      })
    }

    这里是对三种方法的定义'component', 'directive', 'filter',这里我们要说的是component,可以看到,当type = 'component时,通过this.options._base.extend方法将定义对象转化为构造器,而this.options._base.extend其实就是Vue.extend。接下来我们就看一下Vue.extend做了什么。

    Vue.extend = function (extendOptions) {
        extendOptions = extendOptions || {};
        var Super = this;
        var SuperId = Super.cid;
        //组件缓存
        var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
        //如果组件已经被缓存在extendOptions上则直接取出
        if (cachedCtors[SuperId]) {
          return cachedCtors[SuperId]
        }
    
        //如果有name属性,检验name拼写是否合法
        var name = extendOptions.name || Super.options.name;
        if ("development" !== 'production' && name) {
          validateComponentName(name);
        }
    
        var Sub = function VueComponent (options) {
          this._init(options);
        };
        //将vue上原型的方法挂在Sub.prototype中,Sub的实例同时也继承了vue.prototype上的所有属性和方法。
        //关于 prototype的学习:http://www.cnblogs.com/dolphinX/p/3286177.html
        Sub.prototype = Object.create(Super.prototype);
        //Sub构造函数修正,学习于https://www.cnblogs.com/SheilaSun/p/4397918.html
        Sub.prototype.constructor = Sub;
        Sub.cid = cid++;
        //通过vue的合并策略合并添加项到新的构造器上
        Sub.options = mergeOptions(
          Super.options,
          extendOptions
        );
        //缓存父构造器
        Sub['super'] = Super;
    
        // 处理props和computed响应式配置项
        if (Sub.options.props) {
          initProps$1(Sub);
        }
        if (Sub.options.computed) {
          initComputed$1(Sub);
        }
    
        // allow further extension/mixin/plugin usage
        Sub.extend = Super.extend;
        Sub.mixin = Super.mixin;
        Sub.use = Super.use;
    
        //在新的构造器上挂上vue的工具方法
        ASSET_TYPES.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;
        Sub.sealedOptions = extend({}, Sub.options);
    
        //缓存组件构造器在extendOptions上
        cachedCtors[SuperId] = Sub;
        return Sub
      };

    vue.extend返回了一个带有附加Option的vue构造器。这个构造器被命名为Sub,等待render时候初始化。
    initAssetRegisters完成之后,options下挂载了全局组件button-counter,如图:

    注册前:

     

    注册后:

    二、局部注册

    如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的components属性实现局部注册。

    注册方式

    new Vue({
            el: '#components-demo',
            components:{
                'button-counter':{
                    template:'#clickBtn',
                    data: function () {
                        return {
                            count: 0
                        }
                    }
                }
            }
        })

    源码实现过程

    Vue局部组件注册也是通过initAssetRegisters()方法调用Vue.extend,不同的是在createComponent()时,initMixin()里面有判断

    if (options && options._isComponent) {
          //因为Vue动态合并策略非常慢,并且内部组件的选项都不需要特殊处理。
          //调用initInternalComponent快捷方法,内部组件实例化。
          initInternalComponent(vm, options);
     }
     else {
         vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          );
        }
    function initInternalComponent (vm, options) {
    //在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因。
    var opts = vm.$options = Object.create(vm.constructor.options); // 这样做是因为它比动态枚举更快。 var parentVnode = options._parentVnode; opts.parent = options.parent; opts._parentVnode = parentVnode; var 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; } }

     opts的结构如图所示:

    局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 Vue.options 下。在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因。

    三、组件名定义

    定义组件名的方式有两种:

      • 使用短横线形式
    Vue.component('button-counter', {})

    引用这个自定义元素时,必须用 <button-counter></button-counter>

      • 使用驼峰的形式
    Vue.component('buttonCounter', { })

    此时在引用这个自定义元素时,两种命名方法都可以使用。也就是说,<buttonCounter> 和 <button-counter> 都是可行的。
    注意,直接在 DOM (即非字符串的模板) 中使用时只有短横线是有效的。如下:

    <div id="components-demo">
            <button-counter></button-counter>
    </div>

     参照:https://www.jianshu.com/p/3765a5bb0a9e

  • 相关阅读:
    C++ 线程的创建、挂起、唤醒和结束 &&&& 利用waitForSingleObject 函数陷入死锁的问题解决
    接收数据界面卡顿-----待整理
    vs2012 在调试或运行的过程中不能加断点
    matlab 学习笔记
    周立功USBCAN-II 上位机开发(MFC)
    vs添加静态链接库+添加动态链接库+添加头文件目录
    Go 面试每天一篇(第 2 天)
    http 协议
    Samba配置
    svn checkout 单个文件
  • 原文地址:https://www.cnblogs.com/vickylinj/p/13674257.html
Copyright © 2011-2022 走看看