本篇内容主要讲解“Vue 3.0进阶之应用创建的方法过程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue 3.0进阶之应用创建的方法过程”吧!接下来,我们将
本篇内容主要讲解“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进阶之应用创建的方法过程”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
--结束END--
本文标题: Vue 3.0进阶之应用创建的方法过程
本文链接: https://lsjlt.com/news/83438.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0