import { computed, ref, shallowRef, triggerRef } from 'vue'; import type { ComputedGetter, DebuggerOptions, Ref, ShallowRef, WritableComputedOptions, WritableComputedRef } from 'vue'; type Updater = (value: T) => T; type Mutator = (value: T) => void; /** * Signal is a reactive value that can be set, updated or mutated * * @example * ```ts * const count = useSignal(0); * * // `watchEffect` * watchEffect(() => { * console.log(count()); * }); * * // watch * watch(count, value => { * console.log(value); * }); * * // useComputed * const double = useComputed(() => count() * 2); * const writeableDouble = useComputed({ * get: () => count() * 2, * set: value => count.set(value / 2) * }); * ``` */ export interface Signal { (): Readonly; /** * Set the value of the signal * * It recommend use `set` for primitive values * * @param value */ set(value: T): void; /** * Update the value of the signal using an updater function * * It recommend use `update` for non-primitive values, only the first level of the object will be reactive. * * @param updater */ update(updater: Updater): void; /** * Mutate the value of the signal using a mutator function * * this action will call `triggerRef`, so the value will be tracked on `watchEffect`. * * It recommend use `mutate` for non-primitive values, all levels of the object will be reactive. * * @param mutator */ mutate(mutator: Mutator): void; /** * Get the reference of the signal * * Sometimes it can be useful to make `v-model` work with the signal * * ```vue * ; * * * ``` */ getRef(): Readonly>>; } export interface ReadonlySignal { (): Readonly; } export interface SignalOptions { /** * Whether to use `ref` to store the value * * @default false use `sharedRef` to store the value */ useRef?: boolean; } export function useSignal(initialValue: T, options?: SignalOptions): Signal { const { useRef } = options || {}; const state = useRef ? (ref(initialValue) as Ref) : shallowRef(initialValue); return createSignal(state); } export function useComputed(getter: ComputedGetter, debugOptions?: DebuggerOptions): ReadonlySignal; export function useComputed(options: WritableComputedOptions, debugOptions?: DebuggerOptions): Signal; export function useComputed( getterOrOptions: ComputedGetter | WritableComputedOptions, debugOptions?: DebuggerOptions ) { const isGetter = typeof getterOrOptions === 'function'; const computedValue = computed(getterOrOptions as any, debugOptions); if (isGetter) { return () => computedValue.value as ReadonlySignal; } return createSignal(computedValue); } function createSignal(state: ShallowRef | WritableComputedRef): Signal { const signal = () => state.value; signal.set = (value: T) => { state.value = value; }; signal.update = (updater: Updater) => { state.value = updater(state.value); }; signal.mutate = (mutator: Mutator) => { mutator(state.value); triggerRef(state); }; signal.getRef = () => state as Readonly>>; return signal; }