目录
Vue.createApp(App).mount('#app')执行过程
7. renderComponentRoot,对template进行渲染
8. 把subTree子树的VNode挂载到container上,调用patch()
9. 处理DOM元素,比如div/button/span(每个子树subtree)
来到runtime-dom>src>index.ts文件
- // 创建app入口
- export const createApp = ((...args) => {
- // 创建app使用渲染器ensureRenderer()
- // 渲染器有返回createApp属性,createApp调用之后返回app
- const app = ensureRenderer().createApp(...args)
-
- if (__DEV__) {
- injectNativeTagCheck(app)
- injectCompilerOptionsCheck(app)
- }
- // 从app取出mount
- const { mount } = app
- // 将app.mount函数进行重写,函数原本传入属性,而我们传入‘#app’字符串
- // 重写mount函数是为了跨平台
- app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
- // normalizeContainer通过querySelector获取到container对象
- const container = normalizeContainer(containerOrSelector)
- if (!container) return
-
- const component = app._component
- if (!isFunction(component) && !component.render && !component.template) {
- // __UNSAFE__
- // Reason: potential execution of JS expressions in in-DOM template.
- // The user must make sure the in-DOM template is trusted. If it's
- // rendered by the server, the template should not contain any user data.
- // 原因:在 DOM 模板中可能执行 JS 表达式。
- // 用户必须确保 DOM 模板是受信任的。如果是
- // 由服务器呈现,模板不应包含任何用户数据。
- component.template = container.innerHTML
- // 2.x compat check
- if (__COMPAT__ && __DEV__) {
- for (let i = 0; i < container.attributes.length; i++) {
- const attr = container.attributes[i]
- if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
- compatUtils.warnDeprecation(
- DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
- null
- )
- break
- }
- }
- }
- }
-
- // clear content before mounting
- // 先清空container中的原本内容
- 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>
来到runtime-core>src>renderer.ts文件
- // render函数传入baseCreatoRenderer()的返回值中
- const render: RootRenderFunction = (vnode, container, isSVG) => {
- if (vnode == null) {
- if (container._vnode) {
- unmount(container._vnode, null, null, true)
- }
- } else {
- patch(container._vnode || null, vnode, container, null, null, null, isSVG)
- }
- flushPostFlushCbs()
- container._vnode = vnode
- }
-
- const internals: RendererInternals = {
- p: patch,
- um: unmount,
- m: move,
- r: remove,
- mt: mountComponent,
- mc: mountChildren,
- pc: patchChildren,
- pbc: patchBlockChildren,
- n: getNextHostNode,
- o: options
- }
-
- let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
- let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
- if (createHydrationFns) {
- ;[hydrate, hydrateNode] = createHydrationFns(
- internals as RendererInternals<Node, Element>
- )
- }
- // baseCreatoRenderer()返回 createApp
- return {
- render,
- hydrate,//ssr服务端错误
- // createAppAPI函数使用了柯里化
- createApp: createAppAPI(render, hydrate)
- }
- }
来到runtime-core>src>apiCreateApp.ts文件
- // createApp最终返回createAppAPI() 返回app对象
- export function createAppAPI<HostElement>(
- render: RootRenderFunction,
- hydrate?: RootHydrateFunction
- ): CreateAppFunction<HostElement> {
- // 返回createApp
- return function createApp(rootComponent, rootProps = null) {
- if (!isFunction(rootComponent)) {
- rootComponent = { ...rootComponent }
- }
-
- 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()
-
- let isMounted = false
- // app对象 app.mixin.directive.comoinebt 创建一个app对象
- 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) {
- if (__DEV__) {
- warn(
- `app.config cannot be replaced. Modify individual options instead.`
- )
- }
- },
-
- use(plugin: Plugin, ...options: any[]) {
- if (installedPlugins.has(plugin)) {
- __DEV__ && warn(`Plugin has already been applied to target app.`)
- } else if (plugin && isFunction(plugin.install)) {
- installedPlugins.add(plugin)
- plugin.install(app, ...options)
- } else if (isFunction(plugin)) {
- installedPlugins.add(plugin)
- plugin(app, ...options)
- } else if (__DEV__) {
- warn(
- `A plugin must either be a function or an object with an "install" ` +
- `function.`
- )
- }
- return app
- },
-
- mixin(mixin: ComponentOptions) {
- if (__FEATURE_OPTIONS_API__) {
- if (!context.mixins.includes(mixin)) {
- context.mixins.push(mixin)
- } else if (__DEV__) {
- warn(
- 'Mixin has already been applied to target app' +
- (mixin.name ? `: ${mixin.name}` : '')
- )
- }
- } else if (__DEV__) {
- warn('Mixins are only available in builds supporting Options API')
- }
- return app
- },
-
- component(name: string, component?: Component): any {
- if (__DEV__) {
- validateComponentName(name, context.config)
- }
- if (!component) {
- return context.components[name]
- }
- if (__DEV__ && context.components[name]) {
- warn(`Component "${name}" has already been registered in target app.`)
- }
- context.components[name] = component
- return app
- },
-
- directive(name: string, directive?: Directive) {
- if (__DEV__) {
- validateDirectiveName(name)
- }
-
- if (!directive) {
- return context.directives[name] as any
- }
- if (__DEV__ && context.directives[name]) {
- warn(`Directive "${name}" has already been registered in target app.`)
- }
- context.directives[name] = directive
- return app
- },
-
- mount(
- rootContainer: HostElement,
- isHydrate?: boolean,
- isSVG?: boolean
- ): any {
- if (!isMounted) {
- // #5571
- if (__DEV__ && (rootContainer as any).__vue_app__) {
- warn(
- `There is already an app instance mounted on the host container.\n` +
- ` If you want to mount another app on the same host container,` +
- ` you need to unmount the previous app by calling \`app.unmount()\` first.`
- )
- }
- 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
- ;(rootContainer as any).__vue_app__ = app
-
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- app._instance = vnode.component
- devtoolsInitApp(app, version)
- }
-
- return getExposeProxy(vnode.component!) || vnode.component!.proxy
- } else if (__DEV__) {
- warn(
- `App has already been mounted.\n` +
- `If you want to remount the same app, move your app creation logic ` +
- `into a factory function and create fresh app instances for each ` +
- `mount - e.g. \`const createMyApp = () => createApp(App)\``
- )
- }
- },
-
- unmount() {
- if (isMounted) {
- render(null, app._container)
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- app._instance = null
- devtoolsUnmountApp(app)
- }
- delete app._container.__vue_app__
- } else if (__DEV__) {
- warn(`Cannot unmount an app that is not mounted.`)
- }
- },
-
- provide(key, value) {
- if (__DEV__ && (key as string | symbol) in context.provides) {
- warn(
- `App already provides property with key "${String(key)}". ` +
- `It will be overwritten with the new value.`
- )
- }
-
- context.provides[key as string | symbol] = value
-
- return app
- }
- })
-
- if (__COMPAT__) {
- installAppCompatProperties(app, context, render)
- }
-
- return app
- }
- }
来到runtime-core>src>renderer.ts文件
- // 如果还有子元素,进行pathch之后,进行挂载
- mountChildren(
- vnode.children as VNodeArrayChildren,
- el,
- null,
- parentComponent,
- parentSuspense,
- isSVG && type !== 'foreignObject',
- slotScopeIds,
- optimized
- )
- const mountChildren: MountChildrenFn = (
- children,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized,
- start = 0
- ) => {
- // 12.对子节点进行遍历
- for (let i = start; i < children.length; i++) {
- const child = (children[i] = optimized
- ? cloneIfMounted(children[i] as VNode)
- : normalizeVNode(children[i]))
- // 13。遍历后进行挂载
- patch(
- null,
- child,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
- }
- }
- //片段只能有数组子级 因为它们要么由编译器生成,要么隐式创建 从数组。
- // 拿到fragment的所有根组件,拿到子树元素,进行挂载
- mountChildren(
- // n2.children所有的子元素
- n2.children as VNodeArrayChildren,
- container,
- fragmentEndAnchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
创建流程图:
大致分为13步,具体如下:
- // 注意:此闭包中的函数应使用 'const xxx = () => {}'
- // 样式,以防止被小写器内联。
- // patch:进行diff算法,crateApp->vnode->element
- const patch: PatchFn = (
- n1,//旧的vnode,n1为空,进行挂载
- n2,//新的vnode,n2与n1不同时,进行patch(diff算法)
- container,//patch或mount之后,将vnode挂载到container上
- anchor = null,
- parentComponent = null,
- parentSuspense = null,
- isSVG = false,
- slotScopeIds = null,
- optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
- ) => {
- } else if (shapeFlag & ShapeFlags.COMPONENT) {
- // 处理组件节点,vnode->
按组件进行处理 - processComponent(
- n1,
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
- }
进入processComponent
- // 处理组件类型节点
- 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挂载组件
- mountComponent(
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- optimized
- )
- }
- // 初始化component,对组件的数据进行赋值和操作
- // setup组件实例,对组件的props/emits/slots/data进行初始化
- setupComponent(instance)//执行完之后组件啥都有了
- if (__DEV__) {
- endMeasure(instance, `init`)
- }
- }
-
- // setup() is async. This component relies on async logic to be resolved
- // before proceeding
- // setup() 是异步的。此组件依赖于要解析的异步逻辑
- // 在继续之前
- if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
- parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
-
- // Give it a placeholder if this is not hydration
- // TODO handle self-defined fallback
- if (!initialVNode.el) {
- const placeholder = (instance.subTree = createVNode(Comment))
- processCommentNode(null, placeholder, container!, anchor)
- }
- return
- }
- // 调用设置和渲染的副作用函数
- setupRenderEffect(
- instance,
- initialVNode,
- container,
- anchor,
- parentSuspense,
- isSVG,
- optimized
- )
副作用函数
- // 副作用函数
- const setupRenderEffect: SetupRenderEffectFn = (
- instance,
- initialVNode,
- container,
- anchor,
- parentSuspense,
- isSVG,
- optimized
- ) => {
- // 调用了副作用函数effect()他来着reactive响应式系统,使用响应式数据时,默认进行依赖收集,进行再次执行
- const update: SchedulerJob = (instance.update = () => effect.run())
- update.id = instance.uid
- // allowRecurse
- // #1801, #2043 component render effects should allow recursive updates
- //组件渲染效果应允许递归更新
- toggleRecurse(instance, true)
-
- if (__DEV__) {
- effect.onTrack = instance.rtc
- ? e => invokeArrayFns(instance.rtc!, e)
- : void 0
- effect.onTrigger = instance.rtg
- ? e => invokeArrayFns(instance.rtg!, e)
- : void 0
- update.ownerInstance = instance
- }
- // 7. renderComponentRoot,对template进行渲染
- // subTree是template里面的子树,子树被
包裹 - instance.subTree = renderComponentRoot(instance)
- if (__DEV__) {
- endMeasure(instance, `render`)
- }
- if (__DEV__) {
- startMeasure(instance, `hydrate`)
- }
- // 8. 把subTree子树的VNode挂载到container上,调用patch()
- patch(
- null,//因为是挂载所以是null
- subTree,
- container,
- anchor,
- instance,
- parentSuspense,
- isSVG
- )
- // 9. 处理DOM元素,比如div/button/span(每个子树subtree)
- if (shapeFlag & ShapeFlags.ELEMENT) {
- processElement(
- n1,
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
进入processElement
- // 如果template的根元素是div
- const processElement = (
- n1: VNode | null,
- n2: VNode,
- container: RendererElement,
- anchor: RendererNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- isSVG: boolean,
- slotScopeIds: string[] | null,
- optimized: boolean
- ) => {
- isSVG = isSVG || (n2.type as string) === 'svg'
- if (n1 == null) {
- // 调用mountElement进行挂载元素
- mountElement(
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
- } else {
- } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- // 如果还有子元素,进行pathch之后,进行挂载
- mountChildren(
- vnode.children as VNodeArrayChildren,
- el,
- null,
- parentComponent,
- parentSuspense,
- isSVG && type !== 'foreignObject',
- slotScopeIds,
- optimized
- )
- }
- // 11.挂载子节点
- const mountChildren: MountChildrenFn = (
- children,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized,
- start = 0
- ) => {
- // 12.对子节点进行遍历
- for (let i = start; i < children.length; i++) {
- const child = (children[i] = optimized
- ? cloneIfMounted(children[i] as VNode)
- : normalizeVNode(children[i]))
- // 13。遍历后进行挂载
- patch(
- null,
- child,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
- }
- }
最后给大家找了个流程图方便大家理解:

如果大家觉得还不错,下方公z号👇,来跟作者一起学习吧!