Basic usage
Define components globally:
// Define a b component in the form of Vue.component() Vue.component('b', {<!-- --> template: '<p>{<!-- -->{ a }}{<!-- -->{ name }}</p>', data () {<!-- --> return {<!-- --> a: 999 } }, created(){<!-- --> // ('created of child component') }, beforeMount(){<!-- --> // ('beforeMount of subcomponent') } })
Locally defined components:
new Vue({<!-- --> components: {<!-- --> b: {<!-- --> template: '<a>{<!-- -->{ name }}</a>', data(){<!-- --> return {<!-- --> name: 'I am a component' } } } }, })
Globally defined components are implemented through the component method on the constructor; this method has been analyzed in the global API chapter, and the following is a brief review:
component method
// source code ASSET_TYPES.forEach(type => {<!-- --> Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void {<!-- --> // There is no definition, which means acquisition, directly from the corresponding typs + 's' under options if (!definition) {<!-- --> return this. options[type + 's'][id] } else {<!-- --> // if it is a component if (type === 'component' & amp; & amp; isPlainObject(definition)) {<!-- --> // use id without name definition.name = definition.name || id // Make definition inherit vue definition = this.options._base.extend(definition) } // Settings this.options[type + 's'][id] = definition return definition } } }) // In order to facilitate understanding, modify according to the source code Vue. component = function (id, definition) {<!-- --> definition.name = definition.name || id // Create the class of the child component and inherit from the parent component definition = this.options._base.extend(definition) // record Vue.options.components[id] = definition }
As can be seen from the above code, the component only does two things. The first is to assign the name or id to the name attribute of the component configuration item, then execute the extend method to create the component, and finally place it in the components under options; extend also It has been analyzed in the global API chapter, and a brief review is also given below;
extend method
Vue.extend = function (extendOptions: Object): Function {<!-- --> // Initialization parameters extendOptions = extendOptions || {<!-- -->} // Store the parent class const Super = this // Store the unique identifier of the parent class const SuperId = Super.cid // Get the cached data in the parameter const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {<!-- -->}) // If it has been cached, it will be retrieved from the cache and returned if (cachedCtors[SuperId]) {<!-- --> return cachedCtors[SuperId] } // Get the name const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' & amp; & amp; name) {<!-- --> validateComponentName(name) } // Create a subclass const Sub = function VueComponent (options) {<!-- --> this._init(options) } // prototypal inheritance Sub.prototype = Object.create(Super.prototype) // modify the constructor Sub.prototype.constructor = Sub // The unique identifier of the subclass Sub.cid = cid++ // Merge the parent class and the parameters passed in Sub.options = mergeOptions( Super. options, extendOptions ) // Store the parent class in the child class Sub['super'] = Super // If there are props in the subclass to initialize if (Sub. options. props) {<!-- --> initProps(Sub) } // If there is a computed property in the subclass, initialize it if (Sub. options. computed) {<!-- --> initComputed(Sub) } // Put the extend, mixin, and use of the parent class on the subclass Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // Copy all the specified attributes from the parent class to the subclass ASSET_TYPES.forEach(function (type) {<!-- --> Sub[type] = Super[type] }) if (name) {<!-- --> Sub.options.components[name] = Sub } Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({<!-- -->}, Sub.options) // cache constructor // Cache subclass cachedCtors[SuperId] = Sub return Sub } }
Extend mainly defines a subclass, which inherits the parent class through prototype inheritance; the subclass mainly calls the _init method for initialization; then merges some attributes, and stores the subclass in the cache, in the function Get it from the cache at the beginning;
The above completes the initialization of the component; the following is the process of how to identify, create and mount the components used in the template by compiling the template;
Approximate process
- After the template is compiled, when the dom is mounted, the render function will be executed to generate a Vnode, and the render function will call different functions to generate different vnodes according to different types of nodes;
- When it is an element node, it will call the Vnode function that creates the element type. This function internally judges whether it is the original label through the label name of the element, and if it is not the original label, it is a component;
- If it is a component, the corresponding component data is obtained from the components of the option
- After the component data is obtained, it can be judged whether it is a globally added component or a locally added component
- Partially added components call the parent’s extend method to implement component inheritance and return the constructor of the child component
- Create the Vnode corresponding to the component
- When creating a real dom based on Vnode through patch, it will judge whether the current Vnode is a component. If it is a component, execute the constructor of the component, create an instance of the subcomponent, and then call the
$mout
of the subcomponent method to generate the real dom and mount it on $el;
The corresponding Vnode is generated in the render function. It has been analyzed in the template compilation chapter. The render function uses different functions to wrap and execute according to different node types. Element nodes are wrapped by _c, so see the _c function for details.
// source location ./src/core/instance/render.js export function initRender (vm: Component) {<!-- --> ... // _c function to create a virtual node is used internally vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) ... }
You can see that the createElement function is executed inside the _c function and returns its result;
// Source location ./src/core/vdom/create-element.js export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> {<!-- --> ... return _createElement(context, tag, data, children, normalizationType) }
The _createElement function is called inside the createElement function;
// Source location ./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, ns if (typeof tag === 'string') {<!-- --> let Ctor ns = (context.$vnode & amp; & amp; context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) {<!-- --> vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) & amp; & amp; isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {<!-- --> vnode = createComponent(Ctor, data, context, children, tag) } else {<!-- --> vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else {<!-- --> vnode = createComponent(tag, data, context, children) } if (Array.isArray(vnode)) {<!-- --> return vnode } else if (isDef(vnode)) {<!-- --> if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else {<!-- --> return createEmptyVNode() } }
If it is an original tag, create the corresponding virtual dom
if (config.isReservedTag(tag)) {<!-- --> vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) }
If it is a component, call the createComponent method
else if ((!data || !data.pre) & amp; & amp; isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {<!-- --> // component vnode = createComponent(Ctor, data, context, children, tag) }
createComponent function
// Source location ./src/core/vdom/create-component.js export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void {<!-- --> if (isUndef(Ctor)) {<!-- --> return } const baseCtor = context. $options._base if (isObject(Ctor)) {<!-- --> Ctor = baseCtor. extend(Ctor) } ... // install component management hooks onto the placeholder node installComponentHooks(data) // return a placeholder vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${<!-- -->Ctor.cid}${<!-- -->name ? `-${<!-- -->name}` : ''}`, data, undefined, undefined, undefined, context, {<!-- --> Ctor, propsData, listeners, tag, children }, asyncFactory ) return vnode }
If the ctor does not exist, return it directly, get the Vue constructor through _base, and judge whether the Ctor is an object, if it is an object, it is an internal component, create the class of the Ctor component through extend, and inherit the child Vue;
if (isUndef(Ctor)) {<!-- --> return } const baseCtor = context. $options._base if (isObject(Ctor)) {<!-- --> Ctor = baseCtor. extend(Ctor) }
Call the installComponentHooks method, which is used to merge some methods of external and internal components and mount them under the data hook, and use them when creating the corresponding real dom in the future; mainly analyze the init method in the hook
const componentVNodeHooks = {<!-- --> init (vnode: VNodeWithData, hydrating: boolean): ?boolean {<!-- --> const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating? vnode.elm : undefined, hydrating) }, ... } const hooksToMerge = Object. keys(componentVNodeHooks) function installComponentHooks (data: VNodeData) {<!-- --> const hooks = data.hook || (data.hook = {<!-- -->}) for (let i = 0; i < hooksToMerge. length; i ++ ) {<!-- --> const key = hooksToMerge[i] const existing = hooks[key] const toMerge = componentVNodeHooks[key] if (existing !== toMerge & amp; & amp; !(existing & amp; & amp; existing._merged)) {<!-- --> hooks[key] = existing? mergeHook(toMerge, existing) : toMerge } } } function mergeHook (f1: any, f2: any): Function {<!-- --> const merged = (a, b) => {<!-- --> // flow complains about extra args which is why we use any f1(a, b) f2(a,b) } merged._merged = true return merged }
The init method internally calls the createComponentInstanceForVnode method to execute the constructor of the current component, returns the instance of the current component, and assigns it to componentInstance, and finally calls mount to create a real dom; (this method is used to create an instance of the component, so that it can be called Vue’s init initializes some properties and methods of the component itself. After the initialization, execute the mout method to create the real dom of the component and mount it on $el of the component instance)
export function createComponentInstanceForVnode ( // we know it's MountedComponentVNode but flow doesn't vnode: any, // activeInstance in lifecycle state parent: any ): Component {<!-- --> return new vnode.componentOptions.Ctor(options) }
The class of the new component inside the createComponentInstanceForVnode function returns an instance; the installComponentHooks method is executed at this point; then the virtual dom of the component is created;
const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${<!-- -->Ctor.cid}${<!-- -->name ? `-${<!-- -->name}` : ''}`, data, undefined, undefined, undefined, context, {<!-- --> Ctor, propsData, listeners, tag, children }, asyncFactory )
The above is the Vnode of the component created; after the render function is executed, the corresponding Vnode is created; the next step is to mount and create a real dom; the whole process of mounting to creation can be seen in the overall process of the source code analysis chapter, and the specific dom creation You can read the diff algorithm and diff algorithm; the following is directly how to process components in the creation of real dom;
// source location ./src/core/vdom/patch.js function createElm ( vnode, insertedVnodeQueue, parent Elm, refElm, nested, ownerArray, index ) {<!-- --> if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {<!-- --> return } .... }
The createElm function is used to create a real dom. First, the createComponent function is called to judge and process the Vnode of the component;
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- --> let i = vnode.data if (isDef(i)) {<!-- --> const isReactivated = isDef(vnode.componentInstance) & amp; & amp; i.keepAlive if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- --> i(vnode, false /* hydrating */) } if (isDef(vnode. componentInstance)) {<!-- --> initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) {<!-- --> reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
Get the data attribute of the component, if the data exists, continue to execute;
if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- --> i(vnode, false /* hydrating */) }
Get the hook in the data, and then get the init in the hook. If init exists, execute the init function and pass the virtual node in. This step is connected with the init function under the componentVNodeHooks analyzed above, and the init function is created internally. An instance of the component is created to initialize the data of the component and create a real dom;
if (isDef(vnode.componentInstance)) {<!-- --> initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm)
If the instance of the component already exists, then initialize the component, and then insert the component directly into the parent element;
function initComponent (vnode, insertedVnodeQueue) {<!-- --> if (isDef(vnode.data.pendingInsert)) {<!-- --> insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) vnode.data.pendingInsert = null } vnode.elm = vnode.componentInstance.$el if (isPatchable(vnode)) {<!-- --> invokeCreateHooks(vnode, insertedVnodeQueue) setScope(vnode) } else {<!-- --> // empty component root. // skip all element-related modules except for ref (#3455) registerRef(vnode) // make sure to invoke the insert hook insertedVnodeQueue.push(vnode) } } function insert (parent, elm, ref) {<!-- --> if (isDef(parent)) {<!-- --> if (isDef(ref)) {<!-- --> if (nodeOps. parentNode(ref) === parent) {<!-- --> nodeOps. insertBefore(parent, elm, ref) } } else {<!-- --> nodeOps.appendChild(parent, elm) } } }
If it is keepAlive, it will be processed by the reactivateComponent function
if (isTrue(isReactivated)) {<!-- --> reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- --> let i let innerNode = vnode while (innerNode. componentInstance) {<!-- --> innerNode = innerNode.componentInstance._vnode if (isDef(i = innerNode.data) & amp; & amp; isDef(i = i.transition)) {<!-- --> for (i = 0; i < cbs.activate.length; + + i) {<!-- --> cbs.activate[i](emptyNode, innerNode) } insertedVnodeQueue.push(innerNode) break } } insert(parentElm, vnode.elm, refElm) }
Asynchronous components
basic use
// The first way callback function Vue.component('async-webpack-example', function (resolve) {<!-- --> // This special `require` syntax will tell webpack // Automatically split your build code into multiple packages, these packages // will be loaded via an Ajax request require(['./my-async-component'], resolve) }) // The second way promise Vue. component( 'async-webpack-example', // This `import` function returns a `Promise` object. () => import('./my-async-component') ) // The third way advanced gameplay const AsyncComponent = () => ({<!-- --> // Component to load (should be a `Promise` object) component: import('./MyComponent.vue'), // Components used when loading asynchronous components loading: LoadingComponent, // Component to use when loading fails error: ErrorComponent, // Display the delay time of the component when loading. Default value is 200 (milliseconds) delay: 200, // If a timeout is provided and component loading also times out, // Then use the component used when loading failed. The default value is: `Infinity` timeout: 3000 })
During the vnode creation of the component analyzed above, the asynchronous component is also processed inside the createComponent function;
// Source location ./src/core/vdom/create-component.js export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void {<!-- --> if (isUndef(Ctor)) {<!-- --> return } const baseCtor = context. $options._base // plain options object: turn it into a constructor if (isObject(Ctor)) {<!-- --> Ctor = baseCtor. extend(Ctor) } // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') {<!-- --> return } // async component // asynchronous component let asyncFactory // If there is no cid to indicate an asynchronous component, because the extend method will not be called if (isUndef(Ctor. cid)) {<!-- --> asyncFactory = Ctor // Handle asynchronous components Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // if there is no result define and return a placeholder component if (Ctor === undefined) {<!-- --> return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } ... }
above code. First judge whether Ctor is an object, an object means it is a non-asynchronous component, because an asynchronous component is a function; judge whether it is an asynchronous component by cid, if cid does not exist, it means an asynchronous component, because it does not execute the extend method, through the resolveAsyncComponent function Process the asynchronous component function, if the returned demerit has no value, then create and return a placeholder component;
// Source location ./src/core/vdom/helpers/resolve-async-component.js export function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component> ): Class<Component> | void {<!-- --> // force render function const forceRender = (renderCompleted: boolean) => {<!-- --> for (let i = 0, l = owners. length; i < l; i ++ ) {<!-- --> (owners[i]: any). $forceUpdate() } if (renderCompleted) {<!-- --> owners. length = 0 if (timerLoading !== null) {<!-- --> clearTimeout(timerLoading) timerLoading = null } if (timerTimeout !== null) {<!-- --> clearTimeout(timerTimeout) timerTimeout = null } } } // resolve function const resolve = once((res: Object | Class<Component>) => {<!-- --> // Inside the ensureCtor function, the constructor of the component is created through the extend method factory.resolved = ensureCtor(res, baseCtor) // force rendering if (!sync) {<!-- --> forceRender(true) } else {<!-- --> owners. length = 0 } }) // reject function const reject = once(reason => {<!-- --> if (isDef(factory. errorComp)) {<!-- --> factory.error = true forceRender(true) } }) // Execute the function of the asynchronous component and pass resolve and reject in for execution. Implement the first method of defining an asynchronous component const res = factory(resolve, reject) // Determine whether the returned result is an object if (isObject(res)) {<!-- --> // Is it a promise to implement the second method of defining asynchronous components if (isPromise(res)) {<!-- --> // If factory.resolved does not exist, it means that the resolve method has not been executed if (isUndef(factory.resolved)) {<!-- --> // pass resolve and reject through then res.then(resolve, reject) } // If there is a component attribute, implement the third method of defining asynchronous components } else if (isPromise(res. component)) {<!-- --> // Execute resolve and reject through then res.component.then(resolve, reject) // If there is an error attribute, define the error component if (isDef(res. error)) {<!-- --> factory.errorComp = ensureCtor(res.error, baseCtor) } // If there is laoding definition loading component if (isDef(res.loading)) {<!-- --> factory.loadingComp = ensureCtor(res.loading, baseCtor) if (res.delay === 0) {<!-- --> // start loading directly without delay attribute factory.loading = true } else {<!-- --> timerLoading = setTimeout(() => {<!-- --> timerLoading = null if (isUndef(factory.resolved) & amp; & amp; isUndef(factory.error)) {<!-- --> factory.loading = true forceRender(false) } }, res. delay || 200) } } if (isDef(res. timeout)) {<!-- --> timerTimeout = setTimeout(() => {<!-- --> timerTimeout = null if (isUndef(factory.resolved)) {<!-- --> reject( process.env.NODE_ENV !== 'production' ? `timeout (${<!-- -->res.timeout}ms)` : null ) } }, res. timeout) } } } sync = false // If there is a loading attribute, return the loading component, otherwise it is an asynchronous component return factory.loading ?factory.loadingComp : factory.resolved }
The resolveAsyncComponent method mainly defines the resolve and reject functions inside, and executes the functions of the asynchronous component, and passes these two methods into it. At this time, the first way of using the asynchronous component is realized; then by judging the type of the returned result To achieve the other two methods; if it is a promise, then pass the Resolve and reject methods through then, this step realizes the second method; if the result has a component attribute, pass resolve and reject through the then method of the component, and process loading , error and other attributes and create the corresponding components. This step implements the third type; finally, if there is a loading attribute, return the loading component, otherwise it is the current asynchronous component;
Approximate process:
- Determine whether it is an asynchronous component by cid, and the asynchronous component creates resolve and reject methods, executes and passes resolve and reject to the asynchronous component function, and finally determines whether there is a loading attribute, and if so, returns the constructor of the loaded component, otherwise returns the current The constructor of the asynchronous component may not execute resolve or reject at this time, so the constructor of the returned component is undefined;
- If the result returned by the external judgment of the function is undefined, then create an empty virtual node to represent the placeholder component;
- Generate virtual nodes that load components
- Patch creates a real node, and the loading component will be rendered on the page at this time
- If the asynchronous component executes the resolve method at this time, the constructor of the asynchronous component will be created inside the Resolve function, and $forceUpdate will be used to force the update. At this time, the update method of the watcher will be executed to update the view; the asynchronous component will be rendered on the page