Vue source code interpretation (11)-render helper

Vue source code interpretation (11)-render helper

Preface

The last article Interpretation of Vue Source Code (10)-The Rendering Function Generated by the Compiler Finally, when the component is updated, you need to execute the rendering function generated by the compiler to get the vnode of the component.

The reason why the rendering function can generate vnode is through it

_c, _l,, _v, _s
And other methods. such as:

  • Ordinary nodes are compiled into executable _c functions

  • The v-for node is compiled into an executable _l function

  • ...

But so far we are not clear about the principles of these methods, how do they generate vnodes? Just know that they are Vue instance methods, today we will find the answer from the source code.

the goal

On the basis of the Vue compiler, further understand how a component generates VNode through these runtime tool methods (render helper)

Source code interpretation

Entrance

We know that these methods are Vue instance methods. According to the previous understanding of the source code, the instance methods are generally placed in

/src/core/instance
Under contents. In fact , I have seen it before in the Vue source code interpretation (6)-example method reading
render helper
,at the end of the article.

/src/core/instance/render.js

export function renderMixin ( Vue: Class<Component> ) { //install runtime convenience helpers //Mount some runtime tools and methods on the component instance installRenderHelpers(Vue.prototype) //... } Copy code

installRenderHelpers

/src/core/instance/render-helpers/index.js

/** * Mount the abbreviated rendering tool function on the instance, these are the runtime code * These tool functions are used in the rendering function generated by the compiler * @param {*} target Vue instance */ export function installRenderHelpers ( target: any ) { /** * Run-time helper of v-once instruction, add static mark to VNode * It s a bit redundant, because the nodes containing the v-once instruction are treated as static nodes, so they won t go here */ target._o = markOnce //convert the value to a number target._n = toNumber /** * Convert the value to string form, ordinary value => String(val), object => JSON.stringify(val) */ target._s = toString /** * The help function for rendering the v-for list at runtime, looping through the val values, executing the render method for each item in turn to generate VNodes, and finally returning a VNode array */ target._l = renderList target._t = renderSlot /** * Determine whether two values are equal */ target._q = looseEqual /** * Equivalent to indexOf method */ target._i = looseIndexOf /** * The helper program of VNode, which is responsible for generating the static tree at runtime, accomplishes the following two things * 1. Execute the rendering function of the specified subscript in the staticRenderFns array, generate the VNode of the static tree and cache it, and read it directly from the cache during rendering (isInFor must be true) * 2. Make a static mark for the VNode of the static tree */ target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps /** * Create VNode for text node */ target._v = createTextVNode /** * Create a VNode for an empty node */ target._e = createEmptyVNode } Copy code

_o = markOnce

/src/core/instance/render-helpers/render-static.js

/** * Runtime helper for v-once. * Effectively it means marking the node as static with a unique key. * Run-time helper of v-once instruction, add static mark to VNode * It s a bit redundant, because the nodes containing the v-once instruction are treated as static nodes, so they won t go here */ export function markOnce ( tree: VNode | Array <VNode>, index: number, key: string ) { markStatic(tree, `__once__ ${index} ${key? `_ ${key} ` : `` } ` , true ) return tree } Copy code

markStatic

/src/core/instance/render-helpers/render-static.js

/** * Mark VNode statically and add three attributes on VNode: * {isStatick: true, key: xx, isOnce: true or false} */ function markStatic ( tree: VNode | Array <VNode>, key: string, isOnce: boolean ) { if ( Array .isArray(tree)) { //tree is an array of VNodes , loop through each VNode in it, and mark each VNode statically for ( let i = 0 ; i <tree.length; i++) { if (tree[i] && typeof tree[i] !== 'string' ) { markStaticNode(tree[i], ` ${key} _ ${i} ` , isOnce) } } } else { markStaticNode(tree, key, isOnce) } } Copy code

markStaticNode

/src/core/instance/render-helpers/render-static.js

/** * Mark static VNode */ function markStaticNode ( node, key, isOnce ) { node.isStatic = true node.key = key node.isOnce = isOnce } Copy code

_l = renderList

/src/core/instance/render-helpers/render-list.js

/** * Runtime helper for rendering v-for lists. * The help function for rendering the v-for list at runtime, looping through the val values, executing the render method for each item in turn to generate VNodes, and finally returning a VNode array */ export function renderList ( val: any, render: ( val: any, keyOrIndex: string | number, index?: number ) => VNode ):? Array < VNode > { let ret:? Array <VNode>, i, l, keys, key if ( Array .isArray(val) || typeof val === 'string' ) { //val is an array or String ret = new Array (val.length) for (i = 0 , l = val.length; i <l; i++) { ret[i] = render(val[i], i) } } else if ( typeof val === 'number' ) { //If val is a number, iterate through all the numbers 0-val ret = new Array (val) for (i = 0 ; i <val; i++) { ret[i] = render(i + 1 , i) } } else if (isObject(val)) { //val is an object, traverse the object if (hasSymbol && val[ Symbol .iterator]) { //val is an iterable object ret = [] const iterator: Iterator<any> = val[ Symbol .iterator]() let result = iterator.next() while (!result.done) { ret.push(render(result.value, ret.length)) result = iterator.next() } } else { //val is an ordinary object keys = Object .keys(val) ret = new Array (keys.length) for (i = 0 , l = keys.length; i <l; i++) { key = keys[i] ret[i] = render(val[key], key, i) } } } if (!isDef(ret)) { ret = [] } //Return the VNode array (ret: any)._isVList = true return ret } Copy code

_m = renderStatic

/src/core/instance/render-helpers/render-static.js

/** * Runtime helper for rendering static trees. * The helper program of VNode, which is responsible for generating static trees at runtime, accomplishes the following two things * 1. Execute the rendering function of the specified subscript in the staticRenderFns array, generate the VNode of the static tree and cache it, and read it directly from the cache during rendering (isInFor must be true) * 2. Make a static mark for the VNode of the static tree * @param {number} index represents the subscript index of the rendering function of the current static node in the staticRenderFns array * @param {boolean} isInFor indicates whether the current static node is wrapped in the node containing the v-for instruction */ export function renderStatic ( index: number, isInFor: boolean ): VNode | Array < VNode > { //Cache, the cached VNode is directly obtained from the cache when the static node is rendered for the second time const cached = this ._staticTrees || ( this ._staticTrees = []) let tree = cached[index] //if has already-rendered static tree and not inside v-for, //we can reuse the same tree. //If the current static tree has been rendered once (that is, there is a cache) and is not wrapped in Inside the node where the v-for instruction is located, it directly returns to the cached VNode if (tree && !isInFor) { return tree } //Execute the specified element in the staticRenderFns array (the rendering function of the static tree) to generate the VNode of the static tree, and cache it //otherwise, render a fresh tree. tree = cached[index] = this .$options.staticRenderFns[index]. call( this ._renderProxy, null , this //for render fns generated for functional component templates ) //Static mark, mark the VNode of the static tree, that is, add {isStatic: true, key: `__static__${index}`, isOnce: false} markStatic(tree, `__static__ ${index} ` , false ) return tree } Copy code

_c

/src/core/instance/render.js

/** * Define _c, which is a currying method of createElement * @param {*} a tag name * @param {*} the JSON string of the b attribute * @param {*} c child node array * @param {*} The normalized type of d node * @returns VNode or Array<VNode> */ Vm._c = ( A, B, C, D ) => the createElement (VM, A, B, C, D, to false ) copy the code

createElement

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

/** * Generate a vnode of a component or a common label, a wrapper function, don t care * wrapper function for providing a more flexible interface * without getting yelled at by flow */ export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array < VNode > { if ( Array .isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } //Execute the _createElement method to create the VNode of the component return _createElement(context, tag, data, children, normalizationType) } Copy code

_createElement

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

/** * Generate vnode, * 1. The platform retains tags and unknown elements and executes new Vnode() to generate vnode * 2. The component executes createComponent to generate vnode * 2.1 The functional component executes its own render function to generate VNode * 2.2 Ordinary components instantiate a VNode and set 4 methods on its data.hook object, which will be called during the patch phase of the component. * So as to enter the instantiation and mounting phase of the sub-components, until the rendering is completed * @param {*} context context * @param {*} tag tag * @param {*} data attribute JSON string * @param {*} children child node array * @param {*} normalizationType node normalization type * @returns VNode or Array<VNode> */ export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object , data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array < VNode > { if (isDef(data) && isDef((data: any).__ob__)) { //The attribute cannot be a reactive object process.env.NODE_ENV !== 'production' && warn ( `Avoid using observed data object as vnode data: ${ JSON .stringify(data)}/n` + 'Always create fresh vnode data objects in each render!' , context ) //If the attribute is a reactive object, return an empty node VNode return createEmptyVNode() } //object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { //If the is attribute of the dynamic component is a false value, if the tag is false, then a VNode of an empty node will be returned //in case of component :is set to falsy value return createEmptyVNode() } //Detect the unique key key, it can only be a string or a number //warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !( '@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.' , context ) } } //When there is only one function in the child node array, treat it as the default slot, and then clear the child node list //support single function children as default scoped slot if ( Array .isArray(children) && typeof children[ 0 ] == = 'function' ) { data = data || {} data.scopedSlots = { default : children[ 0 ]} children.length = 0 } //Standardize child elements if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } /** * The beginning is the focus here, the previous ones don t need to be concerned, basically some exception handling or optimization */ let vnode, ns if ( typeof tag === 'string' ) { //When the tag is a string, the tag has three possibilities: //1. The platform reserved tag //2. Custom component //3. Unknown Label let Ctor //Namespace ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { //tag is platform native tag //platform built-in elements if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) { //The .native of the v-on command only takes effect on the component warn( `The .native modifier for v-on is only valid on components but it was used on < ${tag} >.` , context ) } //instantiate a VNode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined , undefined , context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components' , tag))) { //tag is a custom component //in this.$options Find the component constructor with the specified tag name in the .components object //Create the component s VNode, the functional component directly executes its render function to generate a VNode, //ordinary components instantiate a VNode and set it on its data.hook object 4 methods, which will be called in the patch phase of the component, //so as to enter the instantiation and mounting phase of the sub-component, until the rendering is completed //component vnode = createComponent(Ctor, data, context, children, tag) } else { //An unknown tag, but also generates VNode, because it may be given a suitable namespace at runtime //unknown or unlisted namespaced elements //check at runtime because it may get assigned a namespace when its //parent normalizes children vnode = new VNode( tag, data, children, undefined , undefined , context ) } } else { //tag is a non-string, for example, it may be a component's configuration object or a component's constructor //direct component options/constructor vnode = createComponent(tag, data, context, children) } //Return the VNode of the component 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() } } Copy code

createComponent

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

/** * Create the VNode of the component, * 1. The functional component generates the component's VNode by executing its render method * 2. The common component generates its VNode through new VNode(), but an important operation of the common component is to set four hook functions on the data.hook object. * These are init, prepatch, insert, and destroy, which will be called during the patch phase of the component. * For example, the init method, when called, will enter the creation and mounting phase of the sub-component instance until the rendering is completed * @param {*} Ctor component constructor * @param {*} JSON string composed of data attributes * @param {*} context context * @param {*} children child node array * @param {*} tag tag name * @returns VNode or Array<VNode> */ export function createComponent ( Ctor: Class<Component> | Function | Object | void , data: ?VNodeData, context: Component, children:? Array <VNode>, tag?: string ): VNode | Array < VNode > | void { //The component constructor does not exist, end directly if (isUndef(Ctor)) { return } //Vue.extend const baseCtor = context.$options._base //When Ctor is a configuration object, use Vue.extend to turn it into a constructor//plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } //If so far, Ctor is still not a function, it means that this is an invalid component definition //if at this stage it's not a constructor or an async component factory, //reject. if ( typeof Ctor !== ' function' ) { if (process.env.NODE_ENV !== 'production' ) { warn( `Invalid Component definition: ${ String (Ctor)} ` , context) } return } //Asynchronous component //async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined ) { //Return a placeholder node for the asynchronous component. The component is rendered as a comment node, but all the original information of the node is retained. This information will be used for asynchronous server rendering and hydration return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } //The attribute JSON string of the node data = data || {} //This is actually the place where the components do option merging, that is, the compiler compiles the component into a rendering function, executes the render function during rendering, and then executes the _c in it, and you will come here. //Analyze the constructor options and merge Base class options to prevent global mixins from being applied after the component constructor is created //resolve constructor options in case global mixins are applied after //component constructor creation resolveConstructorOptions(Ctor) //Convert the information (value and callback) of the component's v-model into the attributes and values of the data.attrs object and the events and callbacks on the data.on object //transform component v-model data into props & events if (isDef (data.model)) { transformModel(Ctor.options, data) } //Extract the props data to get the propsData object, propsData[key] = val //Use the property in the component props configuration as the key, and the corresponding data in the parent component as the value //extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag ) //Functional component //functional component if (isTrue(Ctor.options.functional)) { /** * Execute the render function of the functional component to generate the VNode of the component, and do the following 3 things: * 1. Set the props object of the component * 2. Set the rendering context of the functional component and pass it to the render function of the functional component * 3. Call the render function of the functional component to generate vnode */ return createFunctionalComponent(Ctor, propsData, data, context, children) } //Get event listener object data.on, because these listeners need to be handled as child component listeners, not DOM listeners //extract listeners, since these needs to be treated as //child component listeners instead of DOM listeners const listeners = data.on //Assign the event object with the .native modifier to data.on //replace with listeners with .native modifier //so it gets processed during parent component patch. data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { //If it is an abstract component, the values retain props, listeners and slot //abstract components do not keep anything //other than props & listeners & slot //work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } /** * Set the hook object on the data object of the component, * The hook object adds four attributes, init, prepatch, insert, destroy, * Responsible for the creation, update, and destruction of components, these methods will be called during the patch phase of the component * install component management hooks onto the placeholder node */ installComponentHooks(data) const name = Ctor.options.name || tag //The VNode that instantiates the component, the tag name of the ordinary component will be special, vue-component-${cid}-${name} const vnode = new VNode( `vue -component- ${Ctor.cid} ${name? `- ${name} ` : '' } ` , data, undefined , undefined , undefined , context, {Ctor, propsData, listeners, tag, children }, asyncFactory ) //Weex specific: invoke recycle-list optimized @render function for //extracting cell-slot template. //https://github.com/Hanks10100/weex-native-directive/tree/master/component /* istanbul ignore if */ if (__WEEX__ && isRecyclableComponent(vnode)) { return renderRecyclableComponentTemplate(vnode) } return vnode } Copy code

resolveConstructorOptions

/src/core/instance/init.js

/** * Parse configuration items from the constructor */ export function resolveConstructorOptions ( Ctor: Class<Component> ) { //Get options from the instance constructor let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) //Cache const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // Indicates that the configuration items of the base class have changed //super option changed, //need to resolve new options. Ctor.superOptions = superOptions //check if there are any late-modified/attached options (#4976) //Find the changed options const modifiedOptions = resolveModifiedOptions(Ctor) //update base extend options if (modifiedOptions) { //The changed options and extend options merge extend(Ctor.extendOptions, modifiedOptions) } //Assign the new option to options options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options } Copy code

resolveModifiedOptions

/src/core/instance/init.js

/** * Analyze the options that are subsequently modified or added in the constructor options */ function resolveModifiedOptions ( Ctor: Class<Component> ):? Object { let modified //constructor options const latest = Ctor.options //sealed constructor options, backup const sealed = Ctor.sealedOptions //compare two options , Record inconsistent options for ( const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} modified[key] = latest[key] } } return modified } Copy code

transformModel

src/core/vdom/create-component.js

/** * Convert the information (value and callback) of the component's v-model into the attributes and values of the data.attrs object and the events and callbacks on the data.on object * transform component v-model info (value and callback) into * prop and event handler respectively. */ function transformModel ( options, data: any ) { //model attributes and events, the default is value and input const prop = (options.model && options.model.prop) || 'value' const event = (options. model && options.model.event) || 'input' //Store the value of v-model on the data.attrs object ; (data.attrs || (data.attrs = {}))[prop] = data.model.value //Store v-model events on the data.on object const on = data.on || (data.on = ()) //Existing event callback function const existing = on[event] //v-model The callback function corresponding to the event const callback = data.model.callback //merge the callback function if (isDef(existing)) { if ( Array .isArray(existing) ? existing.indexOf(callback) ===- 1 : existing !== callback ) { on[event] = [callback].concat(existing) } } else { on[event] = callback } } Copy code

extractPropsFromVNodeData

/src/core/vdom/helpers/extract-props.js

/** * <comp :msg="hello vue"></comp> * * Extract props, get res[key] = val * * Take the property in the props configuration as the key, and the corresponding data in the parent component as the value * When the data in the parent component is updated, trigger a responsive update, re-execute the render, generate a new vnode, and come here again * In this way, the corresponding data in the sub-component will be updated */ export function extractPropsFromVNodeData ( data: VNodeData, //{msg:'hello vue'} Ctor: Class<Component>, //component constructor tag?: string //component tag name ):? Object { //component's props option, {props: {msg: {type: String, default: xx}}} // We are only extracting raw values here. //validation and default values are handled in the child //component itself. const propOptions = Ctor.options. //we are only extracting raw values here. //validation and default values are handled in the child //component itself. const propOptions = Ctor.options. props if (isUndef(propOptions)) { //undefined props directly return return } //Use the property in the component props configuration as the key, and the value passed by the parent component is the value //When the data in the parent component is updated, a responsive update is triggered, the render is executed again, a new vnode is generated, and here again //In this way the corresponding data in the sub-component will be updated const res = {} const {attrs, props} = data if (isDef(attrs) || isDef(props)) { //Traverse propsOptions for ( const key in propOptions) { //Convert the key in the small camel case form to a hyphenated form const altKey = hyphenate(key) //Tip, if the declared props are in the small camel case form (testProps), but because html is case-insensitive, it should be in the html template Use test-props instead of testProps if (process.env.NODE_ENV !== 'production' ) { const keyInLowerCase = key.toLowerCase() if ( key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) ) { tip( `Prop " ${keyInLowerCase} " is passed to component ` + ` ${formatComponentName(tag || Ctor)} , but the declared prop name is` + `" ${key} ". ` + `Note that HTML attributes are case-insensitive and camelCased ` + `props need to use their kebab-case equivalents when using in-DOM` + `templates. You should probably use " ${altKey} " instead of " ${key} ".` ) } } checkProp(res, props, key, altKey, true ) || checkProp(res, attrs, key, altKey, false ) } } return res } Copy code

checkProp

/src/core/vdom/helpers/extract-props.js

/** * Get res[key] = val */ function checkProp ( res: Object , hash:? Object , key: string, altKey: string, preserve: boolean ): boolean { if (isDef(hash)) { //Determine whether there is a key or altKey in the hash (props/attrs) object //If it exists, set it to res => res[key] = hash[key] if (hasOwn( hash, key)) { res[key] = hash[key] if (!preserve) { delete hash[key] } return true } else if (hasOwn(hash, altKey)) { res[key] = hash[altKey] if (!preserve) { delete hash[altKey] } return true } } return false } Copy code

createFunctionalComponent

/src/core/vdom/create-functional-component.js

installRenderHelpers(FunctionalRenderContext.prototype) /** * Execute the render function of the functional component to generate the VNode of the component, and do the following 3 things: * 1. Set the props object of the component * 2. Set the rendering context of the functional component and pass it to the render function of the functional component * 3. Call the render function of the functional component to generate vnode * * @param {*} Ctor component constructor * @param {*} propsData additional props object * @param {*} data JSON string composed of node attributes * @param {*} contextVm context * @param {*} children child node array * @returns Vnode or Array<VNode> */ export function createFunctionalComponent ( Ctor: Class<Component>, propsData:? Object , data: VNodeData, contextVm: Component, children:? Array <VNode> ): VNode | Array < VNode > | void { //Component configuration items const options = Ctor.options //Get props object const props = {} //The props options of the component itself const propOptions = options .props //Set the props object of the functional component if (isDef(propOptions)) { //Explain that the functional component itself provides the props option, then set the value of props.key to the value of the corresponding key passed down on the component for ( const key in propOptions) { props[key] = validateProp(key, propOptions, propsData || emptyObject) } } else { //If the current functional component does not provide props, the attributes on the component will be automatically parsed as props if (isDef(data.attrs)) mergeProps(props, data.attrs) if (isDef(data.props)) mergeProps(props, data.props) } //Instantiate the rendering context of the functional component const renderContext = new FunctionalRenderContext( data, props, children, contextVm, Ctor ) //Call the render function, generate vnode, and pass _c and rendering context to the render function const vnode = options.render.call( null , renderContext._c, renderContext) //Add some marks to the last generated VNode object, indicating that the VNode is generated by a functional component, and finally return VNode if (vnode instanceof VNode) { return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext) } else if ( Array .isArray(vnode)) { const vnodes = normalizeChildren(vnode) || [] const res = new Array (vnodes.length) for ( let i = 0 ; i <vnodes.length; i++) { res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext) } return res } } Copy code

installComponentHooks

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

const hooksToMerge = Object .keys(componentVNodeHooks) /** * Set the hook object on the data object of the component, * The hook object adds four attributes, init, prepatch, insert, destroy, * Responsible for the creation, update and destruction of components */ function installComponentHooks ( data: VNodeData ) { const hooks = data.hook || (data.hook = {}) //Traverse the hooksToMerge array, hooksToMerge = ['init','prepatch','insert''destroy'] for ( let i = 0 ; i <hooksToMerge.length; i++) { //such as key = init const key = hooksToMerge[i] //get the key corresponding method from the data.hook object const existing = hooks[key] //The method of the key object in the componentVNodeHooks object const toMerge = componentVNodeHooks[key] //Combining the hook method passed by the user and the hook method that comes with the framework is actually to execute two methods separately if (existing !== toMerge && !(existing && 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 } Copy code

componentVNodeHooks

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

// inline hooks to be invoked on component VNodes during patch const componentVNodeHooks = { //Initialize init(vnode: VNodeWithData, hydrating : boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { //components wrapped by keep-alive //kept-alive components, treat as a patch const mountedNode: any = vnode //work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { //Create component instance, that is, new vnode.componentOptions.Ctor(options) => get Vue component instance const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) //Execute the $mount method of the component to enter the mounting stage. The next step is to get the render function through the compiler, and then follow the path of mounting and patch until the component is rendered to the page child.$mount(hydrating? Vnode.elm: undefined , hydrating) } }, //Update VNode, update various properties on the old VNode with the new VNode configuration prepatch ( oldVnode: MountedComponentVNode, vnode: MountedComponentVNode ) { //Component configuration items of the new VNode const options = vnode.componentOptions //Components of the old VNode Instance const child = vnode.componentInstance = oldVnode.componentInstance //Update various attributes on child with attributes on vnode updateChildComponent( child, options.propsData, //updated props options.listeners, //updated listeners vnode, //new parent vnode options.children //new children ) }, //Execute the mounted life cycle hook of the component insert ( vnode: MountedComponentVNode ) { const {context, componentInstance} = vnode //If the component is not mounted, call the mounted life cycle hook if (!componentInstance._isMounted) { componentInstance._isMounted = true callHook(componentInstance, 'mounted' ) } //Handling abnormalities of keep-alive component if (vnode.data.keepAlive) { if (context._isMounted) { //vue-router#1212 //During updates, a kept-alive component's child components may //change, so directly walking the tree here may call activated hooks //on incorrect children. Instead we push them into a queue which will //be processed after the whole patch process ended. queueActivatedComponent(componentInstance) } else { activateChildComponent(componentInstance, true /* direct */ ) } } }, /** * Destroy components * 1. If the component is wrapped by a keep-alive component, the component is deactivated and the component instance is not destroyed, thereby caching the state of the component * 2. If the component is not wrapped by keep-alive, directly call the instance s $destroy method to destroy the component */ destroy (vnode: MountedComponentVNode) { //Get the component instance from vnode const {componentInstance} = vnode if (!componentInstance._isDestroyed) { //If the component instance is not destroyed if (!vnode.data.keepAlive) { //The component is not wrapped by a keep-alive component , Then directly call the $destroy method to destroy the component componentInstance.$destroy() } else { //Responsible for deactivating the component, not destroying the component instance, so as to cache the state of the component deactivateChildComponent(componentInstance, true /* direct */ ) } } } } Copy code

createComponentInstanceForVnode

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

/** * new vnode.componentOptions.Ctor(options) => get Vue component instance */ export function createComponentInstanceForVnode ( //we know it's MountedComponentVNode but flow doesn't vnode: any, //activeInstance in lifecycle state parent: any ): Component { const options: InternalComponentOptions = { _isComponent : true , _parentVnode : vnode, parent } //Check the inline template rendering function const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } //new VueComponent(options) => Vue instance return new vnode.componentOptions.Ctor(options) } Copy code

summary

The interviewer asked : How does a component become a VNode?

Answer :

  • The component instance is initialized, and finally $mount is executed to enter the mounting phase

  • If it contains only vue.js at runtime, only directly enter the mounting stage, because the component at this time has become a rendering function, and the compilation process is completed by module packager + vue-loader + vue-template-compiler

  • If you do not use precompilation, you must use the full amount of vue.js

  • If it is found that there is no render option on the component configuration item when mounting, it will enter the compilation phase

  • Compile the template string into an AST syntax tree, which is actually an ordinary JS object

  • Then optimize the AST, traverse the AST object, and mark whether each node is static; then further mark the static root node, and the update of these static nodes will be skipped when the component is updated to improve performance

  • Next, a rendering function is generated from the AST. The generated rendering function consists of two parts:

    • Responsible for generating the render function of the dynamic node VNode

    • There is also a staticRenderFns array, each element in it is a function to generate a static node VNode, these functions will be used as a part of the render function, responsible for generating the VNode of the static node

  • Next, put the rendering function on the configuration object of the component and enter the mount phase, that is, execute the mountComponent method

  • The final responsible for rendering and updating components is a method called updateComponent. Before each execution of this method, the vm._render function needs to be executed. This function is responsible for executing the render generated by the compiler to obtain the VNode of the component.

  • The specific work of generating a component into VNode is in the render function

    _c, _o, _l, _m
    After the methods are completed, these methods are mounted on the Vue instance, responsible for generating the component VNode at runtime

Tips : At this point, we must first understand what VNode is. A one-sentence description is - the representation of the JS object of the component template. It is an ordinary JS object, which describes the information of each node in the component in detail.

The following is a little bit too much, in fact, just remember one sentence, set the component configuration information, and then pass

new VNode (component information)
Generate component VNode

  • _c, the VNode responsible for generating components or HTML elements, _c is the most complex and core method of all render helper methods, and the other _xx are its components

    • Receive label, attribute JSON string, child node array, node normalization type as parameters

    • If the label is a platform reserved label or an unknown element, directly

      new VNode (tag information)
      Get VNode

    • If the label is a component, execute the createComponent method to generate VNode

      • Functional component executes its own render function to generate VNode

      • Ordinary components instantiate a VNode, and set 4 methods on the data.hook object, which will be called during the patch phase of the component to enter the sub-component instantiation and mounting phase, and then compile and generate rendering functions until Finish rendering

      • Of course, some configuration processing will be performed before generating VNode, such as:

        • Sub-component options are merged, and global configuration items are merged into component configuration items

        • V-model handling custom components

        • Process the component's props, extract the component's props data, use the property in the component's props configuration as the key and the corresponding data in the parent component as the value to generate a propsData object; when the component is updated, a new VNode is generated, and this step is performed again. This is the principle of props responsiveness

        • Processing other data, such as listeners

        • Install the built-in init, prepatch, insert, and destroy hooks to the data.hooks object, which will be used in the patch phase of the component

  • _l, the help function for rendering the v-for list at runtime, looping through the val values, executing the render method for each item in turn to generate VNodes, and finally returning a VNode array

  • _m, the VNode responsible for generating the static node, that is, the function that executes the specified subscript in the staticRenderFns array

A brief summary of the role of the render helper is to mount some runtime tool methods on the Vue instance. These methods are used in the rendering function generated by the compiler to generate the VNode of the component.

Well, at this point, the beginning of a component from initialization to the final how to become a VNode is finished, the last remaining is the patch phase, the next article will describe how to render the component's VNode onto the page.

Supporting video

Vue source code interpretation (11)-render helper

Seeking attention

Welcome to my attention Nuggets account and the B station , if there is content to help you, we welcome the thumbs up, focus on the collection +

link

Learning Exchange Group

link