UNPKG

vue-reactivity-fantasy

Version:
298 lines (279 loc) 7.68 kB
import { computed, effectScope, isRef, isReadonly, isReactive, unref, ref, watch, watchEffect, shallowRef } from 'vue'; import { isVue2 } from 'vue-module-demi'; function untrack(fn) { const reactiveScope = computed(() => fn()); const reactiveEffect = reactiveScope.effect; if (isVue2) { reactiveEffect.teardown(); return reactiveEffect.get(); } else { const result = reactiveScope.value; reactiveEffect.stop(); return result; } } const tick = /* @__PURE__ */ Promise.resolve(); const queue = []; let queued = false; const scheduler = (fn) => { queue.push(fn); if (!queued) { queued = true; tick.then(flush); } }; const flush = () => { for (let i = 0; i < queue.length; i++) { queue[i](); } queue.length = 0; queued = false; }; function batch(fn) { const reactiveScope = computed(() => fn()); const reactiveEffect = reactiveScope.effect; let scheduled = false; if (isVue2) { const [track, trigger] = useForceUpdate(); let value; untrack(() => { const scope = effectScope(true); scope.run(() => { value = reactiveScope.value; if (scheduled) { return; } scheduler(() => { trigger(); scheduled = false; }); scheduled = false; }); }); track(); return value; } else { const originScheduler = reactiveEffect.scheduler; reactiveEffect.scheduler = () => { if (scheduled) { return; } scheduler(() => { if (reactiveEffect.active) { if (originScheduler) { originScheduler(); } else { reactiveEffect.run(); } } scheduled = false; }); scheduled = false; }; } return reactiveScope.value; } function isWritableRef(v) { return isRef(v) && !isReadonly(v); } function isValidDependency(dep) { return isRef(dep) || isReactive(dep) || isFunction(dep) || Array.isArray(dep) && dep.every(isValidDependency); } function isFunction(val) { return typeof val === "function"; } function createManualEffect(setup, immediate = false) { let state = 0; let cleanup; const onCleanup = (fn) => { cleanup = fn; }; const clear = () => { isFunction(cleanup) && cleanup(); cleanup = void 0; state = 0; }; const doSetup = () => { if (isFunction(setup)) { const returnSetup = setup(onCleanup); if (!isFunction(cleanup) && isFunction(returnSetup)) { cleanup = returnSetup; } state++; } }; const ensure = () => { if (!state) { doSetup(); } }; const reset = (overrideSetup) => { if (isFunction(overrideSetup)) { setup = overrideSetup; } clear(); doSetup(); }; if (immediate) { doSetup(); } return { clear, ensure, reset }; } function resolveSourceValue(source) { return resolveSourceValueGetter(source)(); } function resolveSourceValueGetter(source) { return isFunction(source) ? source : () => unref(source); } function resolveComputed(source) { return computed(resolveSourceValueGetter(source)); } function createToggleControl(value, defaultValue, reverseValue) { const getDefaultValue = resolveSourceValueGetter(defaultValue); const getReverseValue = resolveSourceValueGetter(reverseValue); return { value, set: (newValue) => value.value = newValue, setDefault: () => value.value = getDefaultValue(), setReverse: () => value.value = getReverseValue(), toggle: () => { const defaultValue2 = getDefaultValue(); value.value = value.value === defaultValue2 ? getReverseValue() : defaultValue2; } }; } function useBoolean(initialValue = false) { const stateRef = isWritableRef(initialValue) ? initialValue : ref(!!unref(initialValue)); const toggleControl = createToggleControl( stateRef, () => true, () => false ); return [ stateRef, { set: toggleControl.set, setTrue: toggleControl.setDefault, setFalse: toggleControl.setReverse, toggle: toggleControl.toggle } ]; } function useComputed(source, setter, debugOptions) { const getter = resolveSourceValueGetter(source); const options = typeof setter === "object" ? setter : debugOptions; return isFunction(setter) ? computed( { get: getter, set: setter }, options ) : computed(getter, options); } function useEffect(setup, dependency, options) { const control = createManualEffect(); if (isValidDependency(dependency)) { return watch( dependency, (dependency2, lastDependency, onCleanup) => { control.reset((onCleanup2) => setup(onCleanup2, dependency2, lastDependency)); onCleanup(control.clear); }, options ); } return watchEffect( (onCleanup) => { control.reset((onCleanup2) => setup(onCleanup2)); onCleanup(control.clear); }, { ...options, immediate: void 0 } ); } function useMemo(source, dependency, options) { const stateRef = ref(); const getter = resolveSourceValueGetter(source); useEffect( (_, dependency2, lastDependency) => { const currentState = getter(dependency2, lastDependency); if (!Object.is(stateRef.value, currentState)) { stateRef.value = currentState; } }, dependency, { ...options, immediate: true } ); return useComputed(stateRef); } function useTransformState(source, transform) { const getter = resolveSourceValueGetter(source); return computed(() => transform(getter())); } function useDerivedState(props, getDerivedStateFromProps) { let prevState; return useMemo( useTransformState(props, (props2) => prevState = getDerivedStateFromProps(props2, prevState)) ); } function useFollowState(initialValue, target, options) { const stateRef = isWritableRef(initialValue) ? initialValue : ref(unref(initialValue)); const stopFollow = useEffect( (_, target2) => { stateRef.value = target2; options?.onFollow?.(); }, target, options ); return [stateRef, stopFollow]; } function useForceUpdate() { const signal = ref(0); return [ () => signal.value, () => { signal.value++; } ]; } function useMergedState(source, defaultValue) { const getValue = resolveSourceValueGetter(source); const getDefaultValue = resolveSourceValueGetter(defaultValue); const getter = () => { const value = getValue(); return value === void 0 ? getDefaultValue() : value; }; return computed(getter); } function useState(initialValue, shallow) { const createRef = shallow ? shallowRef : ref; const state = createRef(resolveSourceValue(initialValue)); return [ state, (value) => { state.value = value; } ]; } function useToggle(defaultValue = false, reverseValue) { const asBoolean = arguments.length <= 1; if (asBoolean && reverseValue === void 0) { reverseValue = !resolveSourceValue(defaultValue); } const transform = asBoolean ? (value) => Boolean(value) : (value) => value; const defaultValueRef = useTransformState(defaultValue, transform); const reverseValueRef = useTransformState(reverseValue, transform); const stateRef = ref(defaultValueRef.value); return [stateRef, createToggleControl(stateRef, defaultValueRef, reverseValueRef)]; } export { batch, createManualEffect, createToggleControl, isValidDependency, isWritableRef, resolveComputed, resolveSourceValue, resolveSourceValueGetter, untrack, useBoolean, useComputed, useDerivedState, useEffect, useFollowState, useForceUpdate, useMemo, useMergedState, useState, useToggle, useTransformState };