What happens when we use createApp to create vue app instances like this?
const { createApp } = Vue createApp({ setup() { return { } } }) .mount('#app')
Let's have a look.
First, enter the createApp function exported by vue, which combines all parameters into args, calls the ensurerender function and createApp on its return data, and then scatters args.
It can be seen here that the ensurerender calls the createRenderer method exported from the runtime core, and the createRenderer receives rendererOptions,
The purpose of doing this is to use renderer to do single instance cache to avoid creating multiple renderers. Second, vue3 considering better support for multi terminal rendering, there is no strong coupling of browser dom operations, but to expose the specific implementation of some operations to developers. The rendererOptions passed in here is the specific implementation of browser dom environment
import { createRenderer } from '@vue/runtime-core' const rendererOptions = extend({ patchProp }, nodeOps) // Some configurations related to rendering, such as the method of updating attributes and the method of operating DOM let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer function ensureRenderer() { return ( renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) ) } export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) // //... })
baseCreateRenderer is called inside the createRenderer method
export function createRenderer< HostNode = RendererNode, HostElement = RendererElement >(options: RendererOptions<HostNode, HostElement>) { return baseCreateRenderer<HostNode, HostElement>(options) }
baseCreateRenderer has a lot of internal content. The options here are the platform related operation details passed in from the outside, rendererOptions, and then take the insert, remove and other methods out of the options. It is invoked in a series of rendering related methods below, because the content is too long, which is omitted here. Further down, at the end of the function, two more important things are exported, the render method and the createApp method. Then, the createApp method is created through the createApp API method, which accepts render and hydrate. This is also an external function. You can call ensurerender() Reason for createApp (... Args)
import { createAppAPI } from './apiCreateApp' function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options const patch: PatchFn = () => {/*.... */ } const processText: ProcessTextOrCommentFn = = () => {/*.... */ } const processCommentNode: ProcessTextOrCommentFn = = () => {/*.... */ } const mountStaticNode = = () => {/*.... */ } const patchStaticNode = () => {/*.... */ } const moveStaticNode = () => {/*.... */ } const removeStaticNode = () => {/*.... */ } const processElement = () => {/*.... */ } const mountElement = () => {/*.... */ } const setScopeId = () => {/*.... */ } const mountChildren: MountChildrenFn = () => {/*.... */ } const patchElement = () => {/*.... */ } // The fast path for blocks. const patchBlockChildren: PatchBlockChildrenFn = () => {/*.... */ } const patchProps = () => {/*.... */ } const processFragment = () => {/*.... */ } const processComponent = () => {/*.... */ } const mountComponent: MountComponentFn = () => {/*.... */ } const updateComponent = () => {/*.... */ } const setupRenderEffect: SetupRenderEffectFn = () => {/*.... */ } const updateComponentPreRender = () => {/*.... */ } const patchUnkeyedChildren = () => {/*.... */ } const patchKeyedChildren = () => {/*.... */ } const move: MoveFn = () => {/*.... */ } const unmount: UnmountFn = () => {/*.... */ } const remove: RemoveFn = () => {/*.... */ } const removeFragment = () => {/*.... */ } const unmountComponent = () => {/*.... */ } const unmountChildren: UnmountChildrenFn = () => {/*.... */ } const getNextHostNode: NextFn = () => {/*.... */ } const render: RootRenderFunction = () => {/*.... */ } const internals: RendererInternals = () => {/*.... */ } return { render, hydrate, createApp: createAppAPI(render, hydrate) } }
The createApp API internally returned the createApp function. This function mainly creates the basic data structure of APP object, which has UUID, use (registered plug-in), component (registered sub component), mount and other properties and methods. It should be noted here that the mount method internally calls the render renderer received by the createApp API. At this time, it exists in the form of closure, and finally returns the app object
export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { const context = createAppContext() const installedPlugins = new Set() let isMounted = false const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, get config() { return context.config }, use(plugin: Plugin, ...options: any[]) { if (installedPlugins.has(plugin)) { __DEV__ && warn(`Plugin has already been applied to target app.`) } else if (plugin && isFunction(plugin.install)) { installedPlugins.add(plugin) plugin.install(app, ...options) } else if (isFunction(plugin)) { installedPlugins.add(plugin) plugin(app, ...options) } return app }, mixin(mixin: ComponentOptions) { if (__FEATURE_OPTIONS_API__) { if (!context.mixins.includes(mixin)) { context.mixins.push(mixin) } } return app }, // Register components component(name: string, component?: Component): any { if (!component) { return context.components[name] } context.components[name] = component return app }, directive(name: string, directive?: Directive) { if (!directive) { return context.directives[name] as any } context.directives[name] = directive return app }, mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): any { if (!isMounted) { const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context render(vnode, rootContainer, isSVG) isMounted = true app._container = rootContainer ;(rootContainer as any).__vue_app__ = app // Hang app to DOM__ vue_ app__ Attribute return vnode.component!.proxy } }, unmount() { if (isMounted) { render(null, app._container) delete app._container.__vue_app__ } }, provide(key, value) { context.provides[key as string] = value return app } }) return app } }
After returning the app, you will return to the createApp function at the beginning, store the returned app instance in the app variable, take out the mount method, and redefine an app Mount method, the original mount method is packaged. The purpose of packaging is the same as that of renderer. The specific implementation of the platform is separated and implemented by the developer,
Here is app The containerOrSelector parameter received by the mount method is the #app selector passed in externally, and then normalize the selector with normalizeContainer. The actual dom is returned. If the actual dom is not found, it will be returned directly. If it is found that the component does not provide template and render, the innerHTML of the dom will be taken as the template, Before the mount method is called to mount, the innerHTML of the actual dom is cleared. After the mount, the attributes such as v-cloak are removed and the app is returned
export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) const { mount } = app // Remove the original mount app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { const container = normalizeContainer(containerOrSelector) if (!container) return const component = app._component // Take out the object data of the component if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML } container.innerHTML = '' const proxy = mount(container, false, container instanceof SVGElement) if (container instanceof Element) { container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') } return proxy } return app }) as CreateAppFunction<Element>
When the mount method is called externally, app Mount is called naturally
createApp().mount('#app')
This brings you to the initial mount defined in createApp. You create vNode internally, call the render method to render, and modify isMounted
mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): any { if (!isMounted) { const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context render(vnode, rootContainer, isSVG) isMounted = true app._container = rootContainer ;(rootContainer as any).__vue_app__ = app return vnode.component!.proxy } }
OK, so the whole process is over
The whole calling process is as follows:
createApp - > ensurerender - > basecreaterenderer - > createappapi (receive render method) - > createApp create and return app object - > return to createApp and wrap app Mount - > external call Mount ('#app') - > trigger app Mount - > mount - > call render method to render