In my previous article “Debug, Learn to Read Open-source Project Code efficiently”, I used the Vue3 source code debugging configuration as an example to introduce the basic configuration of debugging and highlight its importance in the process of looking at source code. This article uses debugging techniques to get a quick overview of the Vue3 execution process without having to go into detail about the full code implementation.
The simplest code
As we all know, calling vue.createApp method creates a Vue3 application, this article starts from this method, step-by-step analysis of the method in the source code implementation, so that you can understand the overall Vue3 implementation process.
First of all, in the source directory to create packages/vue/examples/hello. HTML file:
<script src=".. /dist/vue.global.js"></script>
<div id="demo">{{text}}</div>
<script>
debugger
Vue.createApp({
data: () = > ({
text: 'hello world'
})
}).mount('#demo')
</script>
Copy the code
The code’s function is simple: Print the Hello World string in the page. Here the debugger code is inserted before executing vue.createApp, causing execution to pause at the debugger.
Debugging start
For a more detailed understanding of debugging principles and procedures, please visit the author’s previous article: “Debug, you learn to read open-source project code efficiently”.
{
"version": "0.2.0"."configurations": [{"type": "chrome"."request": "launch"."name": "Launch hello"."url": "http://localhost:8080"."webRoot": "${workspaceFolder}"."file": "${workspaceFolder}/packages/vue/examples/hello.html"}}]Copy the code
Click the debug button. The program pauses in the debugger and then steps into the createApp method in Vue. CreateApp.
createApp
Let’s directly look at the source code of createApp method, here is a part of the code cut, mainly for the dev environment of some methods to achieve, does not affect the main process, the same below.
// packages/runtime-dom/src/index.ts
export const createApp = ((. args) = > {
constapp = ensureRenderer().createApp(... args)const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
const container = normalizeContainer(containerOrSelector)
if(! container)return
const component = app._component
if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// clear content before mounting
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>
Copy the code
Const app = ensureRenderer().createApp(… Args), we will step through the internal implementation of this line of code. In the debug panel on the left, you can see the value of the APP as follows:
So we can reasonably guess that — ensureRenderer().createApp(… Args), which uses the arguments passed in to initialize the properties and methods, and is mounted in the app variable to return.
After returning the app variable, take out the original mount method, then mount a new method implementation to the app’s mount property, which is the actual implementation of the.mount(‘#demo’) code block in the HTML file, and finally return the app variable.
ensureRenderer().createApp(... args)
Step to const App = ensureRenderer().createApp(… This line of code looks like this:
There are two methods involved: ensureRenderer and createApp, and we’ll look at them one by one.
ensureRenderer
// packages/runtime-dom/src/index.ts
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
Copy the code
The core of this method is createRenderer. Instead of expanding the source code for this method, let’s look at the definition of the value returned by this method:
// packages/runtime-core/src/renderer.ts
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
createApp: CreateAppFunction<HostElement>
}
Copy the code
CreateRenderer returns an object with two properties: render and createApp. Render is the render function referred to in mount below. The createApp method is the return value of createAppAPI. The source code is as follows:
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if(rootProps ! =null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
const context = createAppContext()
const installedPlugins = new Set(a)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() {},set config(v) {},use(plugin: Plugin, ... options:any[]){},mixin(mixin: ComponentOptions) {
},
component(name: string, component? : Component):any{},directive(name: string, directive? : Directive){ }, mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any{},unmount(){},provide(key, value){}})return app
}
}
Copy the code
The source code confirms our guess: createApp mounts several properties and methods in the app variable and returns app.
mount
Const proxy = mount(container, false, Container instanceof SVGElement) const proxy = mount(Container, false, Container instanceof SVGElement);
// packages/runtime-core/src/apiCreateApp.tsmount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
// Use a variable to control that mount is executed only once
if(! isMounted) {// Create a virtual node
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
// HMR root reload
if (__DEV__) {
context.reload = () = > {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)}else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry; (rootContaineras any).__vue_app__ = app
returnvnode.component! .proxy } }Copy the code
As you can see, when we step past render(vnode, rootContainer, isSvg), the Hello World string is displayed in the browser, that is, the mount method mounts the Vue component to the browser. And render is the key rendering method.
One of the highlights is that mount calls the createVNode method to create the virtual node, and render receives the virtual node parameter for rendering.
conclusion
This article for the author serial Vue3 source reading the first, does not involve the interpretation of various principles, the main purpose is to do not need to read the premise of detailed source code, quickly understand the implementation process of Vue3 components.
I hope that readers can read Vue3 source code efficiently through debugging after reading this article. If you think this article can help you, click a like and then go