Let’s start with the simplest example
<div id="app">
<h1>{{title}}</h1>
</div>
<script>
const {createApp} = Vue
createApp({
data() {
return {
title: 'coboy'
}
}
}).mount('#app')
</script>
Copy the code
What did VuE3 do?
The runtime – ensureRenderer in the dom
CreateApp comes from runtime-dom/ SRC /index.ts
// actually called by the user
export const createApp = ((. args) = > {
// Get the renderer first
constapp = ensureRenderer().createApp(... args)/ /... omit
return app
}) as CreateAppFunction<Element>
Copy the code
“CreateApp” is one of the ensureRenderer functions that is used to ensure that a renderer is “ensureRenderer” and that the createApp function is included in the result
EnsureRenderer we continue to trace what is going on inside the ensureRenderer function
function ensureRenderer() {
// If the renderer already exists, return it, otherwise create a renderer, then initialization must take the last part of the code
return (
renderer || ((renderer = createRenderer < Node), Element > rendererOptions)
);
}
Copy the code
We can see that createRenderer comes from run-time core/ SRC /renderer.ts
export function createRenderer<
HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer < HostNode, HostElement > options;
}
Copy the code
We trace back to the baseCreateRenderer function, and then we look at the baseCreateRenderer function
The runtime – baseCreateRenderer in the core
BaseCreateRenderer comes from Run-time core/ SRC /renderer.ts
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
// Too much code, nearly 2000 lines, omitted
}
Copy the code
Then we can see the renderer to create the basis of the function is very much of the code, there are nearly 2000 lines of code, is the biggest function of vue3 source code library, it is said that the had some Suggestions especially greatly the function separation, but was especially greatly declined, especially greatly think it is not necessary to excessive design, the function’s duty is to do the renderer, Responsibilities are very clear and do not need to be divided
So from this story, we can also learn that this function does a lot of work for the renderer, but we can ignore the details for now, we just need to focus on the return value of this function
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
// Too much code, nearly 2000 lines, no details
// The patch function is used whenever a VNode is converted to a DOM
const patch: PatchFn = () = > {}
// ...
// key function
const processComponent = () = > {}
// key function
const mountComponent: MountComponentFn = () = > {}
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) = > {}
// key function
const setupRenderEffect: SetupRenderEffectFn = () = > {}
// ...
const render: RootRenderFunction = (vnode, container, isSVG) = > {}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
CreateApp is the result of createAppAPI
return {
render, // Reactdom.render is the same as reactdom.render in react, which is used to render vNodes, convert them to dom, and append them to the host
hydrate, / / for SSR
createApp: createAppAPI(render, hydrate)
}
}
Copy the code
After initializing the renderer, return a renderer and execute createApp
// Get the renderer first
constapp = ensureRenderer().createApp(... args);Copy the code
CreateApp is the result of createAppAPI from the baseCreateRenderer function that creates the renderer
CreateAppAPI in the renderer instance
BaseCreateRenderer comes from Runtime-core/SRC/apicReateapp.ts CreateAppAPI receives a Render method when initialized and returns a createApp method
// This method is the factory method of createApp
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
// Return the App instance
return function createApp(rootComponent, rootProps = null) {
if(rootProps ! =null && !isObject(rootProps)) {
rootProps = null
}
const context = createAppContext()
const installedPlugins = new Set(a)let isMounted = false
// Get the APP instance outside
const app: App = (context.app = {
get config() {
return context.config
},
set config(v) {},// The plugin application method
use(plugin: Plugin, ... options: any[]) {
return app
},
// Compatible with the mixin method of VUe2
mixin(mixin: ComponentOptions) {
return app
},
// Component methodscomponent(name: string, component? : Component): any {return app
},
// Instruction method
directive(name: string, directive? : Directive) {
return app
},
// The most important thing here is the mount methodmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {// When initializing, buy mount, go here
if(! isMounted) {// Build the root component virtual DOM
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
if (isHydrate && hydrate) {
// Server render goes here
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// The spa program goes here
render(vnode, rootContainer, isSVG)
}
isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app
returnvnode.component! .proxy }else if (__DEV__) {
}
},
unmount() {
if (isMounted) {
render(null, app._container)
delete app._container.__vue_app__
} else if (__DEV__) {
}
},
provide(key, value) {
context.provides[key as string] = value
return app
}
})
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
return app
}
}
Copy the code
CreateApp initializes a Render, builds a vNode, passes it to Render and passes in the host element rootComponent received at mount.
Override mount method
We walked once and then we came back
export const createApp = ((. args) = > {
constapp = ensureRenderer().createApp(... args)const {mount} = app
// Override the mount method, which is also called by the outermost user when the mount method is called
app.mount = (containerOrSelector: Element | ShadowRoot | string): any= > {
// Get the template content
const container = normalizeContainer(containerOrSelector)
if(! container)return
const component = app._component
if(! isFunction(component) && ! component.render && ! component.template) {// Assign the template contents to component.template
component.template = container.innerHTML
}
// Clear the template content before mounting
container.innerHTML = ' '
// Mount the same method as createApp in createAppAPI
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>
Copy the code
Mount method in createAppAPI
To take advantage of closures, the final mount method we perform is the one in createAppAPI above
The key code
// The most important thing here is the mount methodmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {// When initializing, buy mount, go here
if(! isMounted) {// Create the root component virtual DOM
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
// Server-side render
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// Initialization goes here
render(vnode, rootContainer, isSVG)
}
isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app
returnvnode.component! .proxy }else if (__DEV__) {
}
},
Copy the code
The final initialization in the mount method is the render function, which is in the 2,000-line renderer
The key code
// Receive vNodes, convert them to DOM, and append them to the host container
const render: RootRenderFunction = (vnode, container, isSVG) = > {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null.null.true); }}else {
// Initialization goes here
// Patch is used whenever a VNode is converted to a DOM
// When the container._vnode is null during initialization, the initialization logic is used and diff is not performed
patch(container._vnode || null, vnode, container, null.null.null, isSVG);
}
flushPostFlushCbs();
container._vnode = vnode;
};
Copy the code
This patch is also in the 2000 line renderer
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = false
) = > {
if(n1 && ! isSameVNodeType(n1, n2)) { }if (n2.patchFlag === PatchFlags.BAIL) {
}
const {type, ref, shapeFlag} = n2
switch (type) {
case Text:
break
case Comment:
break
case Static:
break
case Fragment:
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// Initialization goes here
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
} else if (__DEV__) {
}
}
Copy the code
Why initialize the else if branch (ShapeFlags.COMPONENT)
Since the mount method in createAppAPI creates the virtual DOM of the root component, ShapeFlags.COMPONENT is true
// When initialized, not yet mounted, go here
if(! isMounted) {// Create the root component virtual DOM
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
}
Copy the code
The processComponent function is also in the 2000 line renderer function
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) = > {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if(n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctxas KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
// Initialization goes here
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
Copy the code
MountComponent: mounts component functions
We finally see the familiar function mountComponent, which mounts component functions
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
// 1. Create a component instance
const instance: ComponentInternalInstance = (initialVNode.component =
createComponentInstance(initialVNode, parentComponent, parentSuspense));
if (isKeepAlive(initialVNode)) {
}
// 2. Component instance setup, equivalent to component initialization, equivalent to vue2's this._init instance property, method initialization, data responsiveness, two lifecycle hooks
// Attribute declaration, responsivity, etc
setupComponent(instance);
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
}
// Update mechanism. Install render function side effect, equivalent to updateComponent+ Watcher
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
);
};
Copy the code
The setupComponent function
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR;
const { props, children, shapeFlag } = instance.vnode;
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT;
// Initialize Props
initProps(instance, props, isStateful, isSSR);
// Initialize slots
initSlots(instance, children);
// setup data state processing
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined;
isInSSRComponentSetup = false;
return setupResult;
}
Copy the code
The setupStatefulComponent function handles the setup data state
function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
const Component = instance.type as ComponentOptions
// CTX is the familiar this component instance from VUe2
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 2. call setup()
const { setup } = Component
// If the setup function is set
if (setup) {
// setupContext context
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
pauseTracking() // Tree shake optimization
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking() // Reset tree shake optimization
currentInstance = null
// Setup may return a Promise
if (isPromise(setupResult)) {
if (isSSR) {
} else if (__FEATURE_SUSPENSE__) {
instance.asyncDep = setupResult
} else if (__DEV__) {
}
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
Copy the code
FinishComponentSetup function
Note that the finishComponentSetup function is eventually executed whether or not the setup function is set
function finishComponentSetup(instance: ComponentInternalInstance, isSSR: boolean) {
const Component = instance.type as ComponentOptions
if (__NODE_JS__ && isSSR) {
} else if(! instance.render) {// If there is no render function, compile it
if(compile && Component.template && ! Component.render) { Component.render = compile(Component.template, {isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
}
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (instance.render._rc) {
instance.withProxy = new Proxy(
instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
}
/ / compatible vue2.0
if (__FEATURE_OPTIONS_API__) {
currentInstance = instance
applyOptions(instance, Component)
currentInstance = null}}Copy the code
The applyOptions function handles the VUe2 API
export function applyOptions(
instance: ComponentInternalInstance,
options: ComponentOptions,
deferredData: DataFn[] = [],
deferredWatch: ComponentWatchOptions[] = [],
asMixin: boolean = false
) {
const {
// composition
mixins,
extends: extendsOptions,
// state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
// assets
components,
directives,
// lifecycle can see that vue3 has no beforeCreate, created function and setup instead
beforeMount,
mounted,
beforeUpdate,
updated,
activated,
deactivated,
beforeDestroy,
beforeUnmount,
destroyed,
unmounted,
render,
renderTracked,
renderTriggered,
errorCaptured
} = options
const publicThis = instance.proxy!
if (beforeMount) {
// We removed a lot of other code logic, looking at this function alone, it is obvious that we can bind instance.proxy with beforeMount context
onBeforeMount(beforeMount.bind(publicThis))
}
}
Copy the code
SetupRenderEffect function
This method wraps the render function as a side effect function and executes it immediately, after which the interface renders, and when the dependent reactive data changes, it reexecutes
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
UseEffect (fn,[])
instance.update = effect(function componentEffect() {
// If isMounted is false for the first time, the initialization process is performed
if(! instance.isMounted) {// First get the component vNode, which calls the component render
// This call triggers dependency collection
// subTree can be understood as the subTree of the current component
const subTree = (instance.subTree = renderComponentRoot(instance))
if (el && hydrateNode) {
} else {
// Update recursively downward
// The first time is a full recursive creation
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
instance.isMounted = true
} else {// Update here}}}Copy the code
Conclusion:
Without Watcher in VUe3,Dep becomes very pure, the relationship between responsive data and its associated side effect function
Draw a flow chart to illustrate the initialization of VUe3
We export a function called createApp from a global variable Vue, and then create an instance of createApp. CreateApp basically creates a renderer first. One instance of a renderer that is “ensureRenderer” or a custom Web platform renderer called baseCreateRenderer in runtime-core is returned. The createApp method in the renderer instance creates an app instance with a mount method that parses the transformation template content and mounts it to the host node