Vue3源码 您所在的位置:网站首页 猫爪app是干什么的 Vue3源码

Vue3源码

2023-09-19 06:34| 来源: 网络整理| 查看: 265

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」

创建Vue应用实例,是一个项目的开始,这篇文章看看createApp内部都发生了什么。当然如果你对Vue3响应式系统感兴趣,也可以先看看这两篇文章:

Vue3源码 | 深入理解响应式系统上篇-reactive

Vue3源码 | 深入理解响应式系统下篇-effect

VUE3使用

这里看下Vue3是如何进行应用初始化。

// vue3 应用初始化 import { createApp } from 'vue' import App from './app' const app = createApp(App) app.mount('#app') // 组件渲染和未捕获错误配置的处理程序 app.config.errorHandler = (err, vm, info) => {} // 添加全局属性 app.config.globalProperties.$http = () => {} // 这里相当于挂载到Vue2的 Vue.prototype // 指定一种方法识别Vue之外定义的自定义元素 app.config.isCustomElement = tag => tag.startsWith('ion-') // 注册组件 app.component('my-component', {}) // 检索组件 const MyComponent = app.component('my-component') // 注册指令 app.directive('my-directive',{}) // 设置一个可以注入到应用程序内所有组件中的值。组件应使用inject来接收提供的值。 app.provide('user', 'administrator') // 卸载应用程序 app.unmount() // 安装vue插件 import MyPlugin from './plugins/MyPlugin' app.use(MyPlugin) ...

Vue3跟Vue2初始化区别不大,都是创建应用实例,然后挂载到DOM节点上。这里也可以看出,Vue是通过JS渲染页面,跟传统页面DOM直出的方式是不一样。DOM直出,简单说是,请求后返回的HTML页面是最终的呈现效果。

createApp流程 createApp

createApp用于创建应用实例,下面看看内部代码实现:

export const createApp = ((...args) => { // 创建app实例 const app = ensureRenderer().createApp(...args) const { mount } = app // 重写mount方法 app.mount = (containerOrSelector: Element | string): any => { // 标准化容器 const container = normalizeContainer(containerOrSelector) if (!container) return const component = app._component // 组件不存在render函数和模板template,则使用container的innerHTML做为组件模板 if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML } // 挂载前,清空容器的内容 container.innerHTML = '' // 挂载容器 const proxy = mount(container) container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') return proxy } return app }) as CreateAppFunction

通过上面源码解析,我们可以看出createApp主要是干了两件事:

创建app实例,并返回该实例 重写mount方法

看完会存在两个主要疑问,ensureRenderer 是干啥用的?为什么要重写 mount 方法,而不直接使用呢?

ensureRenderer

用于创建渲染器,渲染器核心代码处于 runtime-core/src/renderer.ts 文件。

baseCreateRenderer// 可以看出render存在2种类型的渲染器 let renderer: Renderer | HydrationRenderer // 延迟创建渲染器,当用户只是使用reactive响应库,可以做tree-shaking优化 function ensureRenderer() { return renderer || (renderer = createRenderer(rendererOptions)) } export function createRenderer< HostNode = RendererNode, HostElement = RendererElement >(options: RendererOptions) { return baseCreateRenderer(options) } // HydrationRenderer渲染器,也是只在调用的时候创建,方便做tree-shaking优化 export function createHydrationRenderer( options: RendererOptions ) { return baseCreateRenderer(options, createHydrationFunctions) }

从分析可以看出:

存在2种类型的渲染器,它们都是基于 baseCreateRenderer 函数创建,此函数存在重载。 渲染器都是通过延迟创建,方便不使用的时候做 tree-shaking。 baseCreateRenderer

这个函数是个大家伙,1800行左右的代码,包含了组件渲染的核心逻辑。这里只抽离挂载过程相关逻辑,涉及组件更新等其他渲染逻辑,留到后面再深入研究。

function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { // 其他关于组件渲染逻辑以及hydrate相关这里省略 // 渲染函数 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 } return { render, hydrate, createApp: createAppAPI(render, hydrate) } }

可以看出 baseCreateRenderer 主要实现了:

实现了组件渲染的创建、更新、卸载等核心逻辑(后续解读) 返回渲染函数,以及创建应用实例方法,当然还有 hydrate。 createAppAPI

组件渲染核心部分后面看,这里看看创建实例的API实现。

export function createAppAPI( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction { // createApp接收2个参数,根组件和根组件的属性 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 // 重写上下文对象的app属性 const app: App = (context.app = { _component: rootComponent as Component, _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.` ) } }, // 插件的安装,可以看出,插件如果是对象,必须有install方法;如果是函数,则默认是安装方法 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) } } return app }, component(name: string, component?: PublicAPIComponent): 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): any { if (!isMounted) { // 根据根组件创建虚拟节点 const vnode = createVNode(rootComponent as Component, rootProps) vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer) } } render(vnode, rootContainer) isMounted = true app._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = app return vnode.component!.proxy } }, unmount() { if (isMounted) { render(null, app._container) devtoolsUnmountApp(app) } }, provide(key, value) { context.provides[key as string] = value return app } }) return app } }

从上面的代码,我们可以了解到createAppAPI主要实现了:

创建定义一个实例上下文context,包含属性和方法 重写扩展context.app方法,实现用户可以对上下文相关属性的自定义操作,也就是应用实例暴露的api实现,比如自定义指令、混入mixin、组件等提供用户自定义实现。 根据根组件和属性在 mount 方法中完成虚拟节点 vNode 的转换,并通过 render 喊完成渲染,关于渲染函数在 baseCreateRender 已经说过。 总结

至此分析了createApp大致的流程,内部更细致的实现,后续再根据内容深入分析,这里再总结下整个过程。

执行 createApp 首先会创建渲染器,这里要注意的是存在2种渲染器类型,并且它们都是通过延迟创建的,主要目的是当用户只引用reactive响应式框架的时候,方便进行tree-shaking优化。且两种渲染器都是基于 baseCreateRender 方法来实现。 baseCreateRender 函数执行后会返回 render 渲染函数和 createApp 方法,其中 render 函数是组件创建、更新和卸载的主要核心逻辑实现。createApp则用于创建应用实例,进行应用实例的初始化。 createAppAPI用于生成默认的应用上下文 context,这里定义了应用实例具备的属性和方法,并通过重写扩展 context.app 属性,让用户能够进行对上下文的自定义操作,比如自定义组件、指令、mixin、插件安装等一系列操作。并存在mount方法完成将根组件转为虚拟节点 vNode,并通过render 函数完成对 vNode 的渲染。

下一篇,我们将立足这篇文章,接着深入render函数内部,先看看vNode是什么,再瞧瞧DOM Diff过程。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有