天天看点

vue3源码分析之createApp

一、入口函数

​ 第一步:创建

app

实例,调用

ensureRenderer()

方法,在这个方法中判断了是否有

render

渲染器,如果没有的话调用

createRenderer<Node, Element | ShadowRoot>(rendererOptions))

方法创建

function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}
           

​ 在

createRenderer

方法中调用

baseCreateRenderer

方法

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
           

baseCreateRenderer

方法是这里最核心的方法,这段代码有两千多行,只解读核心部分,调用了

createAppAPI

,返回实力的方法,例如: mount mixin provide 等方法

function baseCreateRenderer(
      options: RendererOptions,
      createHydrationFns?: typeof createHydrationFunctions
  ): any {
      /**
      **
      ** 
      **
      */
      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)
        }
        flushPreFlushCbs()
        flushPostFlushCbs()
        container._vnode = vnode
      }
      let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
      }
}
           

createAppAPI

方法返回的实例:

export function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  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

    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[]) {
          return app
      },
      mixin(mixin: ComponentOptions) {
          return app
      },
      component(name: string, component?: Component): any {
          return app
      },
      directive(name: string, directive?: Directive) {
          return app
      },
      mount {
          
      },
      unmount() {
          
      },
      provide(key, value) {
          return app
      }
    })

    if (__COMPAT__) {
      installAppCompatProperties(app, context, render)
    }
    
    return app
  }
}
           

​ 第二步:调用

ensureRenderer

里返回的

createApp

方法,最终返回一系列方法,实例化给

app

​ 第三步:重写

mount

方法

​ 第四步:清空容器,在重写的

mount

方法中去创建

proxy

实例,最终返回的是一个

vnode

export const createApp = ((...args) => {
  // 创建了 render 渲染器,和创建了 app 实例,最终返回 app 应用实例 createAppAPI 的方法包括 mount mixin provide 等方法
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    injectNativeTagCheck(app)
    injectCompilerOptionsCheck(app)
  }

  const { mount } = app
  // 重写 app 的 mount 方法
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 返回 containerOrSelector 对应的容器
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    const component = app._component
      // 组件不存在render函数和模板template,则使用container的innerHTML做为组件模板
    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.
      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.innerHTML = ''
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    // 返回的是个 vnode
    return proxy
  }

  return app
}) as CreateAppFunction<Element>
           

继续阅读