vue-reactivity-fantasy
Version:
Some fancy functionalities about vue reactivity
319 lines (299 loc) • 8.21 kB
JavaScript
;
const vue = require('vue');
const vueModuleDemi = require('vue-module-demi');
function untrack(fn) {
const reactiveScope = vue.computed(() => fn());
const reactiveEffect = reactiveScope.effect;
if (vueModuleDemi.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 = vue.computed(() => fn());
const reactiveEffect = reactiveScope.effect;
let scheduled = false;
if (vueModuleDemi.isVue2) {
const [track, trigger] = useForceUpdate();
let value;
untrack(() => {
const scope = vue.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 vue.isRef(v) && !vue.isReadonly(v);
}
function isValidDependency(dep) {
return vue.isRef(dep) || vue.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 : () => vue.unref(source);
}
function resolveComputed(source) {
return vue.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 : vue.ref(!!vue.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) ? vue.computed(
{
get: getter,
set: setter
},
options
) : vue.computed(getter, options);
}
function useEffect(setup, dependency, options) {
const control = createManualEffect();
if (isValidDependency(dependency)) {
return vue.watch(
dependency,
(dependency2, lastDependency, onCleanup) => {
control.reset((onCleanup2) => setup(onCleanup2, dependency2, lastDependency));
onCleanup(control.clear);
},
options
);
}
return vue.watchEffect(
(onCleanup) => {
control.reset((onCleanup2) => setup(onCleanup2));
onCleanup(control.clear);
},
{
...options,
immediate: void 0
}
);
}
function useMemo(source, dependency, options) {
const stateRef = vue.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 vue.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 : vue.ref(vue.unref(initialValue));
const stopFollow = useEffect(
(_, target2) => {
stateRef.value = target2;
options?.onFollow?.();
},
target,
options
);
return [stateRef, stopFollow];
}
function useForceUpdate() {
const signal = vue.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 vue.computed(getter);
}
function useState(initialValue, shallow) {
const createRef = shallow ? vue.shallowRef : vue.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 = vue.ref(defaultValueRef.value);
return [stateRef, createToggleControl(stateRef, defaultValueRef, reverseValueRef)];
}
exports.batch = batch;
exports.createManualEffect = createManualEffect;
exports.createToggleControl = createToggleControl;
exports.isValidDependency = isValidDependency;
exports.isWritableRef = isWritableRef;
exports.resolveComputed = resolveComputed;
exports.resolveSourceValue = resolveSourceValue;
exports.resolveSourceValueGetter = resolveSourceValueGetter;
exports.untrack = untrack;
exports.useBoolean = useBoolean;
exports.useComputed = useComputed;
exports.useDerivedState = useDerivedState;
exports.useEffect = useEffect;
exports.useFollowState = useFollowState;
exports.useForceUpdate = useForceUpdate;
exports.useMemo = useMemo;
exports.useMergedState = useMergedState;
exports.useState = useState;
exports.useToggle = useToggle;
exports.useTransformState = useTransformState;