您好,登錄后才能下訂單哦!
本篇內容主要講解“Vue 3.0進階之應用創建的方法過程”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue 3.0進階之應用創建的方法過程”吧!
接下來,我們將從一個簡單的例子出發,從頭開始一步步分析 Vue 3.0 應用創建的過程。
<div id="app"></div> <script> const { createApp, h } = Vue const app = createApp({ // ① data() { return { name: '我是阿寶哥' } }, template: `<div>大家好, {{name}}!</div>` }) app.mount('#app') // ② </script>
在以上代碼中,首先我們通過 createApp 函數創建 app 對象,然后調用 app.mount 方法執行應用掛載操作。當以上代碼成功運行后,頁面上會顯示 大家好,我是阿寶哥!,具體如下圖所示:
對于以上的示例來說,它主要包含兩個步驟:創建 app 對象和應用掛載。這里我們只分析創建 app 對象的過程,而應用掛載的過程將在下一篇文章中介紹。
一、創建 app 對象
首先,阿寶哥利用 Chrome 開發者工具的 Performance 標簽欄,記錄了創建 app 對象的主要過程:
從圖中我們看到了在創建 app 對象過程中,所涉及的相關函數。為了讓大家能直觀地了解 app 對象創建的過程,阿寶哥畫了一張圖:
大致了解了主要過程之后,我們從 createApp 這個入口開始分析。接下來,打開 Chrome 開發者工具,在 createApp 處加個斷點:
通過斷點,我們找到了 createApp 函數,調用該函數之后會返回一個提供應用上下文的應用實例。應用實例掛載的整個組件樹共享同一個上下文。createApp 函數被定義在 runtime-dom/src/index.ts 文件中:
// packages/runtime-dom/src/index.ts export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) const { mount } = app app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { // 省略mount內部的處理邏輯 } return app }) as CreateAppFunction<Element>
在 createApp 內部,會先調用 ensureRenderer 函數,該函數的內部代碼很簡單:
// packages/runtime-dom/src/index.ts function ensureRenderer() { return renderer || (renderer = createRenderer<Node, Element>(rendererOptions)) }
在以上代碼中會延遲創建渲染器,那么為什么要這樣做呢?我們從 runtime-dom/src/index.ts 文件中的注釋,找到了答案:
// lazy create the renderer - this makes core renderer logic tree-shakable // in case the user only imports reactivity utilities from Vue.
對于我們的示例來說,是需要使用到渲染器的,所以會調用 createRenderer 函數創建渲染器。在分析 createRenderer 函數前,我們先來分析一下它的參數rendererOptions:
// packages/runtime-dom/src/index.ts export const extend = Object.assign const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
由以上代碼可知,參數 rendererOptions 是一個包含 patchProp、forcePatchProp 等屬性的對象,其中 nodeOps 是 node operations 的縮寫。對于 Web 瀏覽器環境來說,它定義了操作節點/元素的 API,比如創建元素、創建文本節點、插入元素和刪除元素等。因為 Vue 3.0 的源碼是使用 TypeScript 編寫的,所以可以在源碼中找到rendererOptions 參數的類型定義:
// packages/runtime-core/src/renderer.ts export interface RendererOptions< HostNode = RendererNode, HostElement = RendererElement > { patchProp(el: HostElement, key: string, prevValue: any, nextValue: any, ...): void forcePatchProp?(el: HostElement, key: string): boolean insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void remove(el: HostNode): void createElement( type: string, isSVG?: boolean, isCustomizedBuiltIn?: string): HostElement createText(text: string): HostNode createComment(text: string): HostNode setText(node: HostNode, text: string): void setElementText(node: HostElement, text: string): void parentNode(node: HostNode): HostElement | null nextSibling(node: HostNode): HostNode | null querySelector?(selector: string): HostElement | null setScopeId?(el: HostElement, id: string): void cloneNode?(node: HostNode): HostNode insertStaticContent?(content: string, parent: HostElement, ...): HostElement[] }
在 RendererOptions 接口中定義了與渲染器相關的所有方法,這樣做的目的是對渲染器做了一層抽象。開發者在滿足該接口約束的情況下,就可以根據自己的需求實現自定義渲染器。了解完 rendererOptions 參數,我們來介紹 createRenderer 函數:
// packages/runtime-core/src/renderer.ts export interface RendererNode { [key: string]: any // 索引簽名 } export interface RendererElement extends RendererNode {} export function createRenderer< HostNode = RendererNode, HostElement = RendererElement >(options: RendererOptions<HostNode, HostElement>) { return baseCreateRenderer<HostNode, HostElement>(options) }
在 createRenderer 函數內部會繼續調用 baseCreateRenderer 函數來執行創建渲染器的邏輯,該函數內部的邏輯比較復雜,這里我們先來看一下調用該函數后的返回結果:
// packages/runtime-core/src/renderer.ts function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { // 省略大部分代碼 return { render, hydrate, createApp: createAppAPI(render, hydrate) } }
在以上代碼中,我們終于看到了期待已久的 createApp 屬性,該屬性的值是調用 createAppAPI 函數后的返回結果。看過阿寶哥之前文章的小伙伴,對 createAppAPI 函數應該不會陌生,它被定義在 runtime-core/src/apiCreateApp.ts 文件中:
// packages/runtime-core/src/apiCreateApp.ts export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { const context = createAppContext() const installedPlugins = new Set() let isMounted = false const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _context: context, // 省略use、mixin、unmount和provide等方法 component(name: string, component?: Component): any { // ... }, directive(name: string, directive?: Directive) { // ... }, mount(rootContainer: HostElement, isHydrate?: boolean): any { // ... }, }) return app } }
通過以上的代碼可知,createApp 方法支持 rootComponent 和 rootProps 兩個參數,調用該方法之后會返回一個 app 對象,該對象為了開發者提供了多個應用 API,比如,用于注冊或檢索全局組件的 component 方法,用于注冊或檢索全局指令的 directive方法及用于將應用實例的根組件掛載到指定 DOM 元素上的 mount 方法等。
此外,在 createApp 函數體中,我們看到了 const context = createAppContext() 這行代碼。顧名思義,createAppContext 函數用于創建與當前應用相關的上下文對象。那么所謂的上下文對象長啥樣呢?要搞清楚這個問題,我們來看一下 createAppContext 函數的具體實現:
// packages/runtime-core/src/apiCreateApp.ts export function createAppContext(): AppContext { return { app: null as any, config: { ... }, mixins: [], components: {}, directives: {}, provides: Object.create(null) } }
介紹完 app 和 context 對象之后,我們來繼續分析 createApp 函數剩下的邏輯代碼:
// packages/runtime-dom/src/index.ts export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) const { mount } = app app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { // 省略mount內部的處理邏輯 } return app }) as CreateAppFunction<Element>
由以上代碼可知,在創建完 app 對象之后,并不會立即返回已創建的 app 對象,而是會重寫 app.mount 屬性:
// packages/runtime-dom/src/index.ts export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) const { mount } = app app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { const container = normalizeContainer(containerOrSelector) // 同時支持字符串和DOM對象 if (!container) return const component = app._component // 若根組件非函數對象且未設置render和template屬性,則使用容器的innerHTML作為模板的內容 if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML } container.innerHTML = '' // 在掛載前清空容器內容 const proxy = mount(container) // 執行掛載操作 if (container instanceof Element) { container.removeAttribute('v-cloak') // 避免在網絡不好或加載數據過大的情況下,頁面渲染的過程中會出現Mustache標簽 container.setAttribute('data-v-app', '') } return proxy } return app }) as CreateAppFunction<Element>
在 app.mount 方法內部,當設置好根組件的相關信息之后,就會調用 app 對象原始的mount 方法執行掛載操作:
// packages/runtime-core/src/apiCreateApp.ts export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, 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, _context: context, mount(rootContainer: HostElement, isHydrate?: boolean): any { if (!isMounted) { // 基于根組件和根組件屬性創建對應的VNode節點 const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context // 應用上下文 if (isHydrate && hydrate) { // 與服務端渲染相關 hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { // 把vnode渲染到根容器中 render(vnode, rootContainer) } isMounted = true // 設置已掛載的狀態 app._container = rootContainer return vnode.component!.proxy } }, }) return app } }
那么為什么要重寫 app.mount 方法呢?原因是為了支持跨平臺,在 runtime-dom 包中定義的 app.mount 方法,都是與 Web 平臺有關的方法。另外,在 runtime-dom 包中,還會為 Web 平臺創建該平臺對應的渲染器。即在創建渲染器時,使用的 nodeOps 對象中封裝了 DOM 相關的 API:
// packages/runtime-dom/src/nodeOps.ts export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = { // 省略部分方法 createElement: (tag, isSVG, is): Element => isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag, is ? { is } : undefined), createText: text => doc.createTextNode(text), createComment: text => doc.createComment(text), querySelector: selector => doc.querySelector(selector), }
現在創建 app 對象的過程中涉及的主要函數已經介紹完了,對這個過程還不理解的小伙伴,可以參考阿寶哥前面畫的圖,然后斷點調試一下創建 app 對象的過程。
二、阿寶哥有話說
2.1 App 對象提供哪些 API?
在 Vue 3 中,改變全局 Vue 行為的 API 現在被移動到了由新的 createApp 方法所創建的應用實例上。應用實例為我們提供了以下 API 來實現特定的功能:
config():包含應用配置的對象。
unmount():在提供的 DOM 元素上卸載應用實例的根組件。
mixin(mixin: ComponentOptions):將一個 mixin 應用在整個應用范圍內。
provide(key, value):設置一個可以被注入到應用范圍內所有組件中的值。
component(name: string, component?: Component):注冊或檢索全局組件。
directive(name: string, directive?: Directive):注冊或檢索全局指令。
use(plugin: Plugin, ...options: any[]):安裝 Vue.js 插件,當在同一個插件上多次調用此方法時,該插件將僅安裝一次。
mount(rootContainer: HostElement, isHydrate?: boolean,isSVG?: boolean):將應用實例的根組件掛載在提供的 DOM 元素上。
2.2 使用 createApp 函數可以創建多個 Vue 應用么?
通過 createApp 函數,我們可以輕松地創建多個 Vue 應用。每個應用的上下文環境都是互相隔離的,具體的使用方式如下所示:
<div id="appA"></div> <hr> <div id="appB"></div> <script> const { createApp, h } = Vue const appA = createApp({ template: "我是應用A" }) const appB = createApp({ template: "我是應用B" }) appA.mount('#appA') appB.mount('#appB') </script>
到此,相信大家對“Vue 3.0進階之應用創建的方法過程”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。