从源码看看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在从原型链中去查找这个值并返回。

相关推荐
程序员黑豆2 小时前
Java 注释详解:单行、多行与文档注释的完整指南
java·前端·ai编程
剪刀石头布啊2 小时前
antd中可编辑表格中巧用useWatch实现联动高性能效果,以及定制延伸学习
前端
油丶酸萝卜别吃2 小时前
前端转全栈学习路线
前端·学习
浮生望3 小时前
前端路由进阶:History API原理与手写HistoryRouter
前端
陆枫Larry3 小时前
JavaScript 中的竞态是什么,为啥会有竟态?
前端
上海安当技术5 小时前
半天接入:USBKey RESTful API + C 动态库,Web 和 C/S 两套集成路径实战
前端·后端·restful·集成·usbkey
DevUI团队6 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端
weixin_431600446 小时前
NestJS 入门(3):Guard 如何挡住未登录请求?
前端·后端·学习·nest.js
kyriewen7 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
IT_陈寒7 小时前
JavaScript类型转换把我坑惨了,这破玩意真该早点搞明白
前端·人工智能·后端