apiInject.ts 3.3 KB

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