Skip to content

main.ts

打开到项目的主文件main.ts,可以看到

js
    import App from './App.vue'
    console.log(App);
    /* 
      打印App组件,可以看到组件实例有两个方法,render渲染函数 和 setup(暂不深究)

      {__name: 'App', __hmrId: '7a7a37b1', __file: '/Users/kirk/Desktop/project/vue_project/core/vue3/debug/vue-deug/src/App.vue', setup: ƒ, render: ƒ}
      render: ƒ _sfc_render(_ctx, _cache, $props, $setup, $data, $options)
      setup: setup(__props, { expose: __expose }) { __expose(); const count = ref(0); const add = () => {…}
      __file: "/Users/kirk/Desktop/project/vue_project/core/vue3/debug/vue-deug/src/App.vue"
      __hmrId: "7a7a37b1"
      __name:  "App"
      [[Prototype]]: Object
    */
    createApp(App).mount('#app')
  • 进入源码,按住键盘command/control ,点击createApp,可以进入定义 createApp 的源文件 /packages/runtime-dom/src/index.ts
ts
  export const createApp = ((...args) => {

  // ensureRenderer函数 返回一个renderer 渲染器
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    if (__DEV__) {
    // 检查是否原生标签
    injectNativeTagCheck(app)
    // 检查是否自定义标签
    injectCompilerOptionsCheck(app)
  }
  }
  
  // 缓存原始mount方法
  const { mount } = app
  // 重写mount方法
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {

    // 获取mount所挂载的dom
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    // 存储的App根组件
    const component = app._component
    // 检查根组件是否存在template,否则就使用 所挂载dom的 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.
      /* 
       __UNSAFE__
      /原因:DOM模板中可能执行JS表达式。用户必须确保DOM中的模板是可信的。如果由服务器呈现,则模板不应包含任何用户数据。
      */
      component.template = container.innerHTML
      // 2.x compat check
      /*  __COMPAT__ 是启动的时候通过rollup去注入进去的
         用来判断是否向下兼容 */
      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 = ''

    // 使用原始mount方法挂载
    const proxy = mount(container, false, resolveRootNamespace(container))
    if (container instanceof Element) {

      // vue2的话,会给#app设置一个v-cloak属性,在render的时候清空掉
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }
 
  // 返回app实例
  return app
}) as CreateAppFunction<Element>
  • ensureRenderer 经过一系列参数处理 指向了 baseCreateRenderer方法
js
baseCreateRenderer(){
  // ...
  // 返回渲染器的属性
  return {
    render, // 渲染h函数
    hydrate, //SSR相关
    createApp: createAppAPI(render, hydrate), // 返回app对象并携带上下文
  }
}
  • createAppAPI, // 返回app对象并携带上下文
ts
  export interface App<HostElement = any> {
  version: string
  config: AppConfig

  use<Options extends unknown[]>(
    plugin: Plugin<Options>,
    ...options: Options
  ): this
  use<Options>(plugin: Plugin<Options>, options: Options): this

  mixin(mixin: ComponentOptions): this
  component(name: string): Component | undefined
  component(name: string, component: Component | DefineComponent): this
  directive<T = any, V = any>(name: string): Directive<T, V> | undefined
  directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
  mount(
    rootContainer: HostElement | string,
    isHydrate?: boolean,
    namespace?: boolean | ElementNamespace,
  ): ComponentPublicInstance
  unmount(): void
  provide<T>(key: InjectionKey<T> | string, value: T): this

  /**
   * Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
   * to variables provided via `app.provide()`.
   *
   * @param fn - function to run with the app as active instance
   */
  runWithContext<T>(fn: () => T): T

  // internal, but we need to expose these for the server-renderer and devtools
  _uid: number
  _component: ConcreteComponent
  _props: Data | null
  _container: HostElement | null
  _context: AppContext
  _instance: ComponentInternalInstance | null

  /**
   * v2 compat only
   */
  filter?(name: string): Function | undefined
  filter?(name: string, filter: Function): this

  /**
   * @internal v3 compat only
   */
  _createRoot?(options: ComponentOptions): ComponentPublicInstance
}
  • 总结 vue3 的 createApp 方法首先创建了一个renderer渲染器,并调用渲染器的 createApp方法创建了app对象,app对象携带了上下文context和一些属性方法 之后缓存了mount方法, 然后又重写了app.mount,并调用normalizeContainer校验挂载元素,临时保存了需要渲染的内容。接下来并对vue2的写法做了兼容处理,最终挂载App,完成第一次渲染