This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
preface
Prepare a demo
<div id="app"> {{ a }} </div> ... createApp({ setup() { const data = reactive({ a: 1111 }) return { ... toRefs(data) } } }).mount('#app')Copy the code
In this code, the createApp and mount functions complete the vue initialization process, which is divided into two parts: one is to create the app instance, the other is to mount the instance. Come down and take a look.
CreateApp Creates an instance
The function basically does two things
- create
app
The instance - rewrite
mount
function
The vue instance was created in a single line of code
const app = ensureRenderer().createApp(... args)Copy the code
There is a createApp function in a renderer.
So what does ensureRenderer do?
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code
You can see that it’s used here to get renderer, and if it doesn’t exist it’s created by createRenderer. So what does createRenderer do?
The trace code eventually shows that createRenderer ultimately calls the baseCreateRenderer function. This function is a huge function, because our main task is to get a renderer, and then get the createApp function from the renderer. So we can first look at the return value:
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
Copy the code
We can clearly see that our createApp function calls createAppAPI, and we pass in render and hydrate when we call them. Both functions are defined in the baseCreateRenderer function. The render function is used to unload and update nodes. The hydrate function is used when SSR is used. I don’t care about it here.
Then we can look at the createAppAPI function:
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 // vue Const app: app = (context.app = {//... }) // Return app instanceCopy the code
The createAppAPI function takes two arguments, but internally returns the createApp function directly. The createApp function defines the context and creates the app instance and returns it. We’ve defined some of the common functions in our app and one of them is the mount function.
At this point, the step of creating the app instance is clear:
- You get a renderer
- Get the createApp from the renderer and call the app instance
Overriding the mount function is to convert the selector into a DOM node as a container and then call the original mount function.
Let’s take a look at how the app defines the mount function. # introduction
Prepare a demo
<div id="app"> {{ a }} </div> ... createApp({ setup() { const data = reactive({ a: 1111 }) return { ... toRefs(data) } } }).mount('#app')Copy the code
In this code, the createApp and mount functions complete the vue initialization process, which is divided into two parts: one is to create the app instance, the other is to mount the instance. Come down and take a look.
CreateApp Creates an instance
The function basically does two things
- create
app
The instance - rewrite
mount
function
The vue instance was created in a single line of code
const app = ensureRenderer().createApp(... args)Copy the code
There is a createApp function in a renderer.
So what does ensureRenderer do?
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code
You can see that it’s used here to get renderer, and if it doesn’t exist it’s created by createRenderer. So what does createRenderer do?
The trace code eventually shows that createRenderer ultimately calls the baseCreateRenderer function. This function is a huge function, because our main task is to get a renderer, and then get the createApp function from the renderer. So we can first look at the return value:
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
Copy the code
We can clearly see that our createApp function calls createAppAPI, and we pass in render and hydrate when we call them. Both functions are defined in the baseCreateRenderer function. The render function is used to unload and update nodes. The hydrate function is used when SSR is used. I don’t care about it here.
Then we can look at the createAppAPI function:
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 // vue Const app: app = (context.app = {//... }) // Return app instanceCopy the code
The createAppAPI function takes two arguments, but internally returns the createApp function directly. The createApp function defines the context and creates the app instance and returns it. We’ve defined some of the common functions in our app and one of them is the mount function.
At this point, the step of creating the app instance is clear:
- You get a renderer
- Get the createApp from the renderer and call the app instance
Overriding the mount function is to convert the selector into a DOM node as a container and then call the original mount function.
Let’s take a look at how the app defines the mount function.
Mount Mount
The simplified mount function is as follows
mount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : Boolean): any {// initialize the process if (! isMounted) { const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context render(vnode, rootContainer, # app._container = rootContainer # app._container = rootContainer Because this component is going to be a traceable responsive object return vnode.ponent! .proxy }Copy the code
We will create a VNode based on rootComponent, then call render to render, and return vnode.component.proxy.
Let’s look at the main render function
Render function
The render function is passed in the createAppAPI function, which is the render function defined in the baseCreateRenderer.
const render: RootRenderFunction = (vnode, container, isSVG) => {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
container._vnode = vnode
}
Copy the code
It mainly determines the uninstallation logic or update logic. If vnode does not exist, it is the ummount uninstallation logic; otherwise, it is the patch update logic.
The patch function
The first three parameters of the patch function are n1, n2, and Container, which represent oldVNode, newVNode, and Container, respectively.
The main function of patch is to determine what the new Vnode is according to its type, and then call different functions for different types of nodes to compare the old and new nodes and render them.
Since the createApp function in Demo is passed an object, it is considered a component and processComponent is called.
CreateApp ({/* here is component */})
const patch: PatchFn = (n1, // old VNode n2, // New VNode Container, Anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false ) => { const { type, ref, shapeFlag } = n2 if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } }Copy the code
ProcessComponent function
The main thread in this function is whether to update or initialize the mount based on whether there is an old vNode. For our theme, just focus on the mountComponent 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) => {// mountComponent(n2, Container, Anchor, parentComponent, parentSuspense, isSVG, optimized)}Copy the code
MountComponent function
SetupRenderEffect (); setupComponent (); setupRenderEffect (); setupRenderEffect (); setupRenderEffect ();
The setupComponent function is used to initialize props and slots on the current instance and to call the setup function
The setupRenderEffect function creates the render function for the current instance as responsive
const mountComponent: MountComponentFn = (initialVNode, // initialize vNode/new VNode, corresponding to n2 Container, Anchor, parentComponent, parentSuspense, isSVG, optimized ) => { const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )) setupComponent(instance) setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) }Copy the code
The setupComponent function
The functions props and slots are initialized, and then setupStatefulComponent is called to invoke the setup function
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
const { props, children } = instance.vnode
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
setupStatefulComponent(instance, isSSR)
return setupResult
}
Copy the code
Let’s look at the setupStatefulComponent function
SetupStatefulComponent function
Main operations in the function:
-
Proxy the CTX on the instance and bind it to the proxy property of the instance
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)) Copy the code
Proxying CTX on the instance, future CTX operations will be responsive
We need to use the instance. Proxy object
Because instance. CTX is different in prod and dev environments
-
Create setupContext context
const setupContext = (instance.setupContext = createSetupContext(instance) Copy the code
-
Setup is called with props and setupContext passed in. This means that the setup function will take these two arguments.
const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, [instance.props, setupContext] ) Copy the code
-
Then we call handleSetupResult to process setupResult
HandleSetupResult function
If setupResult is function, then setupResult is assigned to instance.render
If the type is object, then the result of setupResult via proxyRefs proxy is assigned to instance-setupState.
instance.setupState = proxyRefs(setupResult)
Copy the code
The function of proxyRefs is to use the setupResult object as a layer of proxy
Makes it easy for users to access values of type REF directly
For example, setupResult has an object of type ref, so users can use count instead of count. Value
Here is the official website said in the automatic deconstruction of Ref type
SetupResult in demo is an object, so it will be proxyRefs.
Finally, the finishComponentSetup function is also called.
FinishComponentSetup function
I did two things in the function
- if
instance
There is no rendering function in the compiler according tocomponent.template
Generate and assign toinstance.render
- Compatible with
2.x
In theoptions API
At this point, you have seen the main flow in setupComponent. Now you can go back to setupRenderEffect.
SetupRenderEffect function
The main thing in this function is to define instance.update, whose value is the return value of effect, which is a reactive function activeEffect.
const setupRenderEffect = () => {
// create reactive effect for rendering
instance.update = effect(
function componentEffect() {
// ...
}, prodEffectOptions)
}
Copy the code
Effect is called with the componentEffect function passed in. ComponentEffect is the dependency to be collected.
We also pass a prodEffectOptions in which the scheduler is used to trigger our dependencies. Vue3 Reactive source code
Initialize mount and update patch in componentEffect function.
The initialization process is:
-
Execute beforeMount hooks
invokeArrayFns(bm) Copy the code
-
Get the subTree and recursively call Patch to process the subTree
patch( null, subTree, container, anchor, instance, parentSuspense, isSVG ) Copy the code
-
Execute the Mounted hook
queuePostRenderEffect(m, parentSuspense) Copy the code
When this is done, the APP instance is successfully mounted to the DOM.
conclusion
- First create
vue
Instance, and then mount the instance. - During the mount process, the corresponding virtual object is created based on the incoming component object
DOM
. - According to the virtual
DOM
Create the corresponding component instance. - And then initialize
props
和slots
And call thesetup
Function. - Generate render function
- Compatible with
2.x
Version of theoptions API
The processing of - perform
beforeMount
Hook function - To obtain
subTree
And call thepatch
To deal with - call
mounted
Hook function
Follow depth-first throughout the mounting process.
Refer to the link
mini-vue