@uni-store/react
Version:
Unified Store for React
156 lines (150 loc) • 5.42 kB
JavaScript
/*!
* @uni-store/react v0.3.5
* (c) 2022 dolymood (dolymood@gmail.com)
* @license MIT
*/
;
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;