从源码看看vue3的provides/inject是如何实现的

前言

在项目中不可避免的要进行组件间的通信,provide和inject是其中的一种方式,也叫做依赖注入 ,用于父组件向其子代组件中传递数据。可以将其理解为通过props传值的方式在多级嵌套组件下的改进。 笔者想了解下他是如何实现的,但经过简单的检索后没有找到满意的文章,那就自己去看看源码,顺便记录一下。

用法

provide和inject的用法这里就不再赘述了,我们主要来关注vue3是如何来实现的这套api的

源码分析

源码地址:github.com/vuejs/core/...

打开源码看下,其实实现并不复杂

provide

ts 复制代码
export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T,
) {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
  }
}

简单梳理下provide的实现逻辑:上来就是先判断当前组件实例是否存在,若为null则说明provide没有使用在组件的声明周期中,在开发模式下发出warning。

反之若组件实例存在,则获取组件实例上的provides属性和当前组件的父组件的provides。接下来进行一个判断parentProvides === provides,判断是否是在初始状态下声明provide注入的,如果为真则要为当前组件实例上创建一个独立的 provides 对象,要注意是用Object.create()来创建新对象的,也就是常说的原型继承,把父组件实例的provides作为子组件provides的原型,然后再对它进行赋值操作。

至于为什么一定要有这个currentInstance呢?因为provide所注入的数据都存放在currentInstance中,而inject读取数据也依赖于它

inject

ts 复制代码
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) {
    // #2400
    // to support `app.use` plugins,
    // fallback to appContext's `provides` if the instance is at root
    const 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) {
      // TS doesn't allow symbol as index type
      return provides[key as string]
    } 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.`)
  }
}

inject这里做的事情也很简单了,用in操作符来判断要注入的数据是否存在于当前组件实例的provides的原型链中,如果有则返回之。

这里还有一个点可以聊一下,provide api本身只能用于组件中,但vue的应用实例app下也有一个app.provide方法,这个方法就是用于在非组件上下文中提供数据,比如应用上下文。这也是pinia中所用到的一个点,将pinia实例通过app.provide作为全局变量,使得在各个组件中调用useStore时,useStore内部可以通过inject获取到pinia实例,并把创建的store挂载到pinia._s上。所以我们可以看到inject中会尝试获取currentApp!._context.provides

小结

通过对源码的分析,我们知道整个依赖注入的实现原理其实就是:provide在每个组件实例上挂载一个provides对象,并且保持着跟父子组件相同嵌套关系的原型链,然后inject在从原型链中去查找这个值并返回。

相关推荐
别拿曾经看以后~21 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死24 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人36 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人36 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR42 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香44 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍