• Vue3是如何挂载组件的--源码解读(一)


    目录

    Vue.createApp(App).mount('#app')执行过程

    1.创建app入口

    2.render函数

    3.返回app

    mount

    App组件的挂载和渲染过程

    1. patch:进行diff算法.

    2. 处理组件节点.

    3. 调用mountComponent挂载组件

    4. 初始化component,对组件的数据进行赋值和操作

    5. 调用设置和渲染的副作用函数

    6. 调用了副作用函数effect()

    7. renderComponentRoot,对template进行渲染

    8. 把subTree子树的VNode挂载到container上,调用patch()

    9. 处理DOM元素,比如div/button/span(每个子树subtree)

    10. 如果还有子元素,进行patch之后,进行挂载

    11.12.13. 处理子节点,进行挂载


    Vue.createApp(App).mount('#app')执行过程

    1.创建app入口

    来到runtime-dom>src>index.ts文件

    1. // 创建app入口
    2. export const createApp = ((...args) => {
    3. // 创建app使用渲染器ensureRenderer()
    4. // 渲染器有返回createApp属性,createApp调用之后返回app
    5. const app = ensureRenderer().createApp(...args)
    6. if (__DEV__) {
    7. injectNativeTagCheck(app)
    8. injectCompilerOptionsCheck(app)
    9. }
    10. // 从app取出mount
    11. const { mount } = app
    12. // 将app.mount函数进行重写,函数原本传入属性,而我们传入‘#app’字符串
    13. // 重写mount函数是为了跨平台
    14. app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    15. // normalizeContainer通过querySelector获取到container对象
    16. const container = normalizeContainer(containerOrSelector)
    17. if (!container) return
    18. const component = app._component
    19. if (!isFunction(component) && !component.render && !component.template) {
    20. // __UNSAFE__
    21. // Reason: potential execution of JS expressions in in-DOM template.
    22. // The user must make sure the in-DOM template is trusted. If it's
    23. // rendered by the server, the template should not contain any user data.
    24. // 原因:在 DOM 模板中可能执行 JS 表达式。
    25. // 用户必须确保 DOM 模板是受信任的。如果是
    26. // 由服务器呈现,模板不应包含任何用户数据。
    27. component.template = container.innerHTML
    28. // 2.x compat check
    29. if (__COMPAT__ && __DEV__) {
    30. for (let i = 0; i < container.attributes.length; i++) {
    31. const attr = container.attributes[i]
    32. if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
    33. compatUtils.warnDeprecation(
    34. DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
    35. null
    36. )
    37. break
    38. }
    39. }
    40. }
    41. }
    42. // clear content before mounting
    43. // 先清空container中的原本内容
    44. container.innerHTML = ''
    45. const proxy = mount(container, false, container instanceof SVGElement)
    46. if (container instanceof Element) {
    47. container.removeAttribute('v-cloak')
    48. container.setAttribute('data-v-app', '')
    49. }
    50. return proxy
    51. }
    52. return app
    53. }) as CreateAppFunction<Element>

    2.render函数

    来到runtime-core>src>renderer.ts文件

    1. // render函数传入baseCreatoRenderer()的返回值中
    2. const render: RootRenderFunction = (vnode, container, isSVG) => {
    3. if (vnode == null) {
    4. if (container._vnode) {
    5. unmount(container._vnode, null, null, true)
    6. }
    7. } else {
    8. patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    9. }
    10. flushPostFlushCbs()
    11. container._vnode = vnode
    12. }
    13. const internals: RendererInternals = {
    14. p: patch,
    15. um: unmount,
    16. m: move,
    17. r: remove,
    18. mt: mountComponent,
    19. mc: mountChildren,
    20. pc: patchChildren,
    21. pbc: patchBlockChildren,
    22. n: getNextHostNode,
    23. o: options
    24. }
    25. let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
    26. let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
    27. if (createHydrationFns) {
    28. ;[hydrate, hydrateNode] = createHydrationFns(
    29. internals as RendererInternals<Node, Element>
    30. )
    31. }
    32. // baseCreatoRenderer()返回 createApp
    33. return {
    34. render,
    35. hydrate,//ssr服务端错误
    36. // createAppAPI函数使用了柯里化
    37. createApp: createAppAPI(render, hydrate)
    38. }
    39. }

    3.返回app

    来到runtime-core>src>apiCreateApp.ts文件

    1. // createApp最终返回createAppAPI() 返回app对象
    2. export function createAppAPI<HostElement>(
    3. render: RootRenderFunction,
    4. hydrate?: RootHydrateFunction
    5. ): CreateAppFunction<HostElement> {
    6. // 返回createApp
    7. return function createApp(rootComponent, rootProps = null) {
    8. if (!isFunction(rootComponent)) {
    9. rootComponent = { ...rootComponent }
    10. }
    11. if (rootProps != null && !isObject(rootProps)) {
    12. __DEV__ && warn(`root props passed to app.mount() must be an object.`)
    13. rootProps = null
    14. }
    15. const context = createAppContext()
    16. const installedPlugins = new Set()
    17. let isMounted = false
    18. // app对象 app.mixin.directive.comoinebt 创建一个app对象
    19. const app: App = (context.app = {
    20. _uid: uid++,
    21. _component: rootComponent as ConcreteComponent,
    22. _props: rootProps,
    23. _container: null,
    24. _context: context,
    25. _instance: null,
    26. version,
    27. get config() {
    28. return context.config
    29. },
    30. set config(v) {
    31. if (__DEV__) {
    32. warn(
    33. `app.config cannot be replaced. Modify individual options instead.`
    34. )
    35. }
    36. },
    37. use(plugin: Plugin, ...options: any[]) {
    38. if (installedPlugins.has(plugin)) {
    39. __DEV__ && warn(`Plugin has already been applied to target app.`)
    40. } else if (plugin && isFunction(plugin.install)) {
    41. installedPlugins.add(plugin)
    42. plugin.install(app, ...options)
    43. } else if (isFunction(plugin)) {
    44. installedPlugins.add(plugin)
    45. plugin(app, ...options)
    46. } else if (__DEV__) {
    47. warn(
    48. `A plugin must either be a function or an object with an "install" ` +
    49. `function.`
    50. )
    51. }
    52. return app
    53. },
    54. mixin(mixin: ComponentOptions) {
    55. if (__FEATURE_OPTIONS_API__) {
    56. if (!context.mixins.includes(mixin)) {
    57. context.mixins.push(mixin)
    58. } else if (__DEV__) {
    59. warn(
    60. 'Mixin has already been applied to target app' +
    61. (mixin.name ? `: ${mixin.name}` : '')
    62. )
    63. }
    64. } else if (__DEV__) {
    65. warn('Mixins are only available in builds supporting Options API')
    66. }
    67. return app
    68. },
    69. component(name: string, component?: Component): any {
    70. if (__DEV__) {
    71. validateComponentName(name, context.config)
    72. }
    73. if (!component) {
    74. return context.components[name]
    75. }
    76. if (__DEV__ && context.components[name]) {
    77. warn(`Component "${name}" has already been registered in target app.`)
    78. }
    79. context.components[name] = component
    80. return app
    81. },
    82. directive(name: string, directive?: Directive) {
    83. if (__DEV__) {
    84. validateDirectiveName(name)
    85. }
    86. if (!directive) {
    87. return context.directives[name] as any
    88. }
    89. if (__DEV__ && context.directives[name]) {
    90. warn(`Directive "${name}" has already been registered in target app.`)
    91. }
    92. context.directives[name] = directive
    93. return app
    94. },
    95. mount(
    96. rootContainer: HostElement,
    97. isHydrate?: boolean,
    98. isSVG?: boolean
    99. ): any {
    100. if (!isMounted) {
    101. // #5571
    102. if (__DEV__ && (rootContainer as any).__vue_app__) {
    103. warn(
    104. `There is already an app instance mounted on the host container.\n` +
    105. ` If you want to mount another app on the same host container,` +
    106. ` you need to unmount the previous app by calling \`app.unmount()\` first.`
    107. )
    108. }
    109. const vnode = createVNode(
    110. rootComponent as ConcreteComponent,
    111. rootProps
    112. )
    113. // store app context on the root VNode.
    114. // this will be set on the root instance on initial mount.
    115. vnode.appContext = context
    116. // HMR root reload
    117. if (__DEV__) {
    118. context.reload = () => {
    119. render(cloneVNode(vnode), rootContainer, isSVG)
    120. }
    121. }
    122. if (isHydrate && hydrate) {
    123. hydrate(vnode as VNode<Node, Element>, rootContainer as any)
    124. } else {
    125. render(vnode, rootContainer, isSVG)
    126. }
    127. isMounted = true
    128. app._container = rootContainer
    129. // for devtools and telemetry
    130. ;(rootContainer as any).__vue_app__ = app
    131. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    132. app._instance = vnode.component
    133. devtoolsInitApp(app, version)
    134. }
    135. return getExposeProxy(vnode.component!) || vnode.component!.proxy
    136. } else if (__DEV__) {
    137. warn(
    138. `App has already been mounted.\n` +
    139. `If you want to remount the same app, move your app creation logic ` +
    140. `into a factory function and create fresh app instances for each ` +
    141. `mount - e.g. \`const createMyApp = () => createApp(App)\``
    142. )
    143. }
    144. },
    145. unmount() {
    146. if (isMounted) {
    147. render(null, app._container)
    148. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    149. app._instance = null
    150. devtoolsUnmountApp(app)
    151. }
    152. delete app._container.__vue_app__
    153. } else if (__DEV__) {
    154. warn(`Cannot unmount an app that is not mounted.`)
    155. }
    156. },
    157. provide(key, value) {
    158. if (__DEV__ && (key as string | symbol) in context.provides) {
    159. warn(
    160. `App already provides property with key "${String(key)}". ` +
    161. `It will be overwritten with the new value.`
    162. )
    163. }
    164. context.provides[key as string | symbol] = value
    165. return app
    166. }
    167. })
    168. if (__COMPAT__) {
    169. installAppCompatProperties(app, context, render)
    170. }
    171. return app
    172. }
    173. }

    mount

    来到runtime-core>src>renderer.ts文件

    1. // 如果还有子元素,进行pathch之后,进行挂载
    2. mountChildren(
    3. vnode.children as VNodeArrayChildren,
    4. el,
    5. null,
    6. parentComponent,
    7. parentSuspense,
    8. isSVG && type !== 'foreignObject',
    9. slotScopeIds,
    10. optimized
    11. )
    1. const mountChildren: MountChildrenFn = (
    2. children,
    3. container,
    4. anchor,
    5. parentComponent,
    6. parentSuspense,
    7. isSVG,
    8. slotScopeIds,
    9. optimized,
    10. start = 0
    11. ) => {
    12. // 12.对子节点进行遍历
    13. for (let i = start; i < children.length; i++) {
    14. const child = (children[i] = optimized
    15. ? cloneIfMounted(children[i] as VNode)
    16. : normalizeVNode(children[i]))
    17. // 13。遍历后进行挂载
    18. patch(
    19. null,
    20. child,
    21. container,
    22. anchor,
    23. parentComponent,
    24. parentSuspense,
    25. isSVG,
    26. slotScopeIds,
    27. optimized
    28. )
    29. }
    30. }
    1. //片段只能有数组子级 因为它们要么由编译器生成,要么隐式创建 从数组。
    2. // 拿到fragment的所有根组件,拿到子树元素,进行挂载
    3. mountChildren(
    4. // n2.children所有的子元素
    5. n2.children as VNodeArrayChildren,
    6. container,
    7. fragmentEndAnchor,
    8. parentComponent,
    9. parentSuspense,
    10. isSVG,
    11. slotScopeIds,
    12. optimized
    13. )

    创建流程图:

      

    App组件的挂载和渲染过程

    大致分为13步,具体如下:

    1. patch:进行diff算法.

    1. // 注意:此闭包中的函数应使用 'const xxx = () => {}'
    2. // 样式,以防止被小写器内联。
    3. // patch:进行diff算法,crateApp->vnode->element
    4. const patch: PatchFn = (
    5. n1,//旧的vnode,n1为空,进行挂载
    6. n2,//新的vnode,n2与n1不同时,进行patch(diff算法)
    7. container,//patch或mount之后,将vnode挂载到container上
    8. anchor = null,
    9. parentComponent = null,
    10. parentSuspense = null,
    11. isSVG = false,
    12. slotScopeIds = null,
    13. optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
    14. ) => {

    2. 处理组件节点.

    1. } else if (shapeFlag & ShapeFlags.COMPONENT) {
    2. // 处理组件节点,vnode-> 按组件进行处理
    3. processComponent(
    4. n1,
    5. n2,
    6. container,
    7. anchor,
    8. parentComponent,
    9. parentSuspense,
    10. isSVG,
    11. slotScopeIds,
    12. optimized
    13. )
    14. }

    进入processComponent

    1. // 处理组件类型节点
    2. const processComponent = (
    3. n1: VNode | null,
    4. n2: VNode,
    5. container: RendererElement,
    6. anchor: RendererNode | null,
    7. parentComponent: ComponentInternalInstance | null,
    8. parentSuspense: SuspenseBoundary | null,
    9. isSVG: boolean,
    10. slotScopeIds: string[] | null,
    11. optimized: boolean
    12. ) => {

    3. 调用mountComponent挂载组件

    1. // 调用mountComponent挂载组件
    2. mountComponent(
    3. n2,
    4. container,
    5. anchor,
    6. parentComponent,
    7. parentSuspense,
    8. isSVG,
    9. optimized
    10. )
    11. }

    4. 初始化component,对组件的数据进行赋值和操作

    1. // 初始化component,对组件的数据进行赋值和操作
    2. // setup组件实例,对组件的props/emits/slots/data进行初始化
    3. setupComponent(instance)//执行完之后组件啥都有了
    4. if (__DEV__) {
    5. endMeasure(instance, `init`)
    6. }
    7. }
    8. // setup() is async. This component relies on async logic to be resolved
    9. // before proceeding
    10. // setup() 是异步的。此组件依赖于要解析的异步逻辑
    11. // 在继续之前
    12. if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
    13. parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
    14. // Give it a placeholder if this is not hydration
    15. // TODO handle self-defined fallback
    16. if (!initialVNode.el) {
    17. const placeholder = (instance.subTree = createVNode(Comment))
    18. processCommentNode(null, placeholder, container!, anchor)
    19. }
    20. return
    21. }

    5. 调用设置和渲染的副作用函数

    1. // 调用设置和渲染的副作用函数
    2. setupRenderEffect(
    3. instance,
    4. initialVNode,
    5. container,
    6. anchor,
    7. parentSuspense,
    8. isSVG,
    9. optimized
    10. )

    副作用函数

    1. // 副作用函数
    2. const setupRenderEffect: SetupRenderEffectFn = (
    3. instance,
    4. initialVNode,
    5. container,
    6. anchor,
    7. parentSuspense,
    8. isSVG,
    9. optimized
    10. ) => {

    6. 调用了副作用函数effect()

    1. // 调用了副作用函数effect()他来着reactive响应式系统,使用响应式数据时,默认进行依赖收集,进行再次执行
    2. const update: SchedulerJob = (instance.update = () => effect.run())
    3. update.id = instance.uid
    4. // allowRecurse
    5. // #1801, #2043 component render effects should allow recursive updates
    6. //组件渲染效果应允许递归更新
    7. toggleRecurse(instance, true)
    8. if (__DEV__) {
    9. effect.onTrack = instance.rtc
    10. ? e => invokeArrayFns(instance.rtc!, e)
    11. : void 0
    12. effect.onTrigger = instance.rtg
    13. ? e => invokeArrayFns(instance.rtg!, e)
    14. : void 0
    15. update.ownerInstance = instance
    16. }

    7. renderComponentRoot,对template进行渲染

    1. // 7. renderComponentRoot,对template进行渲染
    2. // subTree是template里面的子树,子树被包裹
    3. instance.subTree = renderComponentRoot(instance)
    4. if (__DEV__) {
    5. endMeasure(instance, `render`)
    6. }
    7. if (__DEV__) {
    8. startMeasure(instance, `hydrate`)
    9. }

    8. 把subTree子树的VNode挂载到container上,调用patch()

    1. // 8. 把subTree子树的VNode挂载到container上,调用patch()
    2. patch(
    3. null,//因为是挂载所以是null
    4. subTree,
    5. container,
    6. anchor,
    7. instance,
    8. parentSuspense,
    9. isSVG
    10. )

    9. 处理DOM元素,比如div/button/span(每个子树subtree)

    1. // 9. 处理DOM元素,比如div/button/span(每个子树subtree)
    2. if (shapeFlag & ShapeFlags.ELEMENT) {
    3. processElement(
    4. n1,
    5. n2,
    6. container,
    7. anchor,
    8. parentComponent,
    9. parentSuspense,
    10. isSVG,
    11. slotScopeIds,
    12. optimized
    13. )

    进入processElement

    1. // 如果template的根元素是div
    2. const processElement = (
    3. n1: VNode | null,
    4. n2: VNode,
    5. container: RendererElement,
    6. anchor: RendererNode | null,
    7. parentComponent: ComponentInternalInstance | null,
    8. parentSuspense: SuspenseBoundary | null,
    9. isSVG: boolean,
    10. slotScopeIds: string[] | null,
    11. optimized: boolean
    12. ) => {
    13. isSVG = isSVG || (n2.type as string) === 'svg'
    14. if (n1 == null) {
    15. // 调用mountElement进行挂载元素
    16. mountElement(
    17. n2,
    18. container,
    19. anchor,
    20. parentComponent,
    21. parentSuspense,
    22. isSVG,
    23. slotScopeIds,
    24. optimized
    25. )
    26. } else {

    10. 如果还有子元素,进行patch之后,进行挂载

    1. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    2. // 如果还有子元素,进行pathch之后,进行挂载
    3. mountChildren(
    4. vnode.children as VNodeArrayChildren,
    5. el,
    6. null,
    7. parentComponent,
    8. parentSuspense,
    9. isSVG && type !== 'foreignObject',
    10. slotScopeIds,
    11. optimized
    12. )
    13. }

    11.12.13. 处理子节点,进行挂载

    1. // 11.挂载子节点
    2. const mountChildren: MountChildrenFn = (
    3. children,
    4. container,
    5. anchor,
    6. parentComponent,
    7. parentSuspense,
    8. isSVG,
    9. slotScopeIds,
    10. optimized,
    11. start = 0
    12. ) => {
    13. // 12.对子节点进行遍历
    14. for (let i = start; i < children.length; i++) {
    15. const child = (children[i] = optimized
    16. ? cloneIfMounted(children[i] as VNode)
    17. : normalizeVNode(children[i]))
    18. // 13。遍历后进行挂载
    19. patch(
    20. null,
    21. child,
    22. container,
    23. anchor,
    24. parentComponent,
    25. parentSuspense,
    26. isSVG,
    27. slotScopeIds,
    28. optimized
    29. )
    30. }
    31. }

    最后给大家找了个流程图方便大家理解:

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

  • 相关阅读:
    kafka安装
    计算机毕业设计选什么题目好?springboot 个人健康信息管理系统
    day01_计算机基础和环境搭建
    【线性代数】【一】 1.4 矩阵运算
    ATT&CK框架现有的14个战术进行了详细介绍
    Kafka核心组件详解
    关于操作系统中对进程管理的认识
    SpringBoot基础(九)-- 配置文件优先级
    React 之 react-router-dom
    SpringBoot+vue实现前后端分离的摄影跟拍预定管理系统
  • 原文地址:https://blog.csdn.net/weixin_52691965/article/details/126043512