Vue源码解析系列(十四) -- Vue.use与Vue.component的原理解读

250 阅读4分钟

前言

vue项目中,难免会使用到三方插件,比如ElementUI。那Vue提供了一个Vue.use方法,能够使用这些插件,并且Vue.Component方法能够帮我们注册一个组件,下面我们就来一起看看吧。

Vue.use的依赖

  • 安装插件:npm i xx -D
  • 引入插件:import Xx from 'xx'
  • 使用插件:Vue.use(Xx)
import Vue from 'vue';
import ElementUI from 'element-ui';
Vue.use(ElementUI);

Vue3中:

import { createApp } from 'vue'
import MyPlugin from './plugins/MyPlugin'

const app = createApp({})

app.use(MyPlugin)

原理

  • 参数

    • {Object | Function} plugin
  • 用法

    安装Vue.js插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install方法。install方法调用时,会将Vue作为参数传入。该方法需要在调用new Vue()之前被调用。

    install方法被同一个插件多次调用,插件将只会被安装一次。

  • 源码

export function initUse(Vue: GlobalAPI) {
 // Vue.use是一个函数
 Vue.use = function (plugin: Function | any) {
   // 创建安装的插件数组,用来保存插件实例
   const installedPlugins =
     this._installedPlugins || (this._installedPlugins = [])
   // 去重  
   if (installedPlugins.indexOf(plugin) > -1) {
     return this
   }
   
   const args = toArray(arguments, 1)
   args.unshift(this)
   // isFunction => typeof value === 'function'
   // 检测如果有plugin的install方法, 如果plugin是一个对象
   if (isFunction(plugin.install)) {
     // 执行插件的install方法进行安装
     plugin.install.apply(plugin, args)
     
   // 这里直接当做plugin是一个函数,那么函数直接回当做install方法执行  
   } else if (isFunction(plugin)) {
     plugin.apply(null, args)
   }
   // 保存插件
   installedPlugins.push(plugin)
   return this
 }
}

所以Vue.use的原理就是install方法。

  • plugin为对象的时候,必须要提供一个install方法。
  • plugin为函数的时候,则会被当做install方法执行。
  • 开发插件: 参考

app.use

vue3app.use代码过于简单,如下:

image.png

Vue.component的依赖

  • 使用插件:Vue.component('my-component', { /* ... */ })

Vue3中:

import { createApp } from 'vue'

const app = createApp({})

// 注册一个选项对象
app.component('my-component', {
  /* ... */
})

// 得到一个已注册的组件
const MyComponent = app.component('my-component')

原理

  • 参数

    • {string} id
    • {Function | Object} [definition]
  • 用法

    注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称

// 注册组件,传入一个扩展过的构造器 
Vue.component('my-component', Vue.extend({ /* ... */ }))  
// 注册组件,传入一个选项对象 (自动调用 Vue.extend) 
Vue.component('my-component', { /* ... */ })  
// 获取注册的组件 (始终返回构造器) 
var MyComponent = Vue.component('my-component')
  • 示例vue2.x
// 定义一个名为 button-counter 的新组件  
Vue.component('button-counter', {  
   data: function () {  
       return {  
           count: 0  
       }  
   },  
   template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'  
})
// template
<template>
   <div id="app">
       <button-counter />
   </div>
</template>

codeSandBox地址

Vue.component方法执行,发生在初始化的时候,可以参考Vue源码解析系列(一) -- 初始化类new Vue。初始化的时候vue会调用initGlobalAPI进行各种初始化,其中component方法就发生在initAssetRegisters中。

initAssetRegisters

export function initAssetRegisters(Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  //  ASSET_TYPES : ['component', 'directive', 'filter']
  ASSET_TYPES.forEach(type => {
    // 给Vue.component等方法赋值
    Vue[type] = function (
      id: string, // 组件名
      definition?: Function | Object // 参数
    ): Function | Object | void {
    
      // 如果没有参数Vue.component('my-component')
      if (!definition) {
      
        // 则取得是Vue.options.components[id]
        return this.options[type + 's'][id]
        
      } else {
      
        // 如果有参数, Vue.component('my-component', {...})  
        // 检查组件名是否合法
        if (__DEV__ && type === 'component') {
          validateComponentName(id)
        }
        
        // Vue.component走这里
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          // 调用Vue.extend
          definition = this.options._base.extend(definition)
        }
        ...
        // 绑定组件至Vue.options.components上
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

所以这就是Vue.component的原理,

  • 如果有第二参数,则通过Vue.extend返回组件,然后通过Vue.options.componnets绑定组件。
  • 如果没有第二参数,则直接用Vue.options.components绑定组件名。
  • new Vue的时候能够使用这样的组件。
  • 我画了一个大致的图:

image.png

Vue.extend

那么Vue.extend方法是基于Vue的构造器创建一个子类实例,并返回这个实例,用法如下。

var Profile = Vue.extend({
    template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
    data: function () {
      return {
        firstName: 'Walter',
        lastName: 'White',
        alias: 'Heisenberg'
      }
    }
})
// 挂在到元素上
new Profile().$mount('#mount-point')

源码

Vue.extend = function (extendOptions: any): typeof Component {
    extendOptions = extendOptions || {} // 兼容有无Vue.component的第二参数
    const Super = this // Vue
    const SuperId = Super.cid // Vue.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    
    // 获得组件名
    const name =
      getComponentName(extendOptions) || getComponentName(Super.options)
    if (__DEV__ && name) {
      validateComponentName(name)
    }
    
    // 创建VueComponent
    const Sub = function VueComponent(this: any, options: any) {
      this._init(options) // 初始化参数
    } as unknown as typeof Component
    // 创建原型对象
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub // 指向构造函数
    Sub.cid = cid++
    Sub.options = mergeOptions(Super.options, extendOptions) // 合并配置
    Sub['super'] = Super //通过'super'属性指向Vue
    
    if (Sub.options.props) {
      initProps(Sub)  // 初始化组件的props
    }
    if (Sub.options.computed) {
      initComputed(Sub) // 初始化组件的computed
    }

    // 给实例组件绑定Vue的全局方法
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    //给实例组件绑定component、directive、filter方法
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    
    // 绑定到实例组件的components里面
    if (name) {
      Sub.options.components[name] = Sub
    }

    // 更新时同步data、props、computed等
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // 缓存构造器
    cachedCtors[SuperId] = Sub
    // 返回自身VueComponent
    return Sub
}

所以Vue.extend就是基于Vue构造器创建一个子实例对象并返回出去,也就是在Vue.component的时候,有definition会根据这一步走,产生子实例组件于Vue.options.compoents绑定。

app.component

Vue3app.component方法跟Vue2处理逻辑差不多,先检测名字是否合法,再根据第二参数进行components绑定。 image.png

总结

这一章我们主要是了解了Vue.useVue.component的用法和原理,在Vue2里面基于initRender会调用_c函数,根据render函数的执行加入到创建vnode的过程中去,在vue3里面则是根据setup函数执行机制。下一章我们将会去了解一下关于Vue更多的功能原理。