前言

没有看过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函数,代码如下:

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函数,代码如下:

export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false,
) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
const 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
: undefined if (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。

面试官:来谈谈Vue3的provide和inject实现多级传递的原理的更多相关文章

  1. 【Java8新特性】面试官:谈谈Java8中的Stream API有哪些终止操作?

    写在前面 如果你出去面试,面试官问了你关于Java8 Stream API的一些问题,比如:Java8中创建Stream流有哪几种方式?(可以参见:<[Java8新特性]面试官问我:Java8中 ...

  2. 面试官:Redis集群有哪些方式,Leader选举又是什么原理呢?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 作为一名Java程序员,Redi ...

  3. 【搞定面试官】谈谈你对JDK中Executor的理解?

    ## 前言 随着当今处理器计算能力愈发强大,可用的核心数量越来越多,各个应用对其实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行.在此背景下,Java自JDK1.5 提供了自己的多线程框架 ...

  4. 【搞定Jvm面试】 面试官:谈谈 JVM 类文件结构的认识

    类文件结构 一 概述 在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机.Java 语言通过字节码的方式,在一定程度上解决 ...

  5. 【搞定Jvm面试】 面试官:谈谈 JVM 类加载过程是怎样的?

    类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步:加载->连接->初始化.连接过程 ...

  6. 面试官:"谈谈分库分表吧?"

    转自:学习Java的小姐姐 www.cnblogs.com/chenchen0618/p/11624480.html 1.什么是分库分表 从字面上简单理解,就是将原本存储在一个库的数据分块存储在多个库 ...

  7. 【MySQL】面试官:谈谈你对Mysql的MVCC的理解?

    MVCC(Mutil-Version Concurrency Control),就是多版本并发控制.MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问. 在Mysql的In ...

  8. 面试官:谈谈你对IO流和NIO的理解

    一.概念 NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套N ...

  9. 面试官:说说一条查询sql的执行流程和底层原理?

    一条查询SQL执行流程图如下 序章 自我介绍 我是一条sql,就是一条长长的字符串,不要问我长什么样,因为我比较傲娇. 额~~不是我不说啊,因为细说起来,我可以细分为DML(Update.Insert ...

  10. vue之provide和inject跨组件传递属性值失败(父组件向子组件传值的两种方式)

    简单介绍:当一个子组件需要用到父组件的父组件的某些参数.那么这个时候为了避免组件重复传参,使用vue的依赖注入是个不错的方法,直接在最外层组件设置一个provide,内部不管多少嵌套都可以直接取到最外 ...

随机推荐

  1. Angular 18+ 高级教程 – Change Detection & Ivy rendering engine

    前言 不熟悉 Angular 的朋友可能不了解 Change Detection 和目前当火的 Signal 之间的关系,以至于认为现在应该要学习新潮流 Signal 而不是已经过时的 Change ...

  2. QT QML实用效果之实现页面切换效果

    简介 本文介绍了如何使用QT QML和JavaScript实现页面动态加载和切换的效果. 文章目录 效果 JavaScript代码:butianyun.js文件 QML代码:主页面 页面A QML代码 ...

  3. 使用 Docker 部署 MySql

    前言 虽然不建议将需要持久化的数据保存在容器中,但是自己平时做个小项目玩玩还是没什么问题的. 拉取镜像 docker pull mysql 不加 tag 的话默认从 DockerHub 拉取最新版本的 ...

  4. 干货必收藏!墨天轮最受DBA欢迎的250份学习文档合集

    作为一个DBA,必须要精通SQL命令.各种数据库架构.数据库管理和维护.数据库调优,必要的时候,还需要为开发人员搭建一个健壮.结构良好.性能稳定的数据库环境. 技术也是不断进步的,社会的发展要求DBA ...

  5. C++ 第二节课 结构体, 字符串 和 C语言的区分

    1 #include <iostream> 2 3 using namespace std; 4 5 6 // 结构体 7 struct Stu{ 8 string name; 9 int ...

  6. 放大招!青云企业级容器平台 QKCP 迎来重磅升级

    青云企业级容器平台 QKCP 3.2 重磅发布.QKCP(QingCloud KubeSphere Container Platform)是青云科技基于 KubeSphere 开源容器平台打造的企业级 ...

  7. ToDesk云电脑开启公测!支持AIGC、高性能渲染等场景,价格低至0.98元

    在云计算和人工智能技术飞速发展的今天,云电脑作为一种新型的计算模式,正逐渐改变着传统电脑的使用方式.近日,ToDesk云电脑宣布开启公测,以其支持AIGC(人工智能.大数据.云计算等技术的融合应用). ...

  8. 人口分析实战(利用jupyter)

    目录 1.项目需求 2.开始操作 2.1导入我们所需要的包 2.2导入数据.查看原始数据 2.3对数据进行清洗 2.4对数据进行处理 1.项目需求 需求: 导入文件,查看原始数据 将人口数据和各州简称 ...

  9. 顺序结构程序设计(python)

    文章目录 1.python运算符 1.1 python算数运算 1.2python比较运算符 1.3 Python赋值运算符 1.4 python逻辑运算符 1.5 python成员运算符 1.6py ...

  10. C++ 加权随机抽样

    ​ 在做五子棋 AI 的时候,遇到要根据权重从一组数里边随机选出一个这个问题.这个问题恰好也是 leetcode 第 528 道题,使用 C++,标准库就有现成的: #include <rand ...