UNPKG

@guanghechen/react-viewmodel

Version:
177 lines (167 loc) 6.42 kB
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 };