Recommended PC viewing, mobile code highlighting disorder

In vue.js, except for its built-in components such as keep-alive, Component, transition, transition-group, etc., other user-defined components must be registered before they can be used. Many students may encounter the following error message during the development process:

This error is usually reported because we are using an unregistered component. Vue provides both global and local registration methods.

1. The components and strategies

Before you do that, take a look at the Components policy in mergeOptions. Configuration merge concepts were introduced in the previous section.

// src/core/util/options.js



// components merge strategy

function mergeAssets (

  parentVal: ?Object.

  childVal: ?Object.

vm? : Component,

  key: string

) :Object 
{

  const res = Object.create(parentVal || null)

  if (childVal) {

    // ...

    return extend(res, childVal)

  } else {

    return res

  }

}



// ASSET_TYPES: ['component', 'directive', 'filter']

ASSET_TYPES.forEach(function (type{

  strats[type + 's'] = mergeAssets

})

Copy the code

As you can see, the merge strategy for Components is simple, returning a RES object with properties childVal and a prototype parentVal.

A quick review of mergeOptions initialization logic:

// src/core/instance/init.js



Vue.prototype._init = function (options? :Object{

  // merge options

  if (options && options._isComponent) {

    initInternalComponent(vm, options)

  } else {

    vm.$options = mergeOptions(

      resolveConstructorOptions(vm.constructor), // Vue.options

      options || {},

      vm

    )

  }

  // ...

}

Copy the code

For the root instance, when the else logic is done: VM.code.components.__proto__ === vue.options.ponents

For component instances, when we create the constructor via vue.extend:

// src/core/global-api/extend.js



Vue.extend = function (extendOptions: Object) :Function {

  // ...

  Sub.options = mergeOptions(

    Super.options,

    extendOptions

  )

  // ...

  

  // Enable recursive self-check

  if (name) {

    Sub.options.components[name] = Sub

  }

  

  // ...

  return Sub

}

Copy the code

Merges vue. options with sub. options via mergeOptions.

Vue.options.com Ponents look like this:

After the merger, Sub.options.components will look like this:

Sub.options.ponents.__proto__ === vue.options.ponents

Back in our vue.extend, this logic is also executed:

// Enable recursive self-check
if (name) {
    Sub.options.components[name] = Sub
}
Copy the code

Self-lookup by pointing to itself, so our Sub.options.components look like this:

Then, in the subsequent component instantiation phase, initInternalComponent is executed. The key logic is:

vm.$options = Object.create(vm.constructor.options)
Copy the code

This will allow access to Sub.options.components via VM. code.options.com Ponents

The final conclusion with a picture is:

2. Register globally

Examples of global registration:

Vue.component('my-component', {

  / / options

})

Copy the code

SRC /core/global-api/assets.js

// src/core/global-api/assets.js



export function initAssetRegisters (Vue: GlobalAPI{

  // ASSET_TYPES: ['component', 'directive', 'filter']

  ASSET_TYPES.forEach(type= > {

    Vue[type] = function (

      id: string.

      definition: Function | Object

    
) :Function | Object | void 
{

      if(! definition) {

        return this.options[type + 's'][id]

      } else {

        if(process.env.NODE_ENV ! = ='production' && type= = ='component') {

          validateComponentName(id) // Verify the component name

        }

        if (type= = ='component' && isPlainObject(definition)) {

          definition.name = definition.name || id

          definition = this.options._base.extend(definition)

        }

        // ...

        this.options[type + 's'][id] = definition

        return definition

      }

    }

  })

}

Copy the code

Vue.options.ponents [id] Vue.options.ponents [id]

As for why this.options._base is Vue, we’ve already covered it here

3. Partial registration

When the createElement argument passed in is a component string, you need to register the local component with the Components option

const App = {
  name: 'app',
  render(h) {
    return h('div', {}, 'hello vue')
  },
}

new Vue({
  el: '#app',
  render(h) {
    return h('App')
  },
  components: { App }
})
Copy the code

Let’s review the logic of _createElement again:

// src/core/vdom/create-element.js



export function _createElement (

  context: Component,

tag? :string | Class<Component> | Function | Object.

data? : VNodeData,

children? :any.

normalizationType? :number

) :VNode | Array<VNode
{

  // ...

  let vnode

  if (typeof tag === 'string') {

    let Ctor

    // ...

    if (config.isReservedTag(tag)) {

      // platform built-in elements

      vnode = new VNode(

        config.parsePlatformTagName(tag), data, children,

        undefined.undefined, context

      )

    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {

      // component

      vnode = createComponent(Ctor, data, context, children, tag)

    } else {

      vnode = new VNode(

        tag, data, children,

        undefined.undefined, context

      )

    }

  } else {

    // direct component options / constructor

    vnode = createComponent(tag, data, context, children)

  }

  // ...

}

Copy the code
  • whentagIs a string:
    • ordinaryhtmlTag, created directlyvnode
    • The registered component string, parsed to the component constructor, passescreateComponentCreate a placeholder for the componentvnode
    • Create an unknownvnode
  • whentagDirect is a component object when passedcreateComponentCreate a placeholder for the componentvnode

Let’s see if tag is a component string:

ResolveAsset resolves the tag string to a component constructor or component object (because the global vue.component constructor is already created, and local registration requires additional constructor creation from the component object in createComponent) :

Ctor = resolveAsset(context.$options, 'components', tag)
Copy the code

ResolveAsset this definition in the SRC/core/utils/options in js:

// src/core/utils/options.js



export function resolveAsset (

  options: Object.// vm.$options

  typestring.// components

  id: string.

warnMissing? :boolean

) :any 
{

  if (typeofid ! = ='string') {

    return

  }

  const assets = options[type// vm.$options.components

  

  // Local registration takes precedence: look up the object itself

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

  // camelize: app-child => appChild

  const camelizedId = camelize(id)

  if (hasOwn(assets, camelizedId)) return assets[camelizedId]

  // capitalize: appChild => AppChild

  const PascalCaseId = capitalize(camelizedId)

  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]

  

  // Go to the prototype chain to find the global registry

  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

}

Copy the code

Where the simplified Camelize function:

const camelizeRE = /-(\w)/g
function camelize(str) {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ' ')}Copy the code

Wherein, the capitalize function is simplified:

function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)})Copy the code

The main logic of resolveAsset is as follows:

  • invm.$options.componentsObject itself separatelyLocal registrationComponent constructor of
    • vm.$options.components[id]
    • vm.$options.components[camelizedId]
    • vm.$options.components[PascalCaseId]
  • invm.$options.componentsOn the prototypeGlobal registrationComponent constructor of
    • vm.$options.components.__proto__[id]
    • vm.$options.components.__proto__[camelizedId]
    • vm.$options.components.__proto__[PascalCaseId]

ResolveAsset (resolveAsset)

  • When a component is defined asA hyphenWhen, we can only use it in string templates<my-comp><my-comp>
  • When a component is defined ashumpWe can use it in the string template<my-comp><my-comp>.<myComp><myComp>
  • When a component is defined asUppercaseWe can use it in the string template<my-comp><my-comp>.<myComp><myComp>.<MyComp><MyComp>

conclusion

Note that local registries differ from global registries in that only components of that type can access the child components of local registries, whereas global registries are extended under vue. options, so during the creation of all components, Extension from the global Vue.options.components to the current component’s VM.code.components. ponents, which is why globally registered components can be used arbitrarily.