apiInject.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { isFunction } from '@vue/shared'
  2. import { currentInstance } from './component'
  3. import { currentRenderingInstance } from './componentRenderContext'
  4. import { currentApp } from './apiCreateApp'
  5. import { warn } from './warning'
  6. export interface InjectionKey<T> extends Symbol {}
  7. export function provide<T, K = InjectionKey<T> | string | number>(
  8. key: K,
  9. value: K extends InjectionKey<infer V> ? V : T,
  10. ) {
  11. if (!currentInstance) {
  12. if (__DEV__) {
  13. warn(`provide() can only be used inside setup().`)
  14. }
  15. } else {
  16. let provides = currentInstance.provides
  17. // by default an instance inherits its parent's provides object
  18. // but when it needs to provide values of its own, it creates its
  19. // own provides object using parent provides object as prototype.
  20. // this way in `inject` we can simply look up injections from direct
  21. // parent and let the prototype chain do the work.
  22. const parentProvides =
  23. currentInstance.parent && currentInstance.parent.provides
  24. if (parentProvides === provides) {
  25. provides = currentInstance.provides = Object.create(parentProvides)
  26. }
  27. // TS doesn't allow symbol as index type
  28. provides[key as string] = value
  29. }
  30. }
  31. export function inject<T>(key: InjectionKey<T> | string): T | undefined
  32. export function inject<T>(
  33. key: InjectionKey<T> | string,
  34. defaultValue: T,
  35. treatDefaultAsFactory?: false,
  36. ): T
  37. export function inject<T>(
  38. key: InjectionKey<T> | string,
  39. defaultValue: T | (() => T),
  40. treatDefaultAsFactory: true,
  41. ): T
  42. export function inject(
  43. key: InjectionKey<any> | string,
  44. defaultValue?: unknown,
  45. treatDefaultAsFactory = false,
  46. ) {
  47. // fallback to `currentRenderingInstance` so that this can be called in
  48. // a functional component
  49. const instance = currentInstance || currentRenderingInstance
  50. // also support looking up from app-level provides w/ `app.runWithContext()`
  51. if (instance || currentApp) {
  52. // #2400
  53. // to support `app.use` plugins,
  54. // fallback to appContext's `provides` if the instance is at root
  55. // #11488, in a nested createApp, prioritize using the provides from currentApp
  56. const provides = currentApp
  57. ? currentApp._context.provides
  58. : instance
  59. ? instance.parent == null
  60. ? instance.vnode.appContext && instance.vnode.appContext.provides
  61. : instance.parent.provides
  62. : undefined
  63. if (provides && (key as string | symbol) in provides) {
  64. // TS doesn't allow symbol as index type
  65. return provides[key as string]
  66. } else if (arguments.length > 1) {
  67. return treatDefaultAsFactory && isFunction(defaultValue)
  68. ? defaultValue.call(instance && instance.proxy)
  69. : defaultValue
  70. } else if (__DEV__) {
  71. warn(`injection "${String(key)}" not found.`)
  72. }
  73. } else if (__DEV__) {
  74. warn(`inject() can only be used inside setup() or functional components.`)
  75. }
  76. }
  77. /**
  78. * Returns true if `inject()` can be used without warning about being called in the wrong place (e.g. outside of
  79. * setup()). This is used by libraries that want to use `inject()` internally without triggering a warning to the end
  80. * user. One example is `useRoute()` in `vue-router`.
  81. */
  82. export function hasInjectionContext(): boolean {
  83. return !!(currentInstance || currentRenderingInstance || currentApp)
  84. }