Vue3的provide和inject实现多级传递的原理解析

 更新时间:2024年12月03日 08:49:43   作者:前端欧阳  
Vue3中的provide和inject函数通过原型链实现数据的多级传递,父组件使用provide注入数据,子组件和后代组件通过inject获取这些数据,在创建组件实例时,子组件会继承父组件的provides属性对象,介绍Vue3的provide和inject实现多级传递的原理,需要的朋友可以参考下

前言

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

看个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函数拿到父组件中注入的内容。

到此这篇关于来谈谈Vue3的provide和inject实现多级传递的原理的文章就介绍到这了,更多相关Vue3的provide和inject多级传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue3管理后台项目使用高德地图选点的实现

    Vue3管理后台项目使用高德地图选点的实现

    本文主要介绍了Vue3管理后台项目使用高德地图选点的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • vue实现可视化可拖放的自定义表单的示例代码

    vue实现可视化可拖放的自定义表单的示例代码

    这篇文章主要介绍了vue实现可视化可拖放的自定义表单的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • 基于vue2.0实现仿百度前端分页效果附实现代码

    基于vue2.0实现仿百度前端分页效果附实现代码

    本文通过实例代码给大家介绍了基于vue2.0实现仿百度前端分页效果,在文中给大家记录了遇到的问题及解决方法,需要的朋友可以参考下
    2018-10-10
  • vue实现加载页面自动触发函数(及异步获取数据)

    vue实现加载页面自动触发函数(及异步获取数据)

    这篇文章主要介绍了vue实现加载页面自动触发函数(及异步获取数据),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • vue-test-utils初使用详解

    vue-test-utils初使用详解

    这篇文章主要介绍了vue-test-utils初使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Vue组件之非单文件组件的使用详解

    Vue组件之非单文件组件的使用详解

    Vue中常常会把组件分为非单文件组件和单文件组件,这篇文章主要为大家介绍了非单文件组件的具体使用方法,文中的示例代码讲解详细,需要的可以参考一下
    2023-05-05
  • Vue使用Props实现组件数据交互的示例代码

    Vue使用Props实现组件数据交互的示例代码

    在Vue中,组件的props属性用于定义组件可以接收的外部数据,这些数据来自父组件并传递给子组件,本文给大家介绍了Vue使用Props实现组件数据交互,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2024-06-06
  • nuxt使用vuex存储及获取用户信息踩坑的解决

    nuxt使用vuex存储及获取用户信息踩坑的解决

    这篇文章主要介绍了nuxt使用vuex存储及获取用户信息踩坑的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • Vue简单封装axios网络请求的方法

    Vue简单封装axios网络请求的方法

    这篇文章主要介绍了Vue简单封装axios网络请求,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,对Vue封装axios网络请求相关知识感兴趣的朋友一起看看吧
    2022-11-11
  • Vue3.0 手写放大镜效果

    Vue3.0 手写放大镜效果

    放大镜在很多购物网站都可以看的到,本文主要实现 固定放大两倍,鼠标进入到左侧图片区域的时候,遮罩层显示,离开时,遮罩层隐藏,感兴趣的可以了解一下
    2021-07-07

最新评论