use-context.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { inject, provide } from 'vue';
  2. /**
  3. * Use context
  4. *
  5. * @example
  6. * ```ts
  7. * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
  8. *
  9. * // context.ts
  10. * import { ref } from 'vue';
  11. * import { useContext } from '@sa/hooks';
  12. *
  13. * export const [provideDemoContext, useDemoContext] = useContext('demo', () => {
  14. * const count = ref(0);
  15. *
  16. * function increment() {
  17. * count.value++;
  18. * }
  19. *
  20. * function decrement() {
  21. * count.value--;
  22. * }
  23. *
  24. * return {
  25. * count,
  26. * increment,
  27. * decrement
  28. * };
  29. * })
  30. * ``` // A.vue
  31. * ```vue
  32. * <template>
  33. * <div>A</div>
  34. * </template>
  35. * <script setup lang="ts">
  36. * import { provideDemoContext } from './context';
  37. *
  38. * provideDemoContext();
  39. * // const { increment } = provideDemoContext(); // also can control the store in the parent component
  40. * </script>
  41. * ``` // B.vue
  42. * ```vue
  43. * <template>
  44. * <div>B</div>
  45. * </template>
  46. * <script setup lang="ts">
  47. * import { useDemoContext } from './context';
  48. *
  49. * const { count, increment } = useDemoContext();
  50. * </script>
  51. * ```;
  52. *
  53. * // C.vue is same as B.vue
  54. *
  55. * @param contextName Context name
  56. * @param fn Context function
  57. */
  58. export default function useContext<Arguments extends Array<any>, T>(
  59. contextName: string,
  60. composable: (...args: Arguments) => T
  61. ) {
  62. const key = Symbol(contextName);
  63. /**
  64. * Injects the context value.
  65. *
  66. * @param consumerName - The name of the component that is consuming the context. If provided, the component must be
  67. * used within the context provider.
  68. * @param defaultValue - The default value to return if the context is not provided.
  69. * @returns The context value.
  70. */
  71. const useInject = <N extends string | null | undefined = undefined>(
  72. consumerName?: N,
  73. defaultValue?: T
  74. ): N extends null | undefined ? T | null : T => {
  75. const value = inject(key, defaultValue);
  76. if (consumerName && !value) {
  77. throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``);
  78. }
  79. // @ts-expect-error - we want to return null if the value is undefined or null
  80. return value || null;
  81. };
  82. const useProvide = (...args: Arguments) => {
  83. const value = composable(...args);
  84. provide(key, value);
  85. return value;
  86. };
  87. return [useProvide, useInject] as const;
  88. }