面试官:来谈谈Vue3的provide和inject实现多级传递的原理

news/2024/12/3 8:45:08/文章来源:https://www.cnblogs.com/heavenYJJ/p/18582876

前言

没有看过provideinject函数源码的小伙伴可能觉得他们实现数据多级传递非常神秘,其实他的源码非常简单,这篇文章欧阳来讲讲provideinject函数是如何实现数据多级传递的。ps:本文中使用的Vue版本为3.5.13

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

看个demo

先来看个demo,这个是父组件,代码如下:

<template><ChildDemo />
</template><script setup>
import ChildDemo from "./child.vue";
import { ref, provide } from "vue";
// 提供响应式的值
const count = ref(0);
provide("count", count);
</script>

在父组件中使用provide为后代组件注入一个count响应式变量。

再来看看子组件child.vue代码如下:

<template><GrandChild />
</template>
<script setup>
import GrandChild from "./grand-child.vue";
</script>

从上面的代码可以看到在子组件中什么事情都没做,只渲染了孙子组件。

我们再来看看孙子组件grand-child.vue,代码如下:

<script setup>
import { inject } from "vue";// 注入响应式的值
const count = inject("count");
console.log("inject count is:", count);
</script>

从上面的代码可以看到在孙子组件中使用inject函数拿到了父组件中注入的count响应式变量。

provide函数

我们先来debug看看provide函数的代码,给父组件中的provide函数打个断点,如下图:
provide

刷新页面,此时代码将会停留在断点处。让断点走进provide函数,代码如下:

function provide(key, value) {if (!currentInstance) {if (!!(process.env.NODE_ENV !== "production")) {warn$1(`provide() can only be used inside setup().`);}} else {let provides = currentInstance.provides;const parentProvides = currentInstance.parent && currentInstance.parent.provides;if (parentProvides === provides) {provides = currentInstance.provides = Object.create(parentProvides);}provides[key] = value;}
}

首先判断currentInstance是否有值,如果没有就说明当前没有vue实例,也就是说当前调用provide函数的地方是不在setup函数中执行的,然后给出警告provide只能在setup中使用。

然后走进else逻辑中,首先从当前vue实例中取出存的provides属性对象。并且通过currentInstance.parent.provides拿到父组件vue实例中的provides属性对象。

这里为什么需要判断if (parentProvides === provides)呢?

因为在创建子组件时会默认使用父组件的provides属性对象作为父组件的provides属性对象。代码如下:

const instance: ComponentInternalInstance = {uid: uid++,vnode,type,parent,provides: parent ? parent.provides : Object.create(appContext.provides),// ...省略
}	

从上面的代码可以看到如果有父组件,那么创建子组件实例的时候就直接使用父组件的provides属性对象。

所以这里在provide函数中需要判断if (parentProvides === provides),如果相等说明当前父组件和子组件是共用的同一个provides属性对象。此时如果子组件调用了provide函数,说明子组件需要创建自己的provides属性对象。

并且新的属性对象还需要能够访问到父组件中注入的内容,所以这里以父组件的provides属性对象为原型去创建一个新的子组件的,这样在子组件中不仅能够访问到原型链中注入的provides属性对象,也能够访问到自己注入进去的provides属性对象。

最后就是执行provides[key] = value将当前注入的内容存到provides属性对象中。

inject函数

我们再来看看inject函数是如何隔了一层子组件从父组件中如何取出数据的,还是一样的套路,给孙子组件中的inject函数打个断点。如下图:
inject

将断点走进inject函数,代码如下:

export function inject(key: InjectionKey<any> | string,defaultValue?: unknown,treatDefaultAsFactory = false,
) {// fallback to `currentRenderingInstance` so that this can be called in// a functional componentconst instance = currentInstance || currentRenderingInstance// also support looking up from app-level provides w/ `app.runWithContext()`if (instance || currentApp) {const provides = currentApp? currentApp._context.provides: instance? instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides: instance.parent.provides: undefinedif (provides && key in provides) {return provides[key]} else if (arguments.length > 1) {return treatDefaultAsFactory && isFunction(defaultValue)? defaultValue.call(instance && instance.proxy): defaultValue} else if (__DEV__) {warn(`injection "${String(key)}" not found.`)}} else if (__DEV__) {warn(`inject() can only be used inside setup() or functional components.`)}
}

首先拿到当前渲染的vue实例赋值给本地变量instance。接着使用if (instance || currentApp)判断当前是否有vue实例,如果没有看看有没有使用app.runWithContext手动注入了上下文,如果注入了那么currentApp就有值。

接着就是一串三元表达式,如果使用app.runWithContext手动注入了上下文,那么就优先从注入的上下文中取出provides属性对象。

如果没有那么就看当前组件是否满足instance.parent == null,也就是说当前组件是否是根节点。如果是根节点就取app中注入的provides属性对象。

如果上面的都不满足就去取父组件中注入的provides属性对象,前面我们讲过了在inject函数阶段,如果子组件内没有使用inject函数,那么就会直接使用父组件的provides属性对象。如果子组件中使用了inject函数,那么就以父组件的provides属性对象为原型去创建一个新的子组件的provides属性对象,从而形成一条原型链。

所以这里的孙子节点的provides属性对象中当然就能够拿到父组件中注入的count响应式变量,那么if (provides && key in provides)就满足条件,最后会走到return provides[key]中将父组件中注入的响应式变量count原封不动的返回。

还有就是如果我们inject一个没有使用provide存入的key,并且传入了第二个参数defaultValue,此时else if (arguments.length > 1)就满足条件了。

在里面会去判断是否传入第三个参数treatDefaultAsFactory,如果这个参数的值为true,说明第二个参数defaultValue可能是一个工厂函数。那么就执行defaultValue.call(instance && instance.proxy)defaultValue的当中工厂函数的执行结果进行返回。

如果第三个参数treatDefaultAsFactory的值不为true,那么就直接将第二个参数defaultValue当做默认值返回。

总结

这篇文章讲了使用provideinject函数是如何实现数据多级传递的。

在创建vue组件实例时,子组件的provides属性对象会直接使用父组件的provides属性对象。如果在子组件中使用了provide函数,那么会以父组件的provides属性对象为原型创建一个新的provides属性对象,并且将provide函数中注入的内容塞到新的provides属性对象中,从而形成了原型链。

在孙子组件中,他的parent就是子组件。前面我们讲过了如果没有在组件内使用provide注入东西(很明显这里的子组件确实没有注入任何东西),那么就会直接使用他的父组件的provides属性对象,所以这里的子组件是直接使用的是父组件中的provides属性对象。所以在孙子组件中可以直接使用inject函数拿到父组件中注入的内容。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

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

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

相关文章

C#] 一款代码注释清理工具

在程序开发过程中,很多时候我们都会在代码中进行注释,以便大家更容易理解或能更直观明白某个类或方法是用来做什么的,我们就会用注释 就以C#为列子,注释符大致为//、///、/**/等为我们大家所熟悉。甚至在每个方法名前都有注释。但是往往在最终编译的时候,我们又想去除代码…

「GIS数据」下载全国的GeoJSON、shp格式数据(精确到乡镇街道级)-2024年12月更新

发现个可以免费下载全国 geojson 数据的网站,推荐一下。支持全国、省级、市级、区/县级、街道/乡镇级以及各级的联动数据,支持导入矢量地图渲染框架中使用,例如:D3、Echarts等 geojson 数据下载地址:https://geojson.hxkj.vip 该项目 github 地址:https://github.com/Tan…

VISUAL-STUDIO(2022)-通过Visual Studio Installer添加C++工作负载

一、打开Visual Studio Installer二、点击修改三、找到要新增的工作负载(此处我需要添加的是C++)四、选中后点击下载等待安装即可

PG-pg数据库安装vector

一、下载pg-vector安装包 (按需安装所欲要的版本) 安装地址: https://pgxn.org/dist/vector/0.7.3/ 我安装的是0.7.3版本 通过百度网盘分享的文件:PgVector 链接:https://pan.baidu.com/s/1XKI6MSqOtVUW2VX_fzsERg 提取码:sky1二、管理员执行cmd执行以下语句 其中call后面…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+rememberMe

我们日常的记住密码功能, 实现思路如下:可以看到, 是基于COOKIE进行操作的. Shiro对页面访问的权限分为三个级别: 未认证 - 可访问的页面, 例如: 登录入口.html, 注册入口.html 记住我 - 可访问的页面, 例如: 个人信息.html 已认证 - 可访问的页面, 例如: 转账.html而大概的流程…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+授权

用户登陆成功之后, 要进行响应的操作就需要有对应的权限; 在进行操作之前对权限进行检查 - 授权. 权限控制通常有两类做法: 不同身份的用户登录,不同的操作菜单(没有权限的菜单不显示) 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+Shiro 加密

加密的过程如下:研究这部分内容, 我们需要将数据库中Password值都改为MD5处理后的值, 过程如下: mysql> SELECT * FROM tb_users; +---------+----------+----------+---------------+ | user_id | username | password | password_salt | +---------+----------+----------…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+退出登录

在我们的ShiroAutoConfiguration::ShiroFilterFactoryBean中, 我们增加如下代码: @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilte…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+自定义 Realm

在真正的项目中, 我们不会使用Shiro提供的JdbcRealm, 而是使用自定义Realm, 配合我们的MyBatis, 以及自定义表结构进行联合使用.表结构定义 那么下面我们来定义这些表:-- 用户信息表 CREATE TABLE `tb_users`(user_id int unsigned primary key auto_increment,username varcha…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+IniRealm

IniRealm 如果我们想在SpringBoot中进行使用Shiro, 那么我们肯定是需要围绕如下环节进行研究. 创建 pom.xml: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3&l…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+JdbcRealm

JdbcRealm 其中JdbcRealm需要创建如下表结构: CREATE TABLE `users`(id int primary key auto_increment,username varchar(60) not null unique,password varchar(60) not null,password_salt varchar(20) ); -- 创建五个用户如下 INSERT INTO `users`(username, password) VA…

【Java漏洞】Shiro 漏洞:基于 Java SE 基本使用

在pom.xml文件中进行引入依赖: <dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.4.1</version></dependency> </dependencies>因为本次的Realm从文…