use-signal.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { computed, ref, shallowRef, triggerRef } from 'vue';
  2. import type {
  3. ComputedGetter,
  4. DebuggerOptions,
  5. Ref,
  6. ShallowRef,
  7. WritableComputedOptions,
  8. WritableComputedRef
  9. } from 'vue';
  10. type Updater<T> = (value: T) => T;
  11. type Mutator<T> = (value: T) => void;
  12. /**
  13. * Signal is a reactive value that can be set, updated or mutated
  14. *
  15. * @example
  16. * ```ts
  17. * const count = useSignal(0);
  18. *
  19. * // `watchEffect`
  20. * watchEffect(() => {
  21. * console.log(count());
  22. * });
  23. *
  24. * // watch
  25. * watch(count, value => {
  26. * console.log(value);
  27. * });
  28. *
  29. * // useComputed
  30. * const double = useComputed(() => count() * 2);
  31. * const writeableDouble = useComputed({
  32. * get: () => count() * 2,
  33. * set: value => count.set(value / 2)
  34. * });
  35. * ```
  36. */
  37. export interface Signal<T> {
  38. (): Readonly<T>;
  39. /**
  40. * Set the value of the signal
  41. *
  42. * It recommend use `set` for primitive values
  43. *
  44. * @param value
  45. */
  46. set(value: T): void;
  47. /**
  48. * Update the value of the signal using an updater function
  49. *
  50. * It recommend use `update` for non-primitive values, only the first level of the object will be reactive.
  51. *
  52. * @param updater
  53. */
  54. update(updater: Updater<T>): void;
  55. /**
  56. * Mutate the value of the signal using a mutator function
  57. *
  58. * this action will call `triggerRef`, so the value will be tracked on `watchEffect`.
  59. *
  60. * It recommend use `mutate` for non-primitive values, all levels of the object will be reactive.
  61. *
  62. * @param mutator
  63. */
  64. mutate(mutator: Mutator<T>): void;
  65. /**
  66. * Get the reference of the signal
  67. *
  68. * Sometimes it can be useful to make `v-model` work with the signal
  69. *
  70. * ```vue
  71. * <template>
  72. * <input v-model="model.count" />
  73. * </template>;
  74. *
  75. * <script setup lang="ts">
  76. * const state = useSignal({ count: 0 }, { useRef: true });
  77. *
  78. * const model = state.getRef();
  79. * </script>
  80. * ```
  81. */
  82. getRef(): Readonly<ShallowRef<Readonly<T>>>;
  83. }
  84. export interface ReadonlySignal<T> {
  85. (): Readonly<T>;
  86. }
  87. export interface SignalOptions {
  88. /**
  89. * Whether to use `ref` to store the value
  90. *
  91. * @default false use `sharedRef` to store the value
  92. */
  93. useRef?: boolean;
  94. }
  95. export function useSignal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
  96. const { useRef } = options || {};
  97. const state = useRef ? (ref(initialValue) as Ref<T>) : shallowRef(initialValue);
  98. return createSignal(state);
  99. }
  100. export function useComputed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ReadonlySignal<T>;
  101. export function useComputed<T>(options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions): Signal<T>;
  102. export function useComputed<T>(
  103. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  104. debugOptions?: DebuggerOptions
  105. ) {
  106. const isGetter = typeof getterOrOptions === 'function';
  107. const computedValue = computed(getterOrOptions as any, debugOptions);
  108. if (isGetter) {
  109. return () => computedValue.value as ReadonlySignal<T>;
  110. }
  111. return createSignal(computedValue);
  112. }
  113. function createSignal<T>(state: ShallowRef<T> | WritableComputedRef<T>): Signal<T> {
  114. const signal = () => state.value;
  115. signal.set = (value: T) => {
  116. state.value = value;
  117. };
  118. signal.update = (updater: Updater<T>) => {
  119. state.value = updater(state.value);
  120. };
  121. signal.mutate = (mutator: Mutator<T>) => {
  122. mutator(state.value);
  123. triggerRef(state);
  124. };
  125. signal.getRef = () => state as Readonly<ShallowRef<Readonly<T>>>;
  126. return signal;
  127. }