美文网首页
vue3-创建应用createApp

vue3-创建应用createApp

作者: RiverSouthMan | 来源:发表于2020-11-03 11:00 被阅读0次

    先看一下vue-next官方文档的介绍:

    每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的

    传递给 createApp 的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。

    一个应用需要被挂载到一个 DOM 元素中。例如,如果我们想把一个 Vue 应用挂载到<div id="app"></div>,我们应该传递 #app

    我们将分为两部分进行渲染过程的理解:

    • 创建应用实例,函数createApp的剖析
    • 应用实例挂载, 函数mount方法挂载过程

    本篇详细讲述调用方法createApp过程

    image.png

    创建应用实例 createApp

    下面是一个简单的demo

    <!-- template -->
      <div id="app">
        <input v-model="value"/>
        <p>双向绑定:{{value}}</p>
        <hello-comp person-name="zhangsan"/>
      </div>
    
    const { createApp } = Vue
    const helloComp = {
          name: 'hello-comp',
          props: {
            personName: {
              type: String,
              default: 'wangcong'
            }
          },
          template: '<p>hello {{personName}}!</p>'
        }
    const app = {
      data() {
        return {
          value: '',
          info: {
            name: 'tom',
            age: 18
          }
        }
      },
      components: {
        'hello-comp': helloComp
      },
      mounted() {
        console.log(this.value, this.info)
      },
    }
    createApp(app).mount('#app')
    

    现在我们从createApp函数为入口,去了解应用创建的过程。

    查看官方文档和上面的例子我们可以知道,createApp方法接收的是根组件对象作为参数,并返回了一个有mount方法的应用实例对象。

    按照依赖关系可以找到createApp方法出自packages/runtime-dom/src/index.ts

    export const createApp = ((...args) => {
      const app = ensureRenderer().createApp(...args)
    
      if (__DEV__) {
        injectNativeTagCheck(app)
      }
    
      const { mount } = app
      app.mount = (containerOrSelector: Element | string): any => {
        const container = normalizeContainer(containerOrSelector)
        if (!container) return
        const component = app._component
        if (!isFunction(component) && !component.render && !component.template) {
          component.template = container.innerHTML
        }
        // clear content before mounting
        container.innerHTML = ''
        const proxy = mount(container)
        container.removeAttribute('v-cloak')
        container.setAttribute('data-v-app', '')
        return proxy
      }
    
      return app
    }) as CreateAppFunction<Element>
    

    这里做了两件事情:

    • 创建app应用实例: ensureRenderer().createApp(...args)
    • 重写了app.mount方法。document.querySelector方法获取HTMLElement对象作为参数传入原mount方法。该部分会在mount段落详细讲解。
    image.png

    ensureRenderer

    ensureRenderer函数的目的是惰性创建renderer对象,这样做目的是在用户只引入reactivity模块时,对renderer核心逻辑部分可以进行tree-shake。

    renderer对象包含三个属性:
    render方法、 hydrate( ssr客户端激活相关)、createApp方法

    renderer对象实际上是方法createRenderer函数返回的。

    // nodeOps: dom节点增删改查操作的原生api
    const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
    
    let renderer: Renderer<Element> | HydrationRenderer
    function ensureRenderer() {
      return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
    }
    

    createRenderer

    这个方法在packages/runtime-core/src/renderer.ts中定义。

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

    createRenderer方法接受两个通用的类型参数HostNodeHostElement。其目的是在自定义渲染器中可以传入特定于平台的类型;

    例如:
    对于浏览器环境runtime-domHostNode将是DOM Node接口;HostElement将是DOM Element接口。

    Element继承了Node类,也就是说Element是Node多种类型中的一种,即当NodeType为1时Node即为ElementNode,另外Element扩展了Node,Element拥有id、class、children等属性。

    baseCreateRenderer

    这个方法也在packages/runtime-core/src/renderer.ts中定义
    该方法比较长,暂时忽略中间代码。该函数执行返回了一个对象(上文中的renderer对象):

    function baseCreateRenderer(
      options: RendererOptions,
      createHydrationFns?: typeof createHydrationFunctions
    ): any {
      ...
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
      }
    }
    
    • render:接受两个参数VNodeElement
    const render: RootRenderFunction = (vnode, container) => {
        if (vnode == null) {
          if (container._vnode) {
            unmount(container._vnode, null, null, true)
          }
        } else {
          patch(container._vnode || null, vnode, container)
        }
        flushPostFlushCbs()
        container._vnode = vnode
      }
    
    • hydrate:与ssr客户端激活相关
    • createApp:接收方法createAppAPI(render, hydrate)的返回值

    createAppAPI

    这个方法在packages/runtime-core/src/apiCreateApp.ts中定义。

    在这里createAppAPI的返回结果是一个函数createApp,这里终于找到了demo中调用的那个接受跟组件对象的createApp函数。

    createApp返回了应用实例app对象,其中包含了我们比较熟悉的一些方法,例如:mixincomponentdirective等;

    export function createAppAPI<HostElement>(
      render: RootRenderFunction,
      hydrate?: RootHydrateFunction
    ): CreateAppFunction<HostElement> {
      return function createApp(rootComponent, rootProps = null) {
        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,
    
          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
          },
    
          // dom挂载
          mount(rootContainer: HostElement, isHydrate?: boolean): any {
            ...
            return app
          },
    
          // 卸载
          unmount() {
            if (isMounted) {
              render(null, app._container)
              if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
                devtoolsUnmountApp(app)
              }
            } else if (__DEV__) {
              warn(`Cannot unmount an app that is not mounted.`)
            }
          },
    
          // 注入
          provide(key, value) {
            ...
            return app
          }
        }
    
        return app
      }
    }
    
    

    下面就是在应用实例app还没有调用mount方法进行挂载前的属性:

    image.png

    这里强调一下app._component引用的就是我们传入createApp的根组件对象

    对比

    2.x global API:

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.ignoredElements = [/^app-/]
    Vue.use(/* ... */)
    Vue.mixin(/* ... */)
    Vue.component(/* ... */)
    Vue.directive(/* ... */)
    
    Vue.prototype.customProperty = () => {}
    
    new Vue({
      render: h => h(App)
    }).$mount('#app')
    

    从技术上讲,Vue 2没有“应用”的概念。我们定义为应用的只是通过创建的根Vue实例new Vue()。从同一Vue构造函数创建的每个根实例都共享相同的全局配置。

    Vue当前的某些全局API和配置会永久更改全局状态。这会导致一些问题:

    • 全局配置更容易使测试过程中意外污染其他测试案例
    • 影响每一个根实例
    Vue.mixin({ /* ... */ })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    

    vue3 应用程序实例

    createApp返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。该上下文提供了先前在Vue 2.x中“全局”的配置。该实例不会被应用于其他实例的任何全局配置所污染。共享实例属性应附加到应用程序实例的config.globalProperties

    import { createApp } from 'vue'
    import App from './App.vue'
    
    const app = createApp(App)
    
    app.config.isCustomElement = tag => tag.startsWith('app-')
    app.use(/* ... */)
    app.mixin(/* ... */)
    app.component(/* ... */)
    app.directive(/* ... */)
    app.provide(/* ... */)
    
    app.config.globalProperties.customProperty = () => {}
    
    app.mount(App, '#app')
    

    相关文章

      网友评论

          本文标题:vue3-创建应用createApp

          本文链接:https://www.haomeiwen.com/subject/fyjpmktx.html