refactor(hooks): refactor useSignal, useComputed

This commit is contained in:
Soybean 2024-04-25 13:08:38 +08:00
parent dcd51f4cda
commit 3b5e4b3405
3 changed files with 106 additions and 82 deletions

View File

@ -7,5 +7,5 @@ import useHookTable from './use-table';
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
export * from './use-state';
export * from './use-signal';
export * from './use-table';

View File

@ -0,0 +1,105 @@
import { computed, shallowRef, triggerRef } from 'vue';
import type { ComputedGetter, DebuggerOptions, ShallowRef, WritableComputedOptions, WritableComputedRef } from 'vue';
type Updater<T> = (value: T) => T;
type Mutator<T> = (value: T) => void;
/**
* Signal is a reactive value that can be set, updated or mutated
*
* ```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<T> {
(): Readonly<T>;
/**
* 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<T>): 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<T>): void;
}
export interface ReadonlySignal<T> {
(): Readonly<T>;
}
export function useSignal<T>(initialValue: T): Signal<T> {
const state = shallowRef(initialValue);
return createSignal(state);
}
export function useComputed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ReadonlySignal<T>;
export function useComputed<T>(options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions): Signal<T>;
export function useComputed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
) {
const isGetter = typeof getterOrOptions === 'function';
const computedValue = computed(getterOrOptions as any, debugOptions);
if (isGetter) {
return () => computedValue.value as ReadonlySignal<T>;
}
return createSignal(computedValue);
}
function createSignal<T>(state: ShallowRef<T> | WritableComputedRef<T>): Signal<T> {
const signal = () => state.value;
signal.set = (value: T) => {
state.value = value;
};
signal.update = (updater: Updater<T>) => {
state.value = updater(state.value);
};
signal.mutate = (mutator: Mutator<T>) => {
mutator(state.value);
triggerRef(state);
};
return signal;
}

View File

@ -1,81 +0,0 @@
import { ref } from 'vue';
import type { Ref } from 'vue';
/**
* useRef
*
* it is a simple ref management hook wrapped by vue3's ref function.
*
* to resolve the ref type problem about `UnwrapRef`
*
* @param initValue
*/
export function useRef<T>(initValue: T) {
const refValue = ref(initValue) as Ref<T>;
return refValue;
}
/**
* useState
*
* define a state and a setState function
*
* @param initValue
*/
export function useState<T>(initValue: T) {
const state = useRef(initValue);
function setState(value: T) {
state.value = value;
}
return [state, setState] as const;
}
interface Signal<T> {
(): T;
/**
* the ref object of the signal, but it is readonly
*
* equal to `const ref = ref(initValue);`
*/
readonly ref: Readonly<Ref<T>>;
/**
* set the value of the signal
*
* @param value
*/
set(value: T): void;
/**
* update the value of the signal
*
* @param fn update function
*/
update(fn: (value: T) => T): void;
}
/**
* useSignal
*
* @param initValue
*/
export function useSignal<T>(initValue: T) {
const [state, setState] = useState(initValue);
function updateState(fn: (value: T) => T) {
const updatedValue = fn(state.value);
setState(updatedValue);
}
const signal = function signal() {
return state.value;
} as Signal<T>;
(signal as any).ref = state;
signal.set = setState;
signal.update = updateState;
return signal;
}