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")

相关推荐
蓝胖子的多啦A梦1 分钟前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚
前端·npm·node.js
LinCC74 分钟前
在Vite中构建项目出错-Top-level await is not available in the configured target environme
前端
用户882093216675 分钟前
如何优雅拆分一个充斥十几种逻辑的 SDK 回调函数?
前端
Momoly086 分钟前
vue3+el-table 利用插槽自定义数据样式
前端·javascript·vue.js
唯有选择6 分钟前
让你的应用界面好看的基石:Flutter主题Theme使用和扩展自定义字段
前端·flutter
山有木兮木有枝_7 分钟前
告别布局间隙:浮动(float)在网页排版中的高阶应用
前端
满分观察网友z8 分钟前
vue的<router-link>的to里面的query和params的区别
前端
bo521009 分钟前
从0到1:Element Plus虚拟树的拖拽功能二次开发实战
javascript·vue.js
小约翰仓鼠9 分钟前
vue3表格使用Switch 开关
前端·javascript·vue.js
JiangJiang11 分钟前
🔥 面试官:Webpack 为什么能热更新?你真讲得清吗?
前端·面试·webpack