UNPKG

@uni-store/react

Version:
156 lines (150 loc) 5.42 kB
/*! * @uni-store/react v0.3.5 * (c) 2022 dolymood (dolymood@gmail.com) * @license MIT */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var react = require('react'); var core = require('@uni-store/core'); function useSetup(setup, props) { if (Array.isArray(props)) { // props is DependencyList return react.useMemo(() => core.reactive(setup()), props); } let propsRef = react.useRef(); let resultRef = react.useRef(); const setupState = react.useMemo(() => { if (!resultRef.current) { // first in propsRef.current = core.reactive(Object.assign({}, props || {})); resultRef.current = core.reactive(setup(propsRef.current)); } else { // keep same refer, just update propsRef.current Object.assign(propsRef.current, props); } return resultRef.current; }, [props]); return setupState; } function defineSetup(setup) { return (props) => { if (props) { return useSetup(setup, props); } else { return useSetup(setup); } }; } // clone from https://github.com/mobxjs/mobx/blob/HEAD/packages/mobx-react-lite/src/observer.ts function reactiveReact(baseComponent, options) { const realOptions = { forwardRef: false, ...options }; const baseComponentName = baseComponent.displayName || baseComponent.name; const wrappedComponent = (props, ref) => { return useReactive(() => baseComponent(props, ref)); }; wrappedComponent.displayName = baseComponentName; // Support legacy context: `contextTypes` must be applied before `memo` if (baseComponent.contextTypes) { wrappedComponent.contextTypes = baseComponent.contextTypes; } // todo memo, check props.children change for better performance // fornow props.children always be unequal // because react only do shallowly compare // memo; we are not interested in deep updates // in props; we assume that if deep objects are changed, // this is in observables, which would have been tracked anyway let memoComponent; if (realOptions.forwardRef) { // we have to use forwardRef here because: // 1. it cannot go before memo, only after it // 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it // since it wouldn't be a callable function anymore memoComponent = react.memo(react.forwardRef(wrappedComponent)); } else { memoComponent = react.memo(wrappedComponent); } copyStaticProperties(baseComponent, memoComponent); memoComponent.displayName = baseComponentName; return memoComponent; } // based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js const hoistBlackList = { $$typeof: true, render: true, compare: true, type: true }; function copyStaticProperties(base, target) { Object.keys(base).forEach(key => { if (!hoistBlackList[key]) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)); } }); } // todo use latest observer, support react StrictMode/ConcurrentMode/Suspense modes // https://github.com/mobxjs/mobx/blob/3fa1f4d48c4b9b306ddec40e14c07ed183fb0c18/packages/mobx-react-lite/src/useObserver.ts const NO_RENDERED = {}; const RENDERING = {}; function useReactive(fn) { // todo: necessary ? const scopeRef = react.useRef(null); const stopWatchRef = react.useRef(null); const updatedRef = react.useRef(false); const forceUpdate = useForceUpdate(); let rendering = NO_RENDERED; if (!scopeRef.current) { scopeRef.current = core.effectScope(true); } const scope = scopeRef.current; // clear effects, re collect deps scope.effects.length = 0; scope.run(() => { stopWatchRef.current && stopWatchRef.current(); stopWatchRef.current = core.watchSyncEffect(() => { if (rendering !== NO_RENDERED) { // deps change trigger rerender // just forceUpdate if (rendering === RENDERING) ; else { updatedRef.current && core.nextTick(() => { !updatedRef.current && forceUpdate(); }); updatedRef.current = false; } // no deps now } else { // new render // collect deps updatedRef.current = true; rendering = RENDERING; rendering = fn(); } }); }); react.useEffect(() => () => { if (stopWatchRef.current) { stopWatchRef.current(); stopWatchRef.current = null; } scope.stop(); scopeRef.current = null; updatedRef.current = true; }, []); return rendering; } function useForceUpdate() { const [, setState] = react.useState(); const forceUpdate = () => setState([]); return forceUpdate; } exports.defineSetup = defineSetup; exports.reactiveReact = reactiveReact; exports.useSetup = useSetup;