@guanghechen/react-viewmodel
Version:
177 lines (167 loc) • 6.42 kB
JavaScript
export * from '@guanghechen/viewmodel';
import * as React from 'react';
import React__default, { useState as useState$1, useLayoutEffect, useEffect, useDebugValue } from 'react';
import { Subscriber } from '@guanghechen/subscriber';
let didWarnOld18Alpha = false;
let didWarnUncachedGetSnapshot = false;
function useSyncExternalStore$2(subscribe, getSnapshot, _getServerSnapshot) {
if (process.env.NODE_ENV === 'development') {
if (!didWarnOld18Alpha) {
if (React.startTransition !== undefined) {
didWarnOld18Alpha = true;
console.error('You are using an outdated, pre-release alpha of React 18 that ' +
'does not support useSyncExternalStore. The ' +
'use-sync-external-store shim will not work correctly. Upgrade ' +
'to a newer pre-release.');
}
}
}
const value = getSnapshot();
if (process.env.NODE_ENV === 'development') {
if (!didWarnUncachedGetSnapshot) {
const cachedValue = getSnapshot();
if (!Object.is(value, cachedValue)) {
console.error('The result of getSnapshot should be cached to avoid an infinite loop');
didWarnUncachedGetSnapshot = true;
}
}
}
const [{ inst }, forceUpdate] = useState$1({ inst: { value, getSnapshot } });
useLayoutEffect(() => {
inst.value = value;
inst.getSnapshot = getSnapshot;
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
}, [subscribe, value, getSnapshot]);
useEffect(() => {
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
const handleStoreChange = () => {
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
};
return subscribe(handleStoreChange);
}, [subscribe]);
useDebugValue(value);
return value;
}
function checkIfSnapshotChanged(inst) {
const latestGetSnapshot = inst.getSnapshot;
const prevValue = inst.value;
try {
const nextValue = latestGetSnapshot();
return !Object.is(prevValue, nextValue);
}
catch (_) {
return true;
}
}
function useSyncExternalStore$1(_subscribe, getSnapshot, _getServerSnapshot) {
return getSnapshot();
}
const canUseDOM = !!(typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined');
const shim = canUseDOM ? useSyncExternalStore$2 : useSyncExternalStore$1;
const builtin = React.useSyncExternalStore;
const useSyncExternalStore = builtin ? builtin : shim;
function useComputed(computed$) {
const { getSnapshot, getServerSnapshot, subscribeStateChange } = computed$;
return useSyncExternalStore(subscribeStateChange, getSnapshot, getServerSnapshot);
}
function useObserveKey(collection, key) {
const [state, setState] = React__default.useState(collection.get(key));
React__default.useEffect(() => {
const subscriber = new Subscriber({
onNext: v => setState(v),
});
const unsubscribable = collection.subscribeKey(key, subscriber);
return () => {
subscriber.dispose();
unsubscribable.unsubscribe();
};
}, [collection, key]);
return state;
}
function useObserveSignal(viewmodel, keys) {
const { ticker } = React__default.useMemo(() => viewmodel.ticker(keys), [viewmodel, ...keys]);
const [signal, setSignal] = React__default.useState(0);
React__default.useEffect(() => {
const subscriber = new Subscriber({
onNext: () => setSignal(s => s + 1),
});
const unsubscribable = ticker.subscribe(subscriber);
return () => {
subscriber.dispose();
unsubscribable.unsubscribe();
};
}, [ticker]);
return signal;
}
function useStateValue(state$) {
const { getSnapshot, getServerSnapshot, subscribeStateChange } = state$;
return useSyncExternalStore(subscribeStateChange, getSnapshot, getServerSnapshot);
}
function useSetState(state$) {
const { setState } = state$;
return setState;
}
function useState(state$) {
const { getSnapshot, getServerSnapshot, subscribeStateChange, setState } = state$;
const state = useSyncExternalStore(subscribeStateChange, getSnapshot, getServerSnapshot);
return [state, setState];
}
function useReactState(state$) {
const { getSnapshot, getServerSnapshot, subscribeStateChange, updateState } = state$;
const state = useSyncExternalStore(subscribeStateChange, getSnapshot, getServerSnapshot);
return [state, updateState];
}
function useToggleState(state$) {
const { setState } = state$;
return React__default.useCallback(() => {
setState(flag => !flag);
}, [setState]);
}
const useViewModel = (fn) => {
const ref = React__default.useRef(null);
const [_, setTick] = React__default.useState(0);
const fnRef = React__default.useRef(fn);
fnRef.current = fn;
React__default.useEffect(() => {
let cancelled = false;
if (!ref.current) {
const promise = Promise.resolve(fnRef.current());
void promise
.then((viewmodel) => {
ref.current = viewmodel;
const name = viewmodel.name || viewmodel.constructor.name;
console.log(`[useViewModel] creating ${name}.`);
if (!cancelled) {
setTick(c => c + 1);
}
})
.catch(error => {
ref.current = null;
console.log('[useViewModel] failed. error:', error);
if (!cancelled) {
setTick(c => c + 1);
}
});
}
return () => {
cancelled = true;
const viewmodel = ref.current;
if (!viewmodel)
return;
const name = viewmodel.name || viewmodel.constructor.name;
console.log(`[useViewModel] disposing ${name}.`);
ref.current = null;
viewmodel.dispose();
};
}, []);
return ref.current;
};
export { useComputed, useObserveKey, useObserveSignal, useReactState, useSetState, useState, useStateValue, useSyncExternalStore, useToggleState, useViewModel };