Vue3源码【三】—— createApp执行流程源码分析

使用

在vue3当中是通过createApp将页面给挂在到index.html文件根元素下。下面是一个使用的例子,那么他是怎么运行的呢?又是怎么做的一个链式调用呢?下面就来一一分析

createApp(App)  // APP是一个vue组件.use(ElementPlus, { // 注册El-plus组件locale: zhCn,size: 'small',zIndex: 3000,}).use(router)       // 注册Vue-Router.use(ContextMenu)   // 一个右键菜单组件.use(createPinia()) // 注册pinia.mixin(drawMixin)  // 混入.provide('M', '1')  // .directive('color', (el, binding) => {     // 添加自定义指令el.style.color = binding.value}).directive('load', loadingDirective).use(i18nPlugin, {greetings: {hello: '你好!'}}).mount("#app");    // 挂载

createAppAPI

源码位于packages/runtime-core/src/apiCreateApp.ts

  • 这里进行了一些简化,可以看到在创建应用程序时,会创建一个App对象,然后返回一个createApp函数,这个函数接收两个参数,一个是根组件,一个是根组件的属性。
  • 在得到app实例之后,他里面有很多方法,比如use、mixin、component、directive等。他每一个方法都会返回app,这也就是上面可以使用链式调用的原因
  • 最后就是一个mount方法,这个方法接收一个参数,一个是挂载的元素。将根组件挂载到挂载元素上。这样就完成了整个vue3的初始化流程。
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)) {// 传递给app.mount必须是一个对象rootProps = null}const context = createAppContext()const installedPlugins = new WeakSet()let isMounted = falseconst app: App = (context.app = {_uid: uid++,_component: rootComponent as ConcreteComponent,_props: rootProps,_container: null,_context: context,_instance: null,version,use(plugin: Plugin, ...options: any[]) {},mixin(mixin: ComponentOptions) {},component(name: string, component?: Component): any {},directive(name: string, directive?: Directive) {},mount(rootContainer: HostElement,isHydrate?: boolean,namespace?: boolean | ElementNamespace,): any {},unmount() {},provide(key, value) {},runWithContext(fn) {},})if (__COMPAT__) {installAppCompatProperties(app, context, render)}return app}
}

use 使用组件库

先看一下app的use方法的源码。这里就是看使用use方法传递过来的组件当中有没有install方法,如果有的话,就执行install方法,如果没有的话,就执行组件本身。

const app: App = (context.app = {use(plugin: Plugin, ...options: any[]) {if (installedPlugins.has(plugin)) {// 插件已被应用} else if (plugin && isFunction(plugin.install)) {// 判断组件当中的install是不是一个函数installedPlugins.add(plugin);plugin.install(app, ...options);} else if (isFunction(plugin)) {// 看组件是不是函数installedPlugins.add(plugin);plugin(app, ...options);}return app;}
});

顺便看一下el-plus的install方法。就是去执行makeInstaller.install方法,遍历el-plus当中的组件通过app.use©进行全局注册。
app是vue3当中创建app对象,然后options是use时传递的配置,会将所有的配置都通过provideGlobalConfig弄成全局的provide。后面我们再看provide的原理

var installer = makeInstaller([...Components, ...Plugins]);const makeInstaller = (components = []) => {const install = (app, options) => {if (app[INSTALLED_KEY])return;app[INSTALLED_KEY] = true;components.forEach((c) => app.use(c));if (options)provideGlobalConfig(options, app, true);};return {version,install};
};

Provide & Inject

全局Provide

在给app添加provide方法时,其实做的就是将key和value添加到provides对象当中。这些都在根元素上。

const app: App = (context.app = {provide(key, value) {context.provides[key as string | symbol] = value;return app;}
});

Provide源码分析

先看看provide的源码。源码位于:packages/runtime-core/src/apiInject.ts

在使用provide的时候,会将key和value添加到provides对象当中。这个是到父级别的元素上。
在创建组件实例的时候(createComponentInstance),也会将provides对象添加到父组件实例上。
这也就是在兄弟组件之间去取值时能取到的原因,会从父组件实例的provides对象中取值。

export function provide<T, K = InjectionKey<T> | string | number>(key: K,value: K extends InjectionKey<infer V> ? V : T
) {if (!currentInstance) {// 组件实例不存在,provide只能在setup函数中调用} else {// 数据都会存到组件实例上 provideslet provides = currentInstance.provides;// 默认情况下,实例继承其父对象的provides对象。但当它需要提供自己的价值观时,它会创建own提供对象使用parent提供对象作为原型。// 通过这种方式,在inject中,我们可以简单地从direct中查找注射parent并让原型链来完成工作。const parentProvides =currentInstance.parent && currentInstance.parent.provides;if (parentProvides === provides) {provides = currentInstance.provides = Object.create(parentProvides);}provides[key as string] = value;}
}// 组件实例
export function createComponentInstance(vnode: VNode,parent: ComponentInternalInstance | null,suspense: SuspenseBoundary | null,
) {const type = vnode.type as ConcreteComponentconst appContext =(parent ? parent.appContext : vnode.appContext) || emptyAppContextconst instance: ComponentInternalInstance = {// 还有别的属性 这里先都不看provides: parent ? parent.provides : Object.create(appContext.provides),}return instance
}

inject源码分析

inject的作用是注入数据,该数据来自于它的祖先组件 provide方法提供的数据

如果在一个组件中使用 inject(key, ‘a’)方法,那么它会先从其父组件的 provides对象本身去查找这个 key,如果找到了就返回对应的数据,如果没有找到,则通过
provides的原型去查找这个 key,此时的 provides的原型指向的就是它的父级 provides对象。实际上,inject查找数据的方法其实就是利用了js中原型链查找方式。

export function inject(key: InjectionKey<any> | string,defaultValue?: unknown,treatDefaultAsFactory = false
) {// 回退到 currentRenderingInstance。以便可以在中调用const instance = currentInstance || currentRenderingInstance;if (instance || currentApp) {// 如果实例位于根目录,则回退到appContext的providesconst provides = instance? instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides: instance.parent.provides: currentApp!._context.provides;if (provides && (key as string | symbol) in provides) {return provides[key as string];} else if (arguments.length > 1) {return treatDefaultAsFactory && isFunction(defaultValue)? defaultValue.call(instance && instance.proxy): defaultValue;}}
}

mount 挂载

  • 先检查是否已经挂载,再创建一个虚拟节点vnode,并且设置context上下文
  • 根据namespace设置命名空间
  • 根据isHydrate参数的值决定是使用hydrate函数还是render函数来将虚拟节点渲染到根容器中
  • 最后返回vnode的组件代理
const app: App = (context.app = {mount(rootContainer: HostElement,isHydrate?: boolean,namespace?: boolean | ElementNamespace,): any {if (!isMounted) {if (__DEV__ && (rootContainer as any).__vue_app__) {// 已经挂载了一个app,需要先执行unmount卸载之后再挂载}const vnode = createVNode(rootComponent, rootProps)vnode.appContext = contextif (namespace === true) {namespace = 'svg'} else if (namespace === false) {namespace = undefined}// HMR root reloadif (__DEV__) {context.reload = () => {render(cloneVNode(vnode),rootContainer,namespace as ElementNamespace,)}}if (isHydrate && hydrate) {hydrate(vnode as VNode<Node, Element>, rootContainer as any)} else {render(vnode, rootContainer, namespace)}isMounted = trueapp._container = rootContainer;(rootContainer as any).__vue_app__ = appif (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {app._instance = vnode.componentdevtoolsInitApp(app, version)}return getExposeProxy(vnode.component!) || vnode.component!.proxy}},
})

unmount卸载

该函数用于卸载一个Vue应用。先检查应用是否已挂载,如果是,则通过render函数将应用的组件渲染为null,从而从DOM中移除应用同时删除应用容器上的Vue应用引用。

const app: App = (context.app = {unmount() {if (isMounted) {render(null, app._container);if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {app._instance = null;devtoolsUnmountApp(app);}delete app._container.__vue_app__;}}
});

render函数

源码位于packages/runtime-core/src/renderer.ts 2357line
如果第一个参数是null,则执行销毁组件的逻辑,否则执行patch函数来创建或者更新组件的逻辑(diff比较更新DOM)

  let isFlushing = false;
const render: RootRenderFunction = (vnode, container, namespace) => {if (vnode == null) {if (container._vnode) {unmount(container._vnode, null, null, true);}} else {patch(container._vnode || null,vnode,container,null,null,null,namespace);}if (!isFlushing) {isFlushing = true;flushPreFlushCbs();flushPostFlushCbs();isFlushing = false;}container._vnode = vnode;
};

directive指令

指令本质就是一个js对象,对象上挂着一些钩子函数。全局注册的指令都挂载到context当中。

const app: App = (context.app = {directive(name: string, directive?: Directive) {if (!directive) {return context.directives[name] as any;}context.directives[name] = directive;return app;}
});

mixin混入

判断是否支持Options API,以及当前这个mixin还没有被混入

const app: App = (context.app = {mixin(mixin: ComponentOptions) {if (__FEATURE_OPTIONS_API__) {if (!context.mixins.includes(mixin)) {context.mixins.push(mixin);}}return app;}
});

component组件

注册组件,如果组件已经存在,则返回组件,否则则注册当前组件返回app

const app: App = (context.app = {component(name: string, component?: Component): any {if (!component) {return context.components[name];}context.components[name] = component;return app;}
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/29734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0开始开发一个简单web界面的学习笔记(HTML类)

文章目录 什么是HTML页面vscode 开放工具搭建第一个HTML页面编写vscode 如何快速生成代码框架html标签——注释、标题、段落、换行标签格式化标签img 标签(src 属性01)img 属性02(alt、title、width/height、border)a标签href属性a标签target属性表格标签01 基本属性表格标签02…

【JAVA入门】Day08 - 静态变量

【JAVA入门】Day08 - 静态变量 文章目录 【JAVA入门】Day08 - 静态变量【补充】工具类 静态是面向对象中的一个概念&#xff0c;用英文标识符表示是 static。 在一个标准 JavaBean 类中&#xff0c;static 类型的变量和方法经常被使用。 public class Student {//属性…

css伪类和伪元素选择器

伪类选择器关注元素的状态和条件&#xff0c;而伪元素选择器则关注元素的视觉表现和扩展。两者都是CSS中强大的工具&#xff0c;能够帮助开发者实现复杂的样式布局和交互效果。 伪类选择器 伪类选择器在CSS中用于选择元素的特定状态或位置。以下是一些常见的伪类选择器及其使…

LeetCode:2288.价格减免(Java 字符串处理)

目录 2288.价格减免 题目描述&#xff1a; 实现代码与解析&#xff1a; 模拟 原理思路&#xff1a; 2288.价格减免 题目描述&#xff1a; 句子 是由若干个单词组成的字符串&#xff0c;单词之间用单个空格分隔&#xff0c;其中每个单词可以包含数字、小写字母、和美元符号…

AV1:帧间预测(一)参考帧管理

​AV1中帧类型 在H.26X系列标准中&#xff0c;根据帧允许的预测模式可以将帧分为I帧、P帧和B帧&#xff0c;根据帧在码流中前后的参考关系又可以分为IRAP、RADL等。AV1也类似地将帧分为4种类型&#xff0c;在码流中用frame_type来标识帧类型。 KEY_FRAME&#xff1a;相当于IDR帧…

HTB Editorial

Editorial User Nmap ┌──(kali㉿kali)-[~/…/machine/SeasonV/linux/Editorial] └─$ nmap -A 10.129.24.67 -T 4 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-16 21:54 EDT Nmap scan report for 10.129.2…

C#使用轻量级深度学习模型进行车牌颜色识别和车牌号识别

看到这个文章时候请注意这个不涉及到车牌检测&#xff0c;这个仅仅是车牌颜色和车牌号识别&#xff0c;如果想涉及到车牌检测可以参考这个博客&#xff1a;[C#]winform部署yolov7CRNN实现车牌颜色识别车牌号检测识别_c# yolo 车牌识别-CSDN博客 【训练源码】 https://github.…

DBeaver windows下载、安装与连接数据库

下载 官网下载地址&#xff1a;https://dbeaver.io/download/ 安装 1、双击安装 2、下一步…… 选择所有用户 3、组件选择 配置连接数据库 下载驱动

【记录44】【案例】echarts地图

效果&#xff1a;直接上效果图 环境&#xff1a;vue、echarts4.1.0 源码 // 创建容器 <template><div id"center"></div> </template>//设置容器大小&#xff0c;#center { width: 100%; height: 60vh; }这里需注意&#xff1a;笔者在echar…

Git的下载安装及可视化工具小乌龟

一、 Git 的下载 第1步&#xff1a;下载Git&#xff0c;下载地址&#xff1a;Git for Windows 这个就需要去 Git 官网下载对应系统的软件了&#xff0c;下载地址为 git-scm.com或者gitforwindows.org&#xff0c;或者阿里镜像&#xff08;感谢评论区的星悸迷航同学&#…

亚马逊收购 MX Player

不知道你在安卓手机上用什么视频播放器&#xff0c;个人看最强大的就是MX Player&#xff08;支持快进、倍速、睡眠定时、自定义解码器、AB段重复等&#xff0c;学英语十分强大&#xff09;。 MX Player 最初是韩国的视频播放软件和OTT服务平台&#xff0c;于2011年推出&#…

什么是云恶意软件攻击,如何进行有效的防护

一切都在向云转移。云端数据越多&#xff0c;恶意攻击者攻击云平台的兴趣就越大。 攻击者使用恶意软件窃取数据并破坏服务。虽然恶意软件在云端可能不像在个人电脑上那么普遍&#xff0c;但大行其道的云恶意软件令人担忧。此外&#xff0c;组织不像您预料的那样意识到这点。 …

鸿蒙开发网络管理:【@ohos.request (上传下载)】

上传下载 说明&#xff1a; 本模块首批接口从API version 6开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import request from ohos.request;限制与约束 默认支持https&#xff0c;如果要支持http&#xff0c;需要在config.json里…

所以spring mvc异常处理工作原理是啥

文章目录 spring mvc异常处理&#xff08;源码分析&#xff09;概述原理&#xff08;源码角度&#xff09;模拟debug前期提要分析4个map4个map的初始化为什么需要基于mappedMethods缓存 总结一下 spring mvc异常处理&#xff08;源码分析&#xff09; 概述 spring mvc有下面三…

力扣每日一题 6/18 字符串/模拟

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 2288.价格减免 【中等】 题目&#xff1a; 句子 是由若干个单词组成的字符…

‘引爆增长·赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办

‘引爆增长赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办 2024年6月 17 至18 日&#xff0c;为期两天的“引爆增长赋能十堰”第一届学习交流峰会在湖北十堰东方汉宫国际酒店一号盛大举行&#xff0c;学习峰会现场&#xff0c;来自十堰地区及邻边地市的上百位实体企业…

netty服务端与客户端的启动流程

如图所示&#xff0c;右侧是服务端Server&#xff0c;左侧是客户端Client 要点说明&#xff1a; 1.在Server中&#xff0c;在NioEventLoopGroup()中&#xff0c;会有1个selector和线程在不断循环&#xff0c;等待是否有accept事件&#xff0c;在accept事件发生后&#xff0c;才…

四款让人大开眼界的高质量软件,个个实力超群,使用起来爱不释手

电脑里的Windows软件&#xff0c;简直多得数不清&#xff0c;啥都有。 像那个电子表格、写文章的、玩游戏聊天的、还有修图的&#xff0c;这些都太常见了&#xff0c;它们确实给咱们生活带来方便&#xff0c;但有时候也会让那些不太懂电脑的小伙伴们头疼不已。 讲真&#xff0…

重学java 73.设计模式

本想送你一本沉思录&#xff0c;可该迷途知返的人是我 —— 24.6.18 设计模式 设计模式(Design pattern)&#xff0c;是一套被反复使用、经过分类编目的、代码设计经验的总结&#xff0c;使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性,稳定性。 1995 年&#x…

全网最全 Kimi 使用手册,看完 Kimi 效率提升 80%

在当前AI文字大模型领域&#xff0c;ChatGPT4.0无疑是最强大。然而&#xff0c;最近最火爆的大模型非国产Kimi莫属。 相较于其它大模型&#xff0c;Kimi 最大的优势在于&#xff0c;超长文本输入&#xff0c;支持200万汉字&#xff0c;是全球范围内罕见的超长文本处理工具&…