vue3源码学习api-createApp-amount

  • vue3源码学习api-createApp-amount已关闭评论
  • 101 次浏览
  • A+
所属分类:Web前端
摘要

vue3 地址 https://github.com/vuejs/core​
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

vue3 地址 https://github.com/vuejs/core

首先看看vue文档什么是 Vue?


Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

下面是一个最基本的示例:

import { createApp, ref } from 'vue'  createApp({   setup() {     return {       count: ref(0)     }   } }).mount('#app') 

学习vue 开发 都是从这个例子开始学习,在这个例子中涉及了这些api

  • 1 createApp
  • 2 mount
  • 3 ref
  • 4 setup

其中 ref 属于reactivity:反应系统 就暂时先不深究了

setup 属于vue3 新的语法糖 也先不深究了
就先看看最简单的createApp 和mount

createApp

先看看vue 仓库中的packages/vue/src/index.ts
地址 https://github.com/vuejs/core/blob/main/packages/vue/src/index.ts

export { compileToFunction as compile } export * from '@vue/runtime-dom'  

可以看到到处了一个编译的方法 和 @vue/runtime-dom 中的方法
根据上文
runtime-dom:针对浏览器的运行时。包括原生 DOM API、属性、属性、事件处理程序等的处理。
在runtime-dom 包中找到相关的方法
地址 https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/index.ts

export const createApp = ((...args) => {   const app = ensureRenderer().createApp(...args)    if (__DEV__) {     injectNativeTagCheck(app)     injectCompilerOptionsCheck(app)   }    const { mount } = app   app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {     const container = normalizeContainer(containerOrSelector)     if (!container) return      const component = app._component     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', '')     }     return proxy   }    return app }) as CreateAppFunction<Element>   function ensureRenderer() {   return (     renderer ||     (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))   ) }  

走查代码可以发现 选是创建了一个渲染器Renderer
然后调用了渲染器的方法 createApp
查看具体方法

地址 https://github.com/vuejs/core/blob/main/packages/runtime-core/src/renderer.ts

创建渲染器的方法是一个比较长的方法
里面的很多方法看名称,更多的设计对dom的操作,不过我们还是先关注createApp 干了些什么

function baseCreateRenderer(   options: RendererOptions,   createHydrationFns?: typeof createHydrationFunctions ): any {     ......   return {     render,     hydrate,     createApp: createAppAPI(render, hydrate)   } } 

发现createAppAPI 来自apiCreateApp 文件

地址 https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiCreateApp.ts

import { createAppAPI, CreateAppFunction } from './apiCreateApp' 

代码如下

export function createAppAPI<HostElement>(   render: RootRenderFunction<HostElement>,   hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> {   return function createApp(rootComponent, rootProps = null) {     if (!isFunction(rootComponent)) {       rootComponent = extend({}, rootComponent)     }      if (rootProps != null && !isObject(rootProps)) {       __DEV__ && warn(`root props passed to app.mount() must be an object.`)       rootProps = null     }      const context = createAppContext()      // TODO remove in 3.4     if (__DEV__) {       Object.defineProperty(context.config, 'unwrapInjectedRef', {         get() {           return true         },         set() {           warn(             `app.config.unwrapInjectedRef has been deprecated. ` +               `3.3 now always unwraps injected refs in Options API.`           )         }       })     }      const installedPlugins = new WeakSet()      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[]) {         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.`           )         }         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) {           // #5571           if (__DEV__ && (rootContainer as any).__vue_app__) {             warn(               `There is already an app instance mounted on the host container.n` +                 ` If you want to mount another app on the same host container,` +                 ` you need to unmount the previous app by calling `app.unmount()` first.`             )           }           const vnode = createVNode(rootComponent, 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 getExposeProxy(vnode.component!) || 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.`           )         }          context.provides[key as string | symbol] = value          return app       },        runWithContext(fn) {         currentApp = app         try {           return fn()         } finally {           currentApp = null         }       }     })      if (__COMPAT__) {       installAppCompatProperties(app, context, render)     }      return app   } } 

可以发现createApp 的第一个参数是rootComponent
需要传递的是一个组件,作为根组件
第二个参数rootProps是这个给这个组件传递的参数

通过走查 文件可以发现一些常用的api 也是出现在这里
例如 use、mixin、component、directive、mount、unmount、provide

我们要找的mount 也是对这里mount的调用

mount

可以看到 mount主要是参数是rootContainer 另外两个是可选参数
在通过createVNode 创建一个vnode 之后
调用getExposeProxy
返回当前刚才创建的vnode的代理

export function getExposeProxy(instance: ComponentInternalInstance) {   if (instance.exposed) {     return (       instance.exposeProxy ||       (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {         get(target, key: string) {           if (key in target) {             return target[key]           } else if (key in publicPropertiesMap) {             return publicPropertiesMap[key](instance)           }         },         has(target, key: string) {           return key in target || key in publicPropertiesMap         }       }))     )   } } 

代码中实际调用的mount

依然在createAppAPI 里面 这里的传参更友好 了可以传入Selector 方便选择dom节点

app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {     const container = normalizeContainer(containerOrSelector)     if (!container) return      const component = app._component     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', '')     }     return proxy   }