Vue3 源码解读-Provide&Inject 实现原理


💡 [本系列Vue3源码解读文章基于3.3.4版本](https://github.com/vuejs/core/tree/v3.3.4) 欢迎关注公众号:《前端 Talkking》

1、前言

在 Vuejs 中,我们可以使用 ProvideInject方法来实现跨父子组件的通信,这样一来,无论组件嵌套多少层级,都可以在后代组件中访问它们祖先组件的数据。

2、源码实现

Provide API

provide的作用是提供数据,我们来看它的源码实现: provide API 源码实现

typescript 复制代码
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
  }
}

根据以上代码可知:

  1. 当组件实例调用 provide函数的时候,它会调用父级 provides对象作为原型对象创建自己的 provides对象,然后再给自己的 provides添加新的属性值;
  2. provide提供的数据保存在组件的 provides对象上。

我们来看看创建组件实例的时候,是怎么把 provide放到组件实例上的: 组件实例对象源码

typescript 复制代码
const instance: ComponentInternalInstance = {
    // 省略部分代码
    provides: parent ? parent.provides : Object.create(appContext.provides),
}

由此可见,默认情况下,组件实例的 provides对象指向其父组件的 provides对象,因此会形成以下调用关系:

分析完 provide实现原理后,接下来我们来看看 inject的实现原理。

Inject API

inject的作用是注入数据,该数据来自于它的祖先组件 provide方法提供的数据,我们来看它的源码实现: inject API 源码实现

typescript 复制代码
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(key, 'a')方法,那么它会先从其父组件的 provides对象本身去查找这个 key,如果找到了就返回对应的数据,如果没有找到,则通过 provides的原型去查找这个 key,此时的 provides的原型指向的就是它的父级 provides对象。实际上,inject查找数据的方法其实就是利用了 JavaScript中原型链查找方式

3、总结

Vue 可以使用 provideinject实现跨组件数据传递,其中:

  • provide提供的数据保存在组件的 provides对象上,创建 provides的时候,将父组件的 provide作为自己的原型,因此会形成原型链;
  • inject使用数据时,会沿着 provides的原型上去查找这个 key,本质是利用了 JavaScript中原型链查找方式;

4、参考资料

1\][vue官网](https://link.juejin.cn?target=https%3A%2F%2Fcn.vuejs.org%2F "https://cn.vuejs.org/") \[2\][vuejs设计与实现](https://link.juejin.cn?target=https%3A%2F%2Fwww.ituring.com.cn%2Fbook%2F2953 "https://www.ituring.com.cn/book/2953") \[3\][vue3源码](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Fcore%2Fblob%2Fv3.3.4 "https://github.com/vuejs/core/blob/v3.3.4")

相关推荐
辻戋1 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保1 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun2 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp2 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.3 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl5 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫6 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友6 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理8 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻8 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js