The biggest upgrade to Vue 2.0 over Vue 1.0 is the use of the virtual DOM. Updates to views in Vue 1.0 are purely reactive. During reactive initialization, a deP is created for a reactive data key, and several Watchers are created for each key referenced in the template. That is, a key corresponds to a DEP, which manages one or more watcher. Due to the one-to-one relationship between Watcher and DOM, a DOM can be explicitly updated during update, and the update efficiency is very high.
As applications get larger and more components, a single component can have a lot of Watcher, and performance becomes an issue. With the addition of the virtual DOM and diff in Vue 2.0, a component only needs to create a Watcher, using reactive + DIff updates, reactive between components, and diff within components. When the data changes, watcher is notified of the update, that is, the entire component is notified of the update. The specific element and location of the update can be obtained through the diff algorithm comparing the old and new virtual DOM, and the difference is actually updated to the view.
Virtual DOM is used in React and Vue. On the one hand, it can improve performance, and on the other hand, it can be better cross-platform, such as server rendering.
The virtual DOM, also known as a VNode, essentially uses JS objects to simulate nodes that exist in the real DOM. Here’s an example:
<div id="app">
<h1>Virtual DOM<h1>
</div>
Copy the code
{
tag: "div".attrs: {
id: "app"
},
children: [{tag: "h1".text: "Virtual DOM"}}]Copy the code
VNode
The virtual DOM in Vue is described by a Class. Let’s take a look first:
// src/core/vdom/vnode.js
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodesfnOptions: ? ComponentOptions;// for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (tag? :string, data? : VNodeData, children? :?Array<VNode>, text? :string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? :Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
Copy the code
_render
As mentioned earlier, when reactive data changes, triggering an update simply executes the following code:
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
Copy the code
Execute vm._render() to get the VNode and pass it as a parameter to vm._update to update the view. Let’s look at the definition of vm._render() :
// src/core/instance/render.js
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
// Mount some run-time utility methods on the component instance
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)}/** * Generate VNode by executing render function * but add a lot of exception handling code */
Vue.prototype._render = function () :VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
// Set the parent vnode. This allows the rendering function to access the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
// Execute render function to generate vNode
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// Error handling, the development environment renders the error message, production environment returns the previous VNode, to prevent rendering errors resulting in blank components
if(process.env.NODE_ENV ! = ='production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// If a vNode is an array and has only one entry, place that entry directly back
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]}// The render function returns an empty vnode when it fails
if(! (vnodeinstanceof VNode)) {
if(process.env.NODE_ENV ! = ='production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
Copy the code
The render method is called in _render, in two cases:
- Render compiled from the template
with(this){return _c(tag, data, children, normalizationType)}
Copy the code
- User defined render
render: function (createElement) {
return createElement('div', {
attrs: {
id: 'app'}},this.msg)
}
Copy the code
$createElement = vm.$createElement = vm.$createElement = vm.
// src/core/instance/render.js
export function initRender (vm: Component) {
/ * * *@param {*} A Label name *@param {*} B JSON string * for the property@param {*} C Array of child nodes *@param {*} The normalized type of the d node *@returnsVNode or Array<VNode>
*/
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)}Copy the code
These two methods support the same parameters and actually call the createElement method.
createElement
// src/core/vdom/create-element.js
// A vNode that generates components or common labels, a wrapper function that provides a more flexible interface.
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 a VNode for the component
return _createElement(context, tag, data, children, normalizationType)
}
Copy the code
_createElement
// 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> {
if (isDef(data) && isDef((data: any).__ob__)) {
// If data is a responsive object, return the Vnode of the empty nodeprocess.env.NODE_ENV ! = ='production' && warn(
`Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render! ',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if(! tag) {// Dynamic component: when the IS property is set to false
// in case of component :is set to falsy value
return createEmptyVNode()
}
// 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
)
}
}
// support single function children as default scoped slot
// The child node array has only one function, slot it by default, and then empty its own child node array
if (Array.isArray(children) &&
typeof children[0= = ='function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// The child node is normalized
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// Platform built-in nodes
// platform built-in elements
if(process.env.NODE_ENV ! = ='production'&& isDef(data) && isDef(data.nativeOn) && data.tag ! = ='component') {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>. `,
context
)
}
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
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
// Unknown tag tag, checked at run time because it may be assigned a namespace when its parent normalizes its children
vnode = new VNode(
tag, data, children,
undefined.undefined, context
)
}
} else {
// Tag is not a string
// direct component options / constructor
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()
}
}
Copy the code
Createelement (VNode, VNode, VNode); createElement (VNode, VNode, VNode, VNode, VNode, VNode, VNode);
- If it’s a platform built in node, create a normal VNode.
- If it is a registered component name, pass
createComponent
Create a VNode of component type. - Otherwise, create a VNode with an unknown label name.
Another point worth noting here is the normalization of the child nodes with the normalizeChildren and simpleNormalizeChildren methods so that each node is of type VNode.
The simpleNormalizeChildren method call scenario is that the Render function is compiled. The functional Component returns an array instead of a root node. So the array.prototype. concat method is used to flatten the entire children Array so that it is only one layer deep.
The normalizeChildren method can be called in two scenarios. One scenario is that the render function is written by the user, and when children have only one node, Vue.js allows the user to write children as a basic type to create a single simple text node at the interface level. In this case, createTextVNode is called to create a VNode of the text node. Another scenario is when a nested array is generated when slot, V-for is compiled, and the normalizeArrayChildren method is called
Component type
createComponent
// 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 {
// The component's constructor does not exist
if (isUndef(Ctor)) {
return
}
// context.$options._base = Vue.options._base = Vue
const baseCtor = context.$options._base
// vue. extend is defined in SRC /core/global-api/extend.js
// When Ctor is a configuration object, construct a subclass of Vue by vue. extend
Vue. Extend is not executed when Ctor is a functional, indicating an asynchronous component
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeofCtor ! = ='function') {
if(process.env.NODE_ENV ! = ='production') {
warn(`Invalid Component definition: The ${String(Ctor)}`, context)
}
return
}
// Async component
let asyncFactory
if (isUndef(Ctor.cid)) {
// If Ctor. Cid is null, then Ctor is a function indicating that this is an asynchronous component
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
// Returns a placeholder node for the asynchronous component, which is rendered as an annotation node but retains all the original information of the node, which will be used for asynchronous server rendering and hydration
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
// Node properties JSON String
data = data || {}
// Merge option that resolves constructor options in case global blending is applied after component constructor creation
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// Convert the component's V-model to attributes and values of the data.attrs object and events and callbacks on the data.on object
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
PropsData [key] = val
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional Component (functional Component)
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
Extract event listeners, because these listeners need to be treated 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
// Replace it with a listener with a. Native modifier so that it can be processed during the parent component patch.
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// Abstract components retain only props, Listeners, and slots
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// Install component hook functions init, prepatch, insert, destroy
// install component management hooks onto the placeholder node
installComponentHooks(data)
// Instantiate a VNode with new VNode and return
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
)
// 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 the code
As you can see from the code above, asynchronous components, functional components, and plain components are treated separately. In terms of common components, the createComponent method does a few things:
- through
Vue.extend
Constructor to build a subclass. - Install component hook functions
init
,prepatch
,insert
,destroy
. - Instantiate VNode and return VNode.
The logic inside createComponent is a bit complicated, so let’s look at the generic components first, in several component categories:
resolveConstructorOptions
// src/core/instance/init.js
/ * * *@description: Parses configuration object options from the component constructor and merges base class options *@param {*} Ctor
* @return {Object} options* /
export function resolveConstructorOptions (Ctor: Class<Component>) {
// Get options from the instance constructor
let options = Ctor.options
if (Ctor.super) {
// Base class exists, recursively resolving base class constructor options
const superOptions = resolveConstructorOptions(Ctor.super)
/ / cache
const cachedSuperOptions = Ctor.superOptions
if(superOptions ! == cachedSuperOptions) {// Indicates that the base class configuration item has changed
Ctor.superOptions = superOptions
// Find the option to change
const modifiedOptions = resolveModifiedOptions(Ctor)
// If there are options that have been modified or added, merge the two options
if (modifiedOptions) {
// Merge the changed options with the extend options
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 the code
resolveModifiedOptions
// src/core/instance/init.js
/ * * *@descriptionResolves subsequent changes or additions to the constructor option *@param {*} Ctor
* @return {*} Modified Inconsistent option */
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
// Constructor options
const latest = Ctor.options
// Sealed constructor option, backup
const sealed = Ctor.sealedOptions
// Compare the two options and record the inconsistent options
for (const key in latest) {
if(latest[key] ! == sealed[key]) {if(! modified) modified = {} modified[key] = latest[key] } }return modified
}
Copy the code
transformModel
// src/core/vdom/create-component.js
/** * Transform component V-model info (value and callback) /** * Transform component V-model info (value and callback) into * prop and event handler respectively. */
function transformModel (options, data: any) {
// Attributes and events for model, default to value and input
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
// Store v-model values in 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 = {})
// An existing event callback function
const existing = on[event]
// The event callback function in the V-model
const callback = data.model.callback
// merge callback functions
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1: existing ! == callback ) { on[event] = [callback].concat(existing) } }else {
on[event] = callback
}
}
Copy the code
extractPropsFromVNodeData
// src/core/vdom/helpers/extract-props.js
export function extractPropsFromVNodeData (data: VNodeData, Ctor: Class<Component>, tag? :string
): ?Object {
// Only raw values are extracted, validation and default values are handled in child components
// we are only extracting raw values here.
// validation and default values are handled in the child
// component itself.
const propOptions = Ctor.options.props
// Undefined props
if (isUndef(propOptions)) {
return
}
const res = {}
const { attrs, props } = data
if (isDef(attrs) || isDef(props)) {
/ / traverse propsOptions
for (const key in propOptions) {
// Convert the small hump key to xxx-xxx-xxx
const altKey = hyphenate(key)
if(process.env.NODE_ENV ! = ='production') {
// Error message
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 the code
checkProp
// src/core/vdom/helpers/extract-props.js
function checkProp (
res: Object,
hash: ?Object,
key: string,
altKey: string,
preserve: boolean
) :boolean {
if (isDef(hash)) {
// Determine if there is a key or altKey in the hash (props/attrs) object
// If yes, 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 the code
installComponentHooks
InstallComponentHooks merges componentVNodeHooks into data.hook. If a hook exists, execute mergeHook. The VNode executes related hook functions during patch execution.
// src/core/vdom/create-component.js
Init, prepatch, insert, destroy; / / Create, update, destroy; / / Create, update, destroy
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
// Walk through the hooksToMerge array, hooksToMerge = ['init', 'prepatch', 'insert' 'destroy']
for (let i = 0; i < hooksToMerge.length; i++) {
// key = init
const key = hooksToMerge[i]
// Get the method corresponding to the key from the data.hook object
const existing = hooks[key]
// Method for the key object in the componentVNodeHooks object
const toMerge = componentVNodeHooks[key]
// Merge the user-passed hook method with the framework's own hook method
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 the code
componentVNodeHooks
// src/core/vdom/create-component.js
// Inline hook called on component VNode during patch
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
/ / initialization
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) {// The component wrapped by keep-alive
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
/ / create the component instances, namely the new vnode.com ponentOptions. Ctor (options) = > get Vue component instance
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// Call the $mount method to enter the mount phase.
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
/ / update the VNode
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// New VNode configuration item
const options = vnode.componentOptions
// Old VNode component instance
const child = vnode.componentInstance = oldVnode.componentInstance
Update the old with the new
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children)},// Execute the component's mounted hook function
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if(! componentInstance._isMounted) { componentInstance._isMounted =true
callHook(componentInstance, 'mounted')}// Handle the keep-alive exception
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
destroy (vnode: MountedComponentVNode) {
// Get the component instance
const { componentInstance } = vnode
// Destroyed components skipped
if(! componentInstance._isDestroyed) {if(! vnode.data.keepAlive) {// Call $destroy directly to destroy the component
componentInstance.$destroy()
} else {
// The component wrapped by keep-alive
// Cache the state of the component by deactivating the component without destroying the component instance
deactivateChildComponent(componentInstance, true /* direct */)}}}}Copy the code
createComponentInstanceForVnode
// src/core/vdom/create-component.js
. / / the new vnode.com ponentOptions 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 the code
Finally, instantiate the VNode and return the VNode. In addition to normal components, there are branches: asynchronous components and functional components. Let’s look at the flow of asynchronous and functional components:
Asynchronous components
SRC /core/vdom/create-component.js -- in the createComponent method
// Async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
// Returns a placeholder node for the asynchronous component, which is rendered as an annotation node but retains all the original information of the node, which will be used for asynchronous server rendering and hydration
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
Copy the code
resolveAsyncComponent
// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
) :Class<Component> | void {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved
}
// Owner instance collection container, the reference to the same asynchronous component does not need to be resolved many times, but the instance that currently uses the asynchronous component is collected, after the asynchronous component is resolved, notify rendering update one by one
const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
/ /...
// This bit of code is a bit long, so we'll break it down into regular asynchronous components, Promise asynchronous components, and advanced asynchronous components
}
Copy the code
In the case of ordinary functions, the previous if judgments can be ignored because they are used for advanced components. In the case of factory.xxxxx, it is considered that an asynchronous component should be initialized in multiple places at the same time, so it should actually be loaded only once.
createAsyncPlaceholder
// src/core/vdom/helpers/resolve-async-component.js
// Returns a placeholder node for the asynchronous component, which is rendered as a comment node but retains all of the node's original information
export function createAsyncPlaceholder (
factory: Function, data: ? VNodeData, context: Component, children: ?Array<VNode>,
tag: ?string
) :VNode {
const node = createEmptyVNode()
node.asyncFactory = factory
node.asyncMeta = { data, context, children, tag }
return node
}
Copy the code
Common asynchronous component
Vue.component('async-example'.function (resolve, reject) {
// This particular require syntax tells Webpack
// Automatically split compiled code into different blocks,
// These blocks will be downloaded automatically through Ajax requests.
require(['./my-async-component'], resolve)
}
)
Copy the code
Vue allows components to be defined as factory functions that parse components on the fly. Vue triggers factory functions only when components need to be rendered, and caches the results for later re-rendering.
// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
) :Class<Component> | void {
// The second time a normal asynchronous component executes this will return factory.resolved
if (isDef(factory.resolved)) {
return factory.resolved
}
// Owner instance collection container, the reference to the same asynchronous component does not need to be resolved many times, but the instance that currently uses the asynchronous component is collected, after the asynchronous component is resolved, notify rendering update one by one
const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
if(owner && ! isDef(factory.owners)) {// owner instance collection container
const owners = factory.owners = [owner]
// sync Indicates the synchronization identifier, which identifies whether it is synchronous or asynchronous
let sync = true
let timerLoading = null
let timerTimeout = null
; (owner: any).$on('hook:destroyed'.() = > remove(owners, owner))
const forceRender = (renderCompleted: boolean) = > {
// Call the element's $forceUpdate() method in turn, which forces rendering once
for (let i = 0, l = owners.length; i < l; i++) {
(owners[i]: any).$forceUpdate()
}
// Remove timer after component update to clear dependencies
if (renderCompleted) {
owners.length = 0
if(timerLoading ! = =null) {
clearTimeout(timerLoading)
timerLoading = null
}
if(timerTimeout ! = =null) {
clearTimeout(timerTimeout)
timerTimeout = null}}}// Define a resolve function. Once is a one-time wrapper that ensures that the function passed in is executed only once (to avoid multiple notification updates)
const resolve = once((res: Object | Class<Component>) = > {
/ / the cache resolved
factory.resolved = ensureCtor(res, baseCtor)
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if(! sync) { forceRender(true)}else {
owners.length = 0}})// Define a reject function
const reject = once(reason= >{ process.env.NODE_ENV ! = ='production' && warn(
`Failed to resolve async component: The ${String(factory)}` +
(reason ? `\nReason: ${reason}` : ' '))if (isDef(factory.errorComp)) {
factory.error = true
forceRender(true)}})// Execute the factory() function
const res = factory(resolve, reject)
sync = false
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
Copy the code
ResolveAsyncComponent defines a resolve and reject function inside the resolveAsyncComponent, and then executes the factory() function. Factory () is the function we define in the component. Since require() is an asynchronous operation, resolveAsyncComponent returns undefined.
Back in the createComponent function, since undefined is returned, createAsyncPlaceholder is executed to create a comment node placeholder.
Resolve is the resolve function defined in resolveAsyncComponent. Resolve will save the resolved property in the factory function.
When resolveAsyncComponent does factory. Resolved, return.
Promise asynchronous components
Vue.component(
'async-webpack-example'.// The 'import' function returns a 'Promise' object.
() = > import('./my-async-component'))Copy the code
Webpack 2+ supports syntactic sugar for asynchronous loading: () => import(‘./my-async-component’); res = factory(resolve, reject); import(‘./my-async-component’); It’s a Promise object.
// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
) :Class<Component> | void {
// The second time the component executes here it will return factory.resolved
if (isDef(factory.resolved)) {
return factory.resolved
}
if(owner && ! isDef(factory.owners)) {// ...
// Execute the factory() function,
// Return an object containing THEN
const res = factory(resolve, reject)
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
// If factory.resolved does not exist
Use the then method to specify the resolve and reject callbacks
res.then(resolve, reject)
}
} else if (isPromise(res.component)) {
// ...
}
}
sync = false
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
Copy the code
Resolve is executed when the component is asynchronously loaded, reject is executed when the component is asynchronously loaded. This is a good match for webPack 2+ ‘s asynchronously loaded component (Promise).
Advanced asynchronous component
Advanced asynchronous components can define more states, such as the timeout for loading the component, components that are explicit during loading, components that are explicit when an error occurs, latency, and so on.
const AsyncComp = () = > ({
component: import('./MyComp.vue'),
loading: LoadingComp,
error: ErrorComp,
delay: 200.timeout: 3000
})
Vue.component('async-example', AsyncComp)
Copy the code
For advanced asynchronous components, it loads the same logic as the Promise () method, except for a few more properties:
// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
) :Class<Component> | void {
/ /...
if(owner && ! isDef(factory.owners)) {// ...
// Execute the factory() function
const res = factory(resolve, reject)
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isPromise(res.component)) {
// A branch of the advanced asynchronous component
res.component.then(resolve, reject)
if (isDef(res.error)) {
// Failed module
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
// Set the loading module if there is one
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
// If the wait time is 0
factory.loading = true
} else {
timerLoading = setTimeout(() = > {
timerLoading = null
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender(false)
}
}, res.delay || 200)}}if (isDef(res.timeout)) {
// The timeout period
timerTimeout = setTimeout(() = > {
timerTimeout = null
if(isUndef(factory.resolved)) { reject( process.env.NODE_ENV ! = ='production'
? `timeout (${res.timeout}ms)`
: null
)
}
}, res.timeout)
}
}
}
sync = false
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
Copy the code
Functional component
SRC /core/vdom/create-component.js -- in the createComponent method
// functional Component (functional Component)
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
Copy the code
createFunctionalComponent
// src/core/vdom/create-functional-component.js
export function createFunctionalComponent (
Ctor: Class<Component>,
propsData: ?Object,
data: VNodeData,
contextVm: Component,
children: ?Array<VNode>
) :VNode | Array<VNode> | void {
/ / configuration items
const options = Ctor.options
// props
const props = {}
// Props of the component itself
const propOptions = options.props
// Sets the props object for the functional component
if (isDef(propOptions)) {
// If the functional component itself provides the props option, set the props. Key value to the corresponding key value passed from the component
for (const key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || emptyObject)
}
} else {
// If the functional component does not provide props, the attribute on the component is automatically resolved to props
if (isDef(data.attrs)) mergeProps(props, data.attrs)
if (isDef(data.props)) mergeProps(props, data.props)
}
// Instantiate the rendering context of a functional component
const renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
)
// Call render function to generate vNode and pass _c and render context to render function
const vnode = options.render.call(null, renderContext._c, renderContext)
// Add some tags to the generated VNode object to indicate that the VNode was generated by a functional component, and return the 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 the code
CreateFunctionalComponent in the process of creating functional components, mainly to do so a few things:
- Set the component’s
props
object - Instantiate the component to get the rendering context.
- call
render
Method to generate a VNode. - Returns the marked VNode.
After all, use createElement to createElement vnodes, createComponent to createComponent vnodes, and each VNode has children. Children contains children. A VNode Tree is formed. The _render function is executed to get a VNode tree.
A link to the
Vue (V2.6.14) source code detoxification (pre) : handwritten a simple version of Vue
Vue (V2.6.14) source code detoxification (a) : preparation
Vue (V2.6.14) source code detoxification (two) : initialization and mount
Vue (V2.6.14) source code detoxification (three) : response type principle
Vue (V2.6.14) source code detoxification (four) : update strategy
Vue (v2.6.14) source code detoxification (five) : render and VNode
Vue (v2.6.14) source code: Update and patch (to be continued)
Vue (v2.6.14) source code detoxification (seven) : template compilation (to be continued)
If you think it’s ok, give it a thumbs up!! You can also visit my personal blog at www.mingme.net/