vue3源碼學習api-createApp-amount

来源:https://www.cnblogs.com/qqloving/archive/2023/11/04/17809544.html
-Advertisement-
Play Games

vue3 地址 https://github.com/vuejs/core 首先看看vue文檔什麼是 Vue? ​ Vue (發音為 /vjuː/,類似 view) 是一款用於構建用戶界面的 JavaScript 框架。它基於標準 HTML、CSS 和 JavaScript 構建,並提供了一套聲明式 ...


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
  }
test
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 本篇文章主要介紹的關於本人在使用Linux記錄筆記的一些使用方法和經驗,溫馨提示,本文有點長,約1.7w字,幾十張圖片,建議收藏查看。 一、Linux基礎使用 1,伺服器查看及時日誌 tail -500f catalina.out 2,如何退出logs日誌 ctrl+c 或kill -9 pi ...
  • 在使用selenium的時候,通常要部署到伺服器上,這時通常要連接遠程的瀏覽器驅動,並且還可以在任意一臺電腦看到遠程瀏覽器界面,這時就要部署遠程瀏覽器驅動服務與VNC,以下是步驟: 遠程機器下載瀏覽器和對應的驅動 下載selenium-server-standalone.jar,下載地址:selen ...
  • 哈嘍大家好,我是鹹魚 在文章《三劍客之 sed》中鹹魚向大家介紹了文本三劍客中的 sed sed 全名叫 stream editor,流編輯器,用程式的方式來編輯文本 那麼今天鹹魚打算講一下我在用 sed 原地替換文件時遇到的趣事 sed 讓文件屬性變了? 有這麼一個普通文件 test.txt ,內 ...
  • 1,用戶 和 許可權 的基本概念 1.1 ls 擴展 ls -l 1.2 chmod 簡單使用(重要) + 是加許可權, - 是減許可權 chmod 修改文件許可權 chmod 修改目錄許可權: 想要在目錄下執行終端命令,就必須要有可執行許可權。 1.3 超級用戶 2,組管理終端命令 groupadd 組名 : ...
  • 這裡簡單介紹一下如何處理解決Linux平臺下Oracle 19c啟動時,告警日誌出現ORA-00800錯誤的問題,詳情介紹請見下麵內容: 環境描述: 操作系統:Red Hat Enterprise Linux release 8.8 (Ootpa) 資料庫 :19.16.0.0.0 企業版 問題描述 ...
  • 背景 目前單位系統常用 Keycloak 作為認證系統後端,而前端之前寫的也比較隨意,這次用 Vue 3 插件以及 Ref 響應式來編寫這個模塊。另外,這個可能是全網唯一使用 keycloak 的 OIDC 原生更新密碼流的介紹代碼。 設計 依賴庫選擇 OIDC 客戶端,這裡選擇 oidc-clie ...
  • 作者:WangMin 格言:努力做好自己喜歡的每一件事 CSDN原創文章 博客地址 👉 WangMin 我們在開發的過程中,難免會出現一些難以預料的問題。那麼其中,CSS空白現象就是非常常見的問題之一。雖然它已經被髮現很久,但仍然有許多新手和經驗豐富的開發者們忽略了它對頁面佈局的影響。我們一起來看 ...
  • 函數在JS中也是一個對象,它具有其它對象的所有功能,函數中可以存儲代碼,且可以在需要的時候調用這些代碼 函數的操作 函數的定義 函數聲明 function 函數名([參數列表]) { // 函數體 return 返回值; } 函數表達式 const 函數名 = function([參數列表]) { ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...