Vue3源码阅读(二)createApp

CreateApp

  • 重点在于 ensureRenderer
    // rendererProps来自于两部分组成:
        // nodeProps,查看runtime-dom/patchProps.ts,集合了原生dom方法,处理dom
        // patchProps,查看runtime-dom/patchProps.ts,处理元素属性等。
    const rendererOptions = extend({ patchProp }, nodeOps)
    function ensureRenderer() {
        return (
            renderer ||
            (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
        )
    }
    // 在创建应用时调用的方法
    export const createApp = ((...args) => {
        const app = ensureRenderer().createApp(...args);
        // ensureRenderer是一个单例模式的函数,会返回一个renderer,如果无renderer则会调用createRenderer进行获取renderer,获得了一个app实例
        if (__DEV__) {
            // dev环境下注册一个方法isNativeTag,挂载到app.config下面
            injectNativeTagCheck(app);
            injectCompilerOptionsCheck(app);
        }
        // 获取到实例的mount方法并保存下来
        const { mount } = app;
        // 重写实例的mount方法
        app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
            // 调用normalizeContainer获取根元素容器,如果存在直接返回
            const container = normalizeContainer(containerOrSelector);
            if (!container) return;
            // 判断template,获取需要渲染的模板
            const component = app._component;
            if (
                !isFunction(component) &&
                !component.render &&
                !component.template
            ) {
                component.template = container.innerHTML;
                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;
                        }
                    }
                }
            }
            // 把容器的innerHTML置空
            container.innerHTML = '';
            // 调用上面的mount方法
            const proxy = mount(container, false, container instanceof SVGElement);
            // 删除v-cloak属性,添加data-v-app属性
            if (container instanceof Element) {
                container.removeAttribute('v-cloak');
                container.setAttribute('data-v-app', '');
            }
            // 返回mount后的代理
            return proxy;
        };
        return app;
    }) as CreateAppFunction<Element>;
  • 调用 createRenderer
    // renderer.ts createRenderer
    export function createRenderer<HostNode = RendererNode,HostElement = RendererElement>(options:RendererOptions<HostNode, HostElement>) {
        return baseCreateRenderer<HostNode, HostElement>(options)
    }
  • 调用 baseCreateRenderer这个函数简直可以用庞大来形容,vnode diff patch均在这个方法中实现,先查看返回的是什么
    function baseCreateRenderer(
    options: RendererOptions,
    createHydrationFns?: typeof createHydrationFunctions
    ): any {
    if (__ESM_BUNDLER__ && !__TEST__) {
        initFeatureFlags()
    }
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
        const target = getGlobalThis()
        target.__VUE__ = true
        setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
    }
    const {
        insert: hostInsert,
        remove: hostRemove,
        patchProp: hostPatchProp,
        createElement: hostCreateElement,
        createText: hostCreateText,
        createComment: hostCreateComment,
        setText: hostSetText,
        setElementText: hostSetElementText,
        parentNode: hostParentNode,
        nextSibling: hostNextSibling,
        setScopeId: hostSetScopeId = NOOP,
        cloneNode: hostCloneNode,
        insertStaticContent: hostInsertStaticContent
    } = options
    // ...
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    }
    // 最后返回的是render函数,hydrate函数和createApp函数,createApp函数是通过调用createAppAPI来获取到的。
    }
  • 返回的rander传入createAppAPI真正的createApp,调用creatAppContext。
 export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
   // createApp 将若干属性和方法挂载在 app 这个变量中,最后并返回 app。
  return function createApp(rootComponent, rootProps = null) {
    // 对传递进来的第二个参数,也就是rootProps进行校验
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }
    // 创建默认APP配置,调用createAppContext创建appContext对象,赋值给context
    const context = createAppContext()
    // 创建变量installedPlugins(Set类型),存储已经安装过的插件
    const installedPlugins = new Set()
    // isMounted设为false
    let isMounted = false
    // 创建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.`
          )
        }
        // 返回app,此时的app属于Vue的一个准备阶段,为后面的mount等操作准备好了所需要使用到的函数。
        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) {
          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 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.`
          )
        }
        // TypeScript doesn't allow symbols as index type
        // https://github.com/Microsoft/TypeScript/issues/24587
        context.provides[key as string] = value

        return app
      }
    })

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

    return app
  }
}
  • creatAppContext
export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值