This article follows the previous entry function with a simple example (we will call it this example) to parse the operation after the mount in detail (parsing the options parameter passed in, compiling the template template, generating the virtual DOM object, parsing the virtual DOM object to generate the real DOM, how to trigger the DOM re-rendering after the data modification, etc.).
Simple example (added with Vue3setup
Method as an example)
<div id='app'>
{{message}}
<button @click="modifyMessage">Modify the data</button>
</div>
Copy the code
const { ref, createApp } = Vue
const app = createApp({
// Vue3 added setup property
setup(props) {
const message = ref('Test data')
const modifyMessage = () = > {
message.value = 'Modified test data'
}
return {
message,
modifyMessage
}
}
}).mount('#app')
Copy the code
app.mount
const proxy = mount(container, false, container instanceof SVGElement)
Copy the code
Vue3 creates the app object with the createApp method, and then calls the app.mount method to get the root node. Then calls the original mount method to mount the root node
// Generate the context object
const context = createAppContext()
// The original mount functionmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {if(! isMounted) {const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
// ...
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
(rootContainer as any).__vue_app__ = app
// ...
returnvnode.component! .proxy } }Copy the code
The mount method first calls the createVNode method to create a vNode object. Briefly take a look at the internal implementation of the createVNode method (mainly to see the generation of the shapeFlag variable, associated with the subsequent call to the patch method).
export const createVNode = (
__DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
) :VNode {
// ...
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
// ...
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true)}/ / createBaseVNode method
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
const vnode = {
type,
key,
props,
shapeFlag,
patchFlag,
// ...
}
// ...
return vnode
}
Copy the code
You can see that shapeFlag is determined by the type of argument passed in to the rootComponent object that calls this method, the createApp method. In this case we are passing in an object, so the value of shapeFlag is shapeflags.stateful_component (1 << 2 = 4). Then set the appContext property of vNode to the Context object (this object is generated by calling createAppContext and will be parsed later when using the context object properties). Let’s go back to the mount method
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
Copy the code
Since isHydrate is passed in as false, the render method is called with the vNode object, root root node, and false(isSVG is the value of container Instanceof SVGElement, not an SVG element in this case). The render function called is a pass-through to the createAppAPI method, which is called in the baseCreateRenderer method. Go back to the baseCreateRenderer method and look at the Render method
const render: RootRenderFunction = (vnode, container, isSVG) = > {
// VNode exists
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null.null.true)}}else {
patch(container._vnode || null, vnode, container, null.null.null, isSVG)
}
// ...
}
Copy the code
Use patch to resolve the root component
Since vNode exists, the patch method is called
patch(null, vnode, container, null.null.null.false)
/ / patch method
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false:!!!!! n2.dynamicChildren) = > {
// ...
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// ...
} else if (shapeFlag & ShapeFlags.COMPONENT) {
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__) {
warn('Invalid VNode type:', type, ` (The ${typeof type}) `)}}}Copy the code
The patch method selects the function to call based on the value of type, which in this case is {setup: Setup (props) {}} is an object, so enter the default branch, because the vnode shapeFlag just resolved as 4 (4 & (1 < < 2 | 1 < < 1) = 4), so call processComponent method (processing components)
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) {
// The component is keep-alive
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {}
else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else{}}Copy the code
Mount the root component
Then call the mountComponent method
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// ...
// resolve props and slots for setup context
if(! (__COMPAT__ && compatMountInstance)) {if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance)
if (__DEV__) {
endMeasure(instance, `init`)}}// ...
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
// ...
}
Copy the code
Internally, instance objects are created using the createComponentInstance method (create an Object and add a _ attribute to it using Object.defineProperty, which is the value of instance Object, The vNode property of the object is the vNode passed in when the call is made. There are some other attributes, which will be explained in more detail later). Then call the setupComponent(instance) method.
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
// ...
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
// ...
instance.accessCache = Object.create(null)
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
// 2. call setup()
const { setup } = Component
if (setup) {
// ...
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
// ...}}Copy the code
The setupComponent method calls the setupStatefulComponent method, which internally uses a new Proxy() for instance. CTX with attributes like _, $, and $el. And agent of hook function object is PublicInstanceProxyHandlers
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {},
set(
{ _: instance }: ComponentRenderContext,
key: string,
value: any
): boolean {},
has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }: ComponentRenderContext, key: string){}}Copy the code
PublicInstanceProxyHandlers contains a get, set, from the three hooks trap, subsequent trigger when we parse it in detail. Back in the setupStatefulComponent function, when the setup property exists (in this case), start running the setup function to parse the data and methods in the function body.
conclusion
In the original mount method, the createVNode is used to create the vNode. The parameter type is passed in by calling createApp. The shapeFlag is resolved based on the type of type. Then call the patch method and determine which method to call according to the value of shapeFlag. The mountComponent method is used to create the instance object. The CTX property of the object is added with attributes such as _, $, and $EL. The vNode property of the instance object is the created vNode. The Component property of a VNode object is instance, and the two are related. Then use the new Proxy to Proxy instance. CTX and set the get, set, and HAS hook functions. You’ll then parse the setup method to see how the hook function is set for the data passed in.