UNPKG

mmlpx

Version:

mobx model layer paradigm

148 lines (147 loc) 5.54 kB
import _pull from 'lodash/pull'; import _mergeWith from 'lodash/mergeWith'; /** * @author Kuitos * @homepage https://github.com/kuitos/ * @since 2018-06-25 17:01 */ import { _getGlobalState, comparer, reaction, runInAction } from 'mobx'; import { getInjector, setInjector } from '../core/dependency-inject/instantiate'; import { isArray, isMap, isObject } from '../utils/types'; import genReactiveInjector from './genReactiveInjector'; var SNAPSHOT_PHASE; (function (SNAPSHOT_PHASE) { SNAPSHOT_PHASE[SNAPSHOT_PHASE["PATCHING"] = 0] = "PATCHING"; SNAPSHOT_PHASE[SNAPSHOT_PHASE["DONE"] = 1] = "DONE"; })(SNAPSHOT_PHASE || (SNAPSHOT_PHASE = {})); var phase = SNAPSHOT_PHASE.DONE; /** * serialize and deep walk the models of injector to enable the dependencies tracking * @param model * @returns {Snapshot} serialization */ function walkAndSerialize(model) { // when model is an array, access the array length to enable tracking if (isArray(model)) { return model.length ? model.map(function (value) { return walkAndSerialize(value); }) : []; } // when model is a map, access the map size to enable tracking if (isMap(model)) { if (model.size) { var map_1 = {}; model.forEach(function (value, key) { map_1[key] = walkAndSerialize(value); }); return map_1; } return {}; } if (isObject(model)) { return Object.keys(model).reduce(function (acc, stateName) { acc[stateName] = walkAndSerialize(model[stateName]); return acc; }, {}); } return model; } /** * hijack the mobx global state to run a processor after all reactions finished * @see https://github.com/mobxjs/mobx/blob/master/src/core/reaction.ts#L242 * :dark magic: * @param {() => void} processor */ function processAfterReactionsFinished(processor) { // compatible with mobx 3 var getGlobalState = _getGlobalState || /* istanbul ignore next */require('mobx').extras.getGlobalState; var globalState = getGlobalState(); var previousDescriptor = Object.getOwnPropertyDescriptor(globalState, 'isRunningReactions'); var prevValue = globalState.isRunningReactions; Object.defineProperty(globalState, 'isRunningReactions', { get: function () { return prevValue; }, set: function (v) { prevValue = v; if (v === false) { Object.defineProperty(globalState, 'isRunningReactions', previousDescriptor); processor(); } } }); } export function applySnapshot(snapshot, injector) { if (injector === void 0) { injector = getInjector(); } if (isObject(snapshot)) { patchSnapshot(snapshot, injector); } } export function patchSnapshot(patcher, injector) { if (injector === void 0) { injector = getInjector(); } var currentModels = injector.dump(); phase = SNAPSHOT_PHASE.PATCHING; runInAction(function () { // make a copy of patcher to avoid referencing the original patcher after merge var clonedPatcher = JSON.parse(JSON.stringify(patcher)); var mergedModels = _mergeWith(currentModels, clonedPatcher, function (original, source) { // while source less than original, means the data list has items removed, so the overflowed data should be dropped if (isArray(original)) { original.length = source.length; } // while the keys of source object less than original, means some properties should be removed in original after patch if (isObject(original)) { _pull.apply(void 0, [Object.keys(original)].concat(Object.keys(source))).forEach(function (key) { return delete original[key]; }); } if (isMap(original)) { original.clear(); Object.keys(source).forEach(function (key) { original.set(key, source[key]); }); } }); injector.load(mergedModels); }); processAfterReactionsFinished(function () { return phase = SNAPSHOT_PHASE.DONE; }); } export function getSnapshot(arg1, arg2) { if (typeof arg1 === 'string') { var snapshot = walkAndSerialize((arg2 || getInjector()).dump()); return snapshot[arg1]; } else { return walkAndSerialize((arg1 || getInjector()).dump()); } } export function onSnapshot(arg1, arg2, arg3) { var snapshot; var onChange; var injector; if (typeof arg1 === 'string') { onChange = arg2; injector = genReactiveInjector(arg3 || getInjector()); snapshot = function () { return getSnapshot(arg1, injector); }; } else { onChange = arg1; injector = genReactiveInjector(arg2 || getInjector()); snapshot = function () { return getSnapshot(injector); }; } setInjector(injector); var disposer = reaction(snapshot, function (changedSnapshot) { // only trigger snapshot listeners when snapshot processed if (phase === SNAPSHOT_PHASE.DONE) { onChange(changedSnapshot); } }, { equals: comparer.structural }); return disposer; }