After reading the previous article, I believe that we have been able to have a more in-depth grasp of Vue3’s responsive principle. But it’s not enough to just grasp the responsive principle, and I think Vue3 has three pillars.
- One is the responsive system that we talked about;
- Second, it is written by us
template
,jsx
Code conversion toVirtual NodeThis process is made up ofcompiler-dom
,compiler-core
To provide thecompiler
Function, whose return value is arender
Delta function, I’ll call that in a few more articlesrender
Function forCompile the render; - Third, we willVirtual NodeintoReal Nodethe
render
Delta function, I’ll call that in a few more articlesrender
Function forRendering render;
Note:
- Because the source code analyzed in this article is
runtime-dom
,runtime-core
In this article unless otherwise specifiedrender
Function refers toApply colours to a drawingrender
Function.- The virtual Node mentioned above is often referred to as the virtual DOM, which has the same meaning as a real Node and a real DOM.
In fact, the above description of the three pillars has already highly summarized the core functions of the Vue3 framework. This article will start with a brief example of how to implement createApp. In the course of this analysis, we will discuss the code collaboration between the Run-time dom and run-time core, and the implementation logic of createApp. The implementation logic covers the call to the render function. The implementation details of the Render function will be analyzed step by step in subsequent articles.
Example – Initialize a Vue3 application
In real development we usually initialize a Vue application with the following:
// Snippet 1
import { createApp } from 'vue'
// import the root component App from a single-file component.
import App from './App.vue'
const vueApp = createApp(App)
vueApp.mount('#app')
Copy the code
It’s a few lines of code, but it’s actually a lot of work, because the first thing you need to do is convert the contents of app. vue into a virtual Node. After compiling, the parameter App passed to the function createApp in snippet 1 is a component object. VueApp is an object that has a method called mount. This function converts the component object App into a virtual Node, which in turn converts the virtual Node into a real Node and mounts it under the DOM element that # App points to. As for the compilation process, it will be explained in detail in future articles analyzing Compiler-DOM and Compiler-core, which will not be mentioned in this article.
Write code that does not compile the transformation
To understand the normal operation of the program, it is necessary to use a virtual Node, we transformed the program into the following form:
<! -- Code snippet 2-->
<html>
<body>
<div id="app1"></div>
</body>
<script src="./runtime-dom.global.js"></script>
<script>
const { createApp, h } = VueRuntimeDOM
const RootComponent = {
render(){
return h('div'.'Yang Yitao likes to study source code')
}
}
createApp(RootComponent).mount("#app1")
</script>
</html>
Copy the code
The most obvious change is that we are defining component objects directly, instead of compiling the contents of the app. vue file into component objects, we are also compiling a render function in the component object by hand, and we do not need to compile the render function from the template. Note that there are two compilation processes involved, one is to convert the.vue file into a component object, and the other is to convert the template involved in the component object into the render function, both of which will be covered in more detail in future articles.
In fact, the compile render function of the RootComponent object in snippet 2 executes at some point, as explained in this article when we examine the internal implementation of createApp.
Compile the render function
But we know that an important feature of Vue3 is the freedom to control which data is responsive, and this is due to our setup method. We further convert snippet 2 to the following form:
<! -- Code snippet 3-->
<html>
<body>
<div id="app" haha="5"></div>
</body>
<script src="./runtime-dom.global.js"></script>
<script>
const { createApp, h, reactive } = VueRuntimeDOM
const RootComponent = {
setup(props, context){
let relativeData = reactive({
name:'Yang Yitao'.age: 60
})
let agePlus = () = >{
relativeData.age++
}
return {relativeData, agePlus}
},
render(proxy){
return h('div', {
onClick: proxy.agePlus,
innerHTML:`${proxy.relativeData.name}already${proxy.relativeData.age}Aged, click here to continue to increase the age '
} )
}
}
createApp(RootComponent).mount("#app")
</script>
</html>
Copy the code
As you can see from snippet 3, the return value of the setup method can be obtained by compiling the Render function with the prxoy argument. You might think it’s a little redundant, and it is. Because the compile render function here is itself a product of Vue2. In Vue3 we can write this directly, and the code changes as follows:
<! -- Code snippet 4-->
<html>
<body>
<div id="app" haha="5"></div>
</body>
<script src="./runtime-dom.global.js"></script>
<script>
const { createApp, h, reactive } = VueRuntimeDOM
const RootComponent = {
setup(props, context){
let relativeData = reactive({
name:'Yang Yitao'.age: 60
})
let agePlus = () = >{
relativeData.age++
}
return () = >h('div', {
onClick: agePlus,
innerHTML:`${relativeData.name}already${relativeData.age}Aged, click here to continue to increase the age '
} )
}
}
createApp(RootComponent).mount("#app")
</script>
</html>
Copy the code
In real development, setup typically returns either an object or a function that returns JSX, where the JSX code is converted at compile time into something like snippet 4, in this case in a TSX file format. If you return an object, you usually write template code in a.vue file. You can do either, but remember that Template has compile-time static analysis to improve performance, whereas JSX is more flexible.
summary
Above we briefly looked at some simple forms of component encoding in Vue3 and understood how the component object passed to the function createApp plays a fundamental role in the real world. Let’s move on to the implementation of createApp. When analyzing createApp, I will sometimes revisit some of the effects mentioned above to make it easier to understand Vue3 by comparing them to the source code.
CreateApp code implementation
The outer wrapper of createApp
CreateApp (core/ Packages/Runtime-dom); createApp (core/packages/ Runtime-dom); createApp (createApp);
// Snippet 5
// Omit some code here...
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
// Omit some code here...
const rendererOptions = extend({ patchProp }, nodeOps)
// Omit some code here...
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
// Omit some code here...
export const createApp = ((. args) = > {
constapp = ensureRenderer().createApp(... args)// Omit some code here...
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
// Omit some code here...
const proxy = mount(container, false, container instanceof SVGElement)
// Omit some code here...
return proxy
}
return app
}) as CreateAppFunction<Element>
// Omit some code here...
Copy the code
After making a series of cuts to the code, we found three key points:
- The real
Vue
The application object is the executionensureRenderer().createApp(... args)
Created, andensureRenderer
The function is called internallycreateRenderer
Function. thiscreateRenderer
Function inruntime-core
; - Calling a function
createRender
Function is passed in as an argumentrendererOptions
These parameters are operationsDOM
Nodes andDOM
Specific methods of node attributes. - To create the
Vue
The application objectapp
After, rewrite itmount
Method, rewrittenmount
Inside the method, some browser-specific operations are done, such as clearingDOM
Node. It then calls the pre-overridemount
Method to mount.
In summary, what the Run-time DOM really provides is the ability to manipulate the browser platform DOM nodes. RendererOptions is a method for manipulating specific DOM nodes, and rendererOptions is a method for exposing run-time core. Because the method of manipulating the real browser DOM is passed in as an argument, this can be the specific method of manipulating nodes on other platforms as well. In other words, run-time Core only knows that certain nodes need to be added, modified, or removed, but it doesn’t matter whether those nodes are browser DOM nodes or nodes from other platforms. Run-time Core calls whatever parameters are passed in. In fact, we can use this kind of hierarchical coding idea for reference in actual coding.
createRenderer
Core /packages/ Run-time core/ SRC /render. Ts createRenderer
// Snippet 6
export function createRenderer<
HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
Copy the code
Let’s move on to the function baseCreateRenderer, which has over 2000 lines of code that I’ve streamlined considerably:
// Snippet 7
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
// Omit about 2000 lines of code here...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
Copy the code
Snippet 7 omits most of the code, and I leave only the return value. In fact, the baseCreateRenderer function is the core of the runtime-Core, because all the logic for converting a virtual Node to a real Node is included in this function, including the oft-mentioned diff algorithm. BaseCreateRenderer createApp createAppAPI(render, hydrate) CreateAppAPI (Render, hydrate) actually returns a function. Const app = ensureRenderer().createApp(); const app = ensureRenderer().createApp(… CreateApp args).
createAppAPI
We go to createAppAPI in core/ Packages/Runtime-core/SRC/apicReateApp. ts:
// Snippet 8
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
// Omit some code here...
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
},
set config(v) {
// Omit some code here...
},
use(plugin: Plugin, ... options:any[]) {
// Omit some code here...
},
mixin(mixin: ComponentOptions) {
// Omit some code here...
},
component(name: string, component? : Component):any {
// Omit some code here...
},
directive(name: string, directive? : Directive) {
// Omit some code here...}, mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
// Omit some code here...
},
unmount() {
// Omit some code here...
},
provide(key, value) {
// Omit some code here...}})// Omit some code here...
return app
}
}
Copy the code
As you can see from code snippet 8, createAppAPI returns a function createApp, and the return value of this function is an object app. App is actually the Vue application that we created. The app has many properties and methods that represent the information and capabilities of the Vue application object.
The mount method
As shown in code snippet 1, the first operation after creating a Vue application is to call the mount method to mount it. We can ignore the rest of the content for the moment and focus on the implementation of the mount method of the app:
// Snippet 9mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
if(! isMounted) {const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
// Omit some code here...
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)}else {
render(vnode, rootContainer, isSVG)
}
isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app
// Omit some code here...
returngetExposeProxy(vnode.component!) || vnode.component! .proxy }// Omit some code here...
}
Copy the code
Snippet 9 omits a lot of development-phase code, which can be summarized as follows:
- Root the component object
rootComponent
(Code snippet4The value passed in toRootComponent
) intoVirtual Node; - call
render
The function takes thisVirtual NodeConverted intoReal NodeAnd mountrootContainer
On the element pointing to. That hererender
Where does the function come from? From snippets8It’s not hard to see that it’s passed in as an argument, so where does that argument come from, so let’s go back to the code snippet7Find exactly the functionbaseCreateRenderer
Internally declaredrender
Function. - call
getExposeProxy
The function takes a proxy object and returns it.
How to convert a component object into a virtual Node and how to implement the render function are not discussed in this article, as they are both large and new topics that need to be covered in a new article. Let’s take a look at the getExposeProxy function here, because it’s related to the reactive systems we talked about earlier, and it should be easier to understand now that you’ve learned a lot about reactive systems.
getExposeProxy
// Snippet 10
export function getExposeProxy(instance: ComponentInternalInstance) {
if (instance.exposed) {
return (
instance.exposeProxy ||
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
get(target, key: string) {
if (key in target) {
return target[key]
} else if (key in publicPropertiesMap) {
return publicPropertiesMap[key](instance)
}
}
}))
)
}
}
Copy the code
The heart of code snippet 10 lies in this newly created Proxy instance. The object initialized by this Proxy is the execution result of proxyRefs(markRaw(instance.exposed)). Regardless of the exact meaning of instance.exposed, it can be understood from the logic of the program that if you get data through instance.exposeProxy, you can only get properties that instance.exposed or publicPropertiesMap have. Otherwise, undefined is returned. As for the reason why markRaw is called first and proxyRefs is called, proxyRefs makes a conditional judgment internally. If the object passed in is responsive itself, it will be returned directly, so it needs to process the non-responsive object first. ProxyRefs are used to access the original value of a reactive object without writing.value, which was analyzed in the previous article.
Special use of ref
So what exactly is instance.exposed? Let’s first look at a practical application where ref gets the content of a child component:
<script> import Child from './ child.vue 'export default {components: { Child }, mounted() { // this.$refs.child will hold an instance of <Child /> } } </script> <template> <Child ref="child" /> </template>Copy the code
// Snippet 2, filename: child.vue
export default {
expose: ['publicData'.'publicMethod'].data() {
return {
publicData: 'foo'.privateData: 'bar'}},methods: {
publicMethod() {
/ *... * /
},
privateMethod() {
/ *... * /}}}Copy the code
This particular use of ref can be explained in more detail in the official documentation, but it’s important to note that if a child component sets a value for the Expose property, the parent component only gets the value of those properties that expose declares. This is why there is such a proxy object in snippet 10, and in turn we have seen the implementation of a mechanism to protect the content of the child component from arbitrary access by the parent component.
conclusion
This article first to throw a specific case, since createApp speak again, following the function call stack, mentioned the compilation render, rendering render two functions, analyzes the createRenderer, createAppAPI, mount, getExposeProxy etc. Function. Here you can understand the basic process of creating a Vue application. This article has laid a foundation for the analysis of the specific implementation of render function, about the specific implementation of render function I will be introduced in the next article, please friends look forward to.
Write in the last
After reading the article, you can do the following things to support you:
- if
Like, look, forward
Can let the article help more people in need; - If you are the author of wechat public account, you can find me to open
White list
.reprinted
My original articles;
Finally, please pay attention to my wechat public account: Yang Yitao, you can get my latest news.