createApp
Source location: github.com/vuejs/vue-n…
Are source code, more dry… V 3.0.4
Code implementation:
/** * createApp function */
export const createApp = ((. args) = > {
constapp = ensureRenderer().createApp(... args)// The development environment verifies that the component's name is not the same as the built-in label
if (__DEV__) {
injectNativeTagCheck(app)
}
const { mount } = app
/** * overrides the mount function *@param containerOrSelector
*/
app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
// Container is a real DOM element
const container = normalizeContainer(containerOrSelector)
if(! container)return
const component = app._component // Options for the component
// The template of the default component is the contents of the mount element
if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// Empty the contents of the container
container.innerHTML = ' '
const proxy = mount(container)
if (container instanceof Element) {
// Remove the V-cloak instruction on the element
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app'.' ')}return proxy
}
return app
}) as CreateAppFunction<Element>
Copy the code
Call ensureRenderer (.) createApp (… Args) method to obtain the instance of app; Then we rewrite the app’s mount method. In the new mount method, we do the first thing on the container (if the CSS selector is passed, we get the DOM element from document.querySelector). Make container a real DOM element.
When the component is not a function and the render function and tempalte parameters are not set, the innerHTML in the default Container is the template for the component.
Call the mount method returned in app to complete the DOM mount
ensureRenderer
/** * Lazy create renderer */
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code
The renderer object is lazily created, depending on the platform on which the object is created. In the WEB platform, rendererOptions is an API for DOM manipulation
createRenderer
export function createRenderer<
HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
Copy the code
This method directly returns the baseCreateRenderer method, which has several overloaded methods.
baseCreateRenderer
Source location: github.com/vuejs/vue-n…
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
// The WEB platform gets the method to manipulate the DOM
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
const patch = () = >{... }const processText = () = >{... }const processCommentNode = () = >{... }const mountStaticNode = () = >{... }const patchStaticNode = () = >{... }const moveStaticNode = () = >{... }const removeStaticNode = () = >{... }const processElement = () = >{... }const mountElement = () = >{... }const setScopeId = () = >{... }const mountChildren = () = >{... }const patchElement = () = >{... }const patchBlockChildren = () = >{... }const patchProps = () = >{... }const processFragment = () = >{... }const processComponent = () = >{... }const mountComponent = () = >{... }const updateComponent = () = >{... }const setupRenderEffect = () = >{... }const updateComponentPreRender = () = >{... }const patchChildren = () = >{... }const patchUnkeyedChildren = () = >{... }const patchKeyedChildren = () = >{... }const move = () = >{... }const unmount = () = >{... }const remove = () = >{... }const removeFragment = () = >{... }const unmountComponent = () = >{... }const unmountChildren = () = >{... }const getNextHostNode = () = >{... }const render = () = >{... }return {
render,
hydrate,
/ / createApp entrance
createApp: createAppAPI(render, hydrate)
}
}
Copy the code
EnsureRenderer ().createApp(… The args method gets an instance of app, which is createApp in the object returned by baseCreateRenderer, a function generated by createAppAPI
createAppAPI
Source location: github.com/vuejs/vue-n…
/** * returns app instance *@param render
* @param hydrate
*/
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
/** * receives two arguments * rootComponent rootComponent * rootProps passed to the rootComponent props */
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext() // Return an object
// The installed plug-in
const installedPlugins = new Set(a)// Whether to mount
let isMounted = false
const app = (context.app = {
_uid: uid++, / / the only id
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null._context: context,
version, / / the vue version
get config() { // config is a read-only object. Setting config will raise a warning in the development environment
return context.config
},
use(){... },mixin(){... },component(){... },directive(){... },mount(){... },unmount(){... },provide() {...}
})
return app
}
}
Copy the code
In createApp of the Runtime-dom, override the mount method to call app.mount.
mount
/** * Components mount *@param rootContainer
* @param isHydrate
*/mount(rootContainer: HostElement, isHydrate? :boolean) :any {
if(! isMounted) {// Create a vnode
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// The vnode of the node mounts the context
vnode.appContext = context
// Ignore execution on other platforms, and warnings from development environments
// ...
// Execute the render function
render(vnode, rootContainer)
isMounted = true
app._container = rootContainer
/ / return???
returnvnode.component! .proxy } }Copy the code
In the mount method, the createVNode method is executed to create a component’s VNode, followed by the Render method.
createVNode
Example Create a Vnode
export const createVNode = (__DEV__
? createVNodeWithArgsTransform
: _createVNode) as typeof _createVNode
Copy the code
Regardless of your development environment, look directly at the _createVNode method
_createVNode
/** * How to create a vNode *@param type
* @param props
* @param children
* @param patchFlag
* @param dynamicProps
* @param isBlockNode
*/
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
) :VNode {
if (isVNode(type)) {
// If type is already a vnode, return the clone vnode
const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
return cloned
}
// Handle the class component. The class component has been removed from Vue3
// ...
// class & style normalization.
// Handle props class as a string, style as an object
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)? ShapeFlags.ELEMENT : __FEATURE_SUSPENSE__ && isSuspense(type)? ShapeFlags.SUSPENSE : isTeleport(type)? ShapeFlags.TELEPORT : isObject(type)? ShapeFlags.STATEFUL_COMPONENT : isFunction(type)? ShapeFlags.FUNCTIONAL_COMPONENT :0
// A new vNode
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.SKIP]: true.type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null.component: null.suspense: null.ssContent: null.ssFallback: null.dirs: null.transition: null.el: null.anchor: null.target: null.targetAnchor: null.staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null.appContext: null
}
// validate key The validate key is not a NaN
// ...
// Process the child nodes
normalizeChildren(vnode, children)
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
}
if (
shouldTrack > 0! isBlockNode && currentBlock && (patchFlag >0|| shapeFlag & ShapeFlags.COMPONENT) && patchFlag ! == PatchFlags.HYDRATE_EVENTS ) { currentBlock.push(vnode) }return vnode
}
Copy the code
This method returns a new vNode, even if the argument passed is already a vnode, clone a new vnode and return.
Then look at render, the other method called in mount
render
The render method called in mount is passed in when the createAppAPI method is called. This is the Render method defined in the baseCreateRenderer method
const render: RootRenderFunction = (vnode, container) = > {
// vnode === null Uninstalls components
if (vnode == null) {
if (container._vnode) {
// Uninstalling components needs to be performed
unmount(container._vnode, null.null.true)}}else {
// Patch updates and/or creates components
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
// Save the vNode of the component
container._vnode = vnode
}
Copy the code
Render method through the patch method, vNode into a real DOM, and mount on the page.
patch
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) = > {
// Delete the old node if the new node type is different
if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
n1 = null
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
// ...
break
case Comment:
// ...
break
case Static:
// ...
break
case Fragment:
// ...
break
default:
// Handle other types of nodes
// ...
if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent( / / processing component
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
// Handle the bound ref
if(ref ! =null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2)
}
}
Copy the code
During patch, different methods are called to process the node depending on the type of the vNode. Here we will focus on processComponent, which performs setup and dependency collection.
processComponent
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) = > {
if (n1 == null) {
// ...
/ / mount component
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// Update the component
updateComponent(n1, n2, optimized)
}
}
Copy the code
You can see that the mountComponent method is used to create the component and the updateComponent method is used to update the component. See mountComponent first and then updateComponent.
mountComponent
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
// createComponentInstance handles instance. CTX inconsistencies are handled in this method
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// ...
// The setup method is called, and the value returned by setup is stored in instance.setupState
setupComponent(instance)
// ...
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
The setupRenderEffect function is executed after the promise state resolve
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// ...
return
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
Copy the code
This method creates an instance by createComponentInstance
This instance is obtained by getCurrentInstance. The CTX property of instance is two different things in dev and PROD, and should not be used in production
The setupRenderEffect function finally executes the setupRenderEffect method, in which the dependencies used in vNode are collected
createComponentInstance
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
// ...
const instance: ComponentInternalInstance = {
// ...
}
// Special processing of CTX by the development environment
// This CTX cannot be used in project development, production environment does not support it
if (__DEV__) {
instance.ctx = createRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
}
Copy the code
setupRenderEffect
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
instance.update = effect(function componentEffect() {
// Create a new component
if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
// For child nodes, the component's render function is executed
// The render function collects the value of ref/reactive as the function to be triggered by the dependency change
const subTree = (instance.subTree = renderComponentRoot(instance))
patch( // The patch execution completes the DOM mount
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
instance.isMounted = true
initialVNode = container = anchor = null as any
} else {
// Update the component
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleporthostParentNode(prevTree.el!) ! .// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
next.el = nextTree.el
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
Copy the code
The setupRenderEffect function calls the effect function. Only functions executed in effect can do dependency collection. Create child nodes of the component using renderComponentRoot, which executes the render method of the component. The effect function is collected as a dependency on changes in the Render method for getting values of type Reactive and for ref/computed. Value.
When effect is executed, the second argument prodEffectOptions is passed. In this argument, a scheduler method is called after the dependency update. This scheduler determines when the DOM update will be performed. Instead of changing the DOM every time a dependency changes.
This method also executes the beforeMount hooks function, then executes a patch method after renderComponentRoot, in which the component is created into the DOM. And handles the ref bound in the component template. Through the setRef method.
Mounted hooks are then executed.
Component rendering is finished!!