• Vue源码阅读:createApp的过程(三)


    vue版本:3.2.33

    const { createApp } = Vue;
    
    createApp({
      data() {
        return {
          msg: 'hello vue'
        }
      },
    }).mount('#app') 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    今天我们来聊聊上面这段代码中的 createApp 都干了啥呢?

    首先我们通过上一章节:如何调试源码?发现他进入到了 runtime-dom 目录下面的 index.ts 中的 createApp 这个方法中。代码片段如下:

    这里插一句:还提供了 createSSRApp 方法,主要是提供给SSR渲染用的

    export const createApp = ((...args) => {
      // 创建应用app
      const app = ensureRenderer().createApp(...args)
    
      // 先把刚才创建的app应用中的mount方法取出来,然后重写app.mount方法的时候会用得到
      const { mount } = app
    
      app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
        // 规范化容器处理
        const container = normalizeContainer(containerOrSelector)
        if (!container) return
    
        // 取出根节点
        const component = app._component
        // 根节点不是函数并且没有render并且没有template,就把刚才规范化容器的innerHTML塞给根节点的template
        if (!isFunction(component) && !component.render && !component.template) {
          component.template = container.innerHTML
        }
    
        // app mount之前把容器的innerHTML清空,保持干净整洁
        container.innerHTML = ''
    
        // 然后把刚才取出来的mount捡起来继续执行以下, 就是createAppAPI里面的那个mount
        const proxy = mount(container, false, container instanceof SVGElement)
        return proxy
      }
    
      return app
    }) as CreateAppFunction<Element> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    通过上面代码发现createApp通过ensureRenderer().createApp(...args)创建一个app,然后重写app.mount方法,最终把这个app对象返回出去。

    那我们来看看ensureRenderer()干了啥?

    const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
    
    function ensureRenderer() {
      return (
        renderer ||
        (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
      )
    }
    
    export function createRenderer<
      HostNode = RendererNode,
      HostElement = RendererElement
    >(options: RendererOptions<HostNode, HostElement>) {
      return baseCreateRenderer<HostNode, HostElement>(options)
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    其中rendererOptions由 下面两部分合并组成。

    • patchProp: 处理 props、Attribute、class、style、event事件这几个东西
    • nodeOps: 处理 DOM 节点操作

    nodeOps对象包含:insert、remove、createElement、createText、createComment、setText、setElementText、parentNode、nextSibling、querySelector、setScopeId、cloneNode、insertStaticContent。

    然后调用了 createRenderer 这个方法,并且把合并后的rendererOptions当作参数传递进去。

    createRenderer 这个方法又调用了 baseCreateRenderer 的方法,

    baseCreateRenderer这个方法内容就比较多了,大概有接近2000行吧。如下:

    function baseCreateRenderer(options,createHydrationFns) {
      // 核心diff过程
      const patch = () => {}
      // 渲染挂载流程
      const render = (vnode, container, isSVG) => {}
      return {
        render,
        hydrate,  // 服务端渲染用的
        createApp: createAppAPI(render, hydrate)
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从代码代码可以看出baseCreateRenderer最终返回了renderhydratecreateApp

    后面我们针对baseCreateRenderer中那近2000行代码到时候单独来好好聊聊,大家点个关注不迷路,好找回家路

    我们通过上面代码看到createApp是通过createAppAPI(render, hydrate)这个方法实现的。

    createAppAPI代码如下:

    export function createAppAPI<HostElement>( render: RootRenderFunction,
      hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> {
      // 这里的createApp才是真的创建app,里面的一些方法都是我们比较眼熟的吧
      return function createApp(rootComponent, rootProps = null) {
        if (!isFunction(rootComponent)) {
          rootComponent = { ...rootComponent }
        }
    
        // 创建APP默认配置
        const context = createAppContext()
        const installedPlugins = new Set()
    
        let isMounted = false
    
        // 然后这里根据createAppContext()创建的app默认配置一顿各种加工,最后把加工好的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) {},
          use(plugin: Plugin, ...options: any[]) {},
          mixin(mixin: ComponentOptions) {},
          component(name: string, component?: Component): any {},
          directive(name: string, directive?: Directive) {},
          mount(){},
          unmount() {},
          provide(key, value) {
            return app
          }
        })
    
        return app
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    通过上面代码,我们发现createAppAPI主要做以下几件事情:

    • 1、通过createAppContext()创建的app默认配置
    • 2、给刚才创建的app一顿各种加工,最后把加工好的APP返回出去

    最后createApp流程是这样的:

    createApp > ensureRenderer > createRenderer > baseCreateRenderer > createAppApi > mount

    1. 创建web端渲染器
    2. 调用 baseCreateRenderer 这个方法,就是那个2000多行代码的方法。核心diff过程
    3. 调用 createAppApi 这个方法
      • 通过createAppContext()创建的app默认配置
      • 给刚才创建的app一顿各种加工,最后把加工好的APP返回出去
    4. 然后在最开始的createApp方法里面重写 mount 方法
    5. 最终执行挂载操作。

    后面我们接着来聊聊 mount 的过程。

  • 相关阅读:
    English语法_介词 - of
    「津津乐道播客」#389 科技乱炖:技术播客月,我们一起聊聊技术与开源
    MySQL优化not in和不等于
    Python+Requests+Pytest+YAML+Allure实现接口自动化
    Python3 JSON 数据解析
    P2331 [SCOI2005]最大子矩阵(dp)
    Android——gradle插件配置方式——dependencies和plugins
    java中word转pdf/word转图片/word转html/html转word等操作
    vcenter跨版本升级
    什么是MQ和认识RabbitMQ
  • 原文地址:https://blog.csdn.net/pfourfire/article/details/125510244