UNPKG

mobx

Version:

Simple, scalable state management.

1,266 lines (1,243 loc) 175 kB
/** MobX - (c) Michel Weststrate 2015 - 2019 - MIT Licensed */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __values(o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } function __spread() { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; } var OBFUSCATED_ERROR = "An invariant failed, however the error is obfuscated because this is an production build."; var EMPTY_ARRAY = []; Object.freeze(EMPTY_ARRAY); var EMPTY_OBJECT = {}; Object.freeze(EMPTY_OBJECT); function getNextId() { return ++globalState.mobxGuid; } function fail(message) { invariant(false, message); throw "X"; // unreachable } function invariant(check, message) { if (!check) throw new Error("[mobx] " + (message || OBFUSCATED_ERROR)); } /** * Prints a deprecation message, but only one time. * Returns false if the deprecated message was already printed before */ var deprecatedMessages = []; function deprecated(msg, thing) { if (process.env.NODE_ENV === "production") return false; if (thing) { return deprecated("'" + msg + "', use '" + thing + "' instead."); } if (deprecatedMessages.indexOf(msg) !== -1) return false; deprecatedMessages.push(msg); console.error("[mobx] Deprecated: " + msg); return true; } /** * Makes sure that the provided function is invoked at most once. */ function once(func) { var invoked = false; return function () { if (invoked) return; invoked = true; return func.apply(this, arguments); }; } var noop = function () { }; function unique(list) { var res = []; list.forEach(function (item) { if (res.indexOf(item) === -1) res.push(item); }); return res; } function isObject(value) { return value !== null && typeof value === "object"; } function isPlainObject(value) { if (value === null || typeof value !== "object") return false; var proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } function addHiddenProp(object, propName, value) { Object.defineProperty(object, propName, { enumerable: false, writable: true, configurable: true, value: value }); } function addHiddenFinalProp(object, propName, value) { Object.defineProperty(object, propName, { enumerable: false, writable: false, configurable: true, value: value }); } function isPropertyConfigurable(object, prop) { var descriptor = Object.getOwnPropertyDescriptor(object, prop); return !descriptor || (descriptor.configurable !== false && descriptor.writable !== false); } function assertPropertyConfigurable(object, prop) { if (process.env.NODE_ENV !== "production" && !isPropertyConfigurable(object, prop)) fail("Cannot make property '" + prop.toString() + "' observable, it is not configurable and writable in the target object"); } function createInstanceofPredicate(name, clazz) { var propName = "isMobX" + name; clazz.prototype[propName] = true; return function (x) { return isObject(x) && x[propName] === true; }; } /** * Returns whether the argument is an array, disregarding observability. */ function isArrayLike(x) { return Array.isArray(x) || isObservableArray(x); } function isES6Map(thing) { return thing instanceof Map; } function isES6Set(thing) { return thing instanceof Set; } /** * Returns the following: own keys, prototype keys & own symbol keys, if they are enumerable. */ function getPlainObjectKeys(object) { var enumerables = new Set(); for (var key in object) enumerables.add(key); // *all* enumerables Object.getOwnPropertySymbols(object).forEach(function (k) { if (Object.getOwnPropertyDescriptor(object, k).enumerable) enumerables.add(k); }); // *own* symbols // Note: this implementation is missing enumerable, inherited, symbolic property names! That would however pretty expensive to add, // as there is no efficient iterator that returns *all* properties return Array.from(enumerables); } function stringifyKey(key) { if (key && key.toString) return key.toString(); else return new String(key).toString(); } function getMapLikeKeys(map) { if (isPlainObject(map)) return Object.keys(map); if (Array.isArray(map)) return map.map(function (_a) { var _b = __read(_a, 1), key = _b[0]; return key; }); if (isES6Map(map) || isObservableMap(map)) return Array.from(map.keys()); return fail("Cannot get keys from '" + map + "'"); } function toPrimitive(value) { return value === null ? null : typeof value === "object" ? "" + value : value; } var $mobx = Symbol("mobx administration"); var Atom = /** @class */ (function () { /** * Create a new atom. For debugging purposes it is recommended to give it a name. * The onBecomeObserved and onBecomeUnobserved callbacks can be used for resource management. */ function Atom(name) { if (name === void 0) { name = "Atom@" + getNextId(); } this.name = name; this.isPendingUnobservation = false; // for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed this.isBeingObserved = false; this.observers = new Set(); this.diffValue = 0; this.lastAccessedBy = 0; this.lowestObserverState = exports.IDerivationState.NOT_TRACKING; } Atom.prototype.onBecomeObserved = function () { if (this.onBecomeObservedListeners) { this.onBecomeObservedListeners.forEach(function (listener) { return listener(); }); } }; Atom.prototype.onBecomeUnobserved = function () { if (this.onBecomeUnobservedListeners) { this.onBecomeUnobservedListeners.forEach(function (listener) { return listener(); }); } }; /** * Invoke this method to notify mobx that your atom has been used somehow. * Returns true if there is currently a reactive context. */ Atom.prototype.reportObserved = function () { return reportObserved(this); }; /** * Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate. */ Atom.prototype.reportChanged = function () { startBatch(); propagateChanged(this); endBatch(); }; Atom.prototype.toString = function () { return this.name; }; return Atom; }()); var isAtom = createInstanceofPredicate("Atom", Atom); function createAtom(name, onBecomeObservedHandler, onBecomeUnobservedHandler) { if (onBecomeObservedHandler === void 0) { onBecomeObservedHandler = noop; } if (onBecomeUnobservedHandler === void 0) { onBecomeUnobservedHandler = noop; } var atom = new Atom(name); // default `noop` listener will not initialize the hook Set if (onBecomeObservedHandler !== noop) { onBecomeObserved(atom, onBecomeObservedHandler); } if (onBecomeUnobservedHandler !== noop) { onBecomeUnobserved(atom, onBecomeUnobservedHandler); } return atom; } function identityComparer(a, b) { return a === b; } function structuralComparer(a, b) { return deepEqual(a, b); } function defaultComparer(a, b) { return Object.is(a, b); } var comparer = { identity: identityComparer, structural: structuralComparer, default: defaultComparer }; var mobxDidRunLazyInitializersSymbol = Symbol("mobx did run lazy initializers"); var mobxPendingDecorators = Symbol("mobx pending decorators"); var enumerableDescriptorCache = {}; var nonEnumerableDescriptorCache = {}; function createPropertyInitializerDescriptor(prop, enumerable) { var cache = enumerable ? enumerableDescriptorCache : nonEnumerableDescriptorCache; return (cache[prop] || (cache[prop] = { configurable: true, enumerable: enumerable, get: function () { initializeInstance(this); return this[prop]; }, set: function (value) { initializeInstance(this); this[prop] = value; } })); } function initializeInstance(target) { if (target[mobxDidRunLazyInitializersSymbol] === true) return; var decorators = target[mobxPendingDecorators]; if (decorators) { addHiddenProp(target, mobxDidRunLazyInitializersSymbol, true); for (var key in decorators) { var d = decorators[key]; d.propertyCreator(target, d.prop, d.descriptor, d.decoratorTarget, d.decoratorArguments); } } } function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) { return function decoratorFactory() { var decoratorArguments; var decorator = function decorate(target, prop, descriptor, applyImmediately // This is a special parameter to signal the direct application of a decorator, allow extendObservable to skip the entire type decoration part, // as the instance to apply the decorator to equals the target ) { if (applyImmediately === true) { propertyCreator(target, prop, descriptor, target, decoratorArguments); return null; } if (process.env.NODE_ENV !== "production" && !quacksLikeADecorator(arguments)) fail("This function is a decorator, but it wasn't invoked like a decorator"); if (!Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) { var inheritedDecorators = target[mobxPendingDecorators]; addHiddenProp(target, mobxPendingDecorators, __assign({}, inheritedDecorators)); } target[mobxPendingDecorators][prop] = { prop: prop, propertyCreator: propertyCreator, descriptor: descriptor, decoratorTarget: target, decoratorArguments: decoratorArguments }; return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable); }; if (quacksLikeADecorator(arguments)) { // @decorator decoratorArguments = EMPTY_ARRAY; return decorator.apply(null, arguments); } else { // @decorator(args) decoratorArguments = Array.prototype.slice.call(arguments); return decorator; } }; } function quacksLikeADecorator(args) { return (((args.length === 2 || args.length === 3) && typeof args[1] === "string") || (args.length === 4 && args[3] === true)); } function deepEnhancer(v, _, name) { // it is an observable already, done if (isObservable(v)) return v; // something that can be converted and mutated? if (Array.isArray(v)) return observable.array(v, { name: name }); if (isPlainObject(v)) return observable.object(v, undefined, { name: name }); if (isES6Map(v)) return observable.map(v, { name: name }); if (isES6Set(v)) return observable.set(v, { name: name }); return v; } function shallowEnhancer(v, _, name) { if (v === undefined || v === null) return v; if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v) || isObservableSet(v)) return v; if (Array.isArray(v)) return observable.array(v, { name: name, deep: false }); if (isPlainObject(v)) return observable.object(v, undefined, { name: name, deep: false }); if (isES6Map(v)) return observable.map(v, { name: name, deep: false }); if (isES6Set(v)) return observable.set(v, { name: name, deep: false }); return fail(process.env.NODE_ENV !== "production" && "The shallow modifier / decorator can only used in combination with arrays, objects, maps and sets"); } function referenceEnhancer(newValue) { // never turn into an observable return newValue; } function refStructEnhancer(v, oldValue, name) { if (process.env.NODE_ENV !== "production" && isObservable(v)) throw "observable.struct should not be used with observable values"; if (deepEqual(v, oldValue)) return oldValue; return v; } function createDecoratorForEnhancer(enhancer) { invariant(enhancer); var decorator = createPropDecorator(true, function (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) { if (process.env.NODE_ENV !== "production") { invariant(!descriptor || !descriptor.get, "@observable cannot be used on getter (property \"" + stringifyKey(propertyName) + "\"), use @computed instead."); } var initialValue = descriptor ? descriptor.initializer ? descriptor.initializer.call(target) : descriptor.value : undefined; asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer); }); var res = // Extra process checks, as this happens during module initialization typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" ? function observableDecorator() { // This wrapper function is just to detect illegal decorator invocations, deprecate in a next version // and simply return the created prop decorator if (arguments.length < 2) return fail("Incorrect decorator invocation. @observable decorator doesn't expect any arguments"); return decorator.apply(null, arguments); } : decorator; res.enhancer = enhancer; return res; } // Predefined bags of create observable options, to avoid allocating temporarily option objects // in the majority of cases var defaultCreateObservableOptions = { deep: true, name: undefined, defaultDecorator: undefined, proxy: true }; Object.freeze(defaultCreateObservableOptions); function assertValidOption(key) { if (!/^(deep|name|equals|defaultDecorator|proxy)$/.test(key)) fail("invalid option for (extend)observable: " + key); } function asCreateObservableOptions(thing) { if (thing === null || thing === undefined) return defaultCreateObservableOptions; if (typeof thing === "string") return { name: thing, deep: true, proxy: true }; if (process.env.NODE_ENV !== "production") { if (typeof thing !== "object") return fail("expected options object"); Object.keys(thing).forEach(assertValidOption); } return thing; } var deepDecorator = createDecoratorForEnhancer(deepEnhancer); var shallowDecorator = createDecoratorForEnhancer(shallowEnhancer); var refDecorator = createDecoratorForEnhancer(referenceEnhancer); var refStructDecorator = createDecoratorForEnhancer(refStructEnhancer); function getEnhancerFromOptions(options) { return options.defaultDecorator ? options.defaultDecorator.enhancer : options.deep === false ? referenceEnhancer : deepEnhancer; } /** * Turns an object, array or function into a reactive structure. * @param v the value which should become observable. */ function createObservable(v, arg2, arg3) { // @observable someProp; if (typeof arguments[1] === "string") { return deepDecorator.apply(null, arguments); } // it is an observable already, done if (isObservable(v)) return v; // something that can be converted and mutated? var res = isPlainObject(v) ? observable.object(v, arg2, arg3) : Array.isArray(v) ? observable.array(v, arg2) : isES6Map(v) ? observable.map(v, arg2) : isES6Set(v) ? observable.set(v, arg2) : v; // this value could be converted to a new observable data structure, return it if (res !== v) return res; // otherwise, just box it fail(process.env.NODE_ENV !== "production" && "The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'"); } var observableFactories = { box: function (value, options) { if (arguments.length > 2) incorrectlyUsedAsDecorator("box"); var o = asCreateObservableOptions(options); return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals); }, array: function (initialValues, options) { if (arguments.length > 2) incorrectlyUsedAsDecorator("array"); var o = asCreateObservableOptions(options); return createObservableArray(initialValues, getEnhancerFromOptions(o), o.name); }, map: function (initialValues, options) { if (arguments.length > 2) incorrectlyUsedAsDecorator("map"); var o = asCreateObservableOptions(options); return new ObservableMap(initialValues, getEnhancerFromOptions(o), o.name); }, set: function (initialValues, options) { if (arguments.length > 2) incorrectlyUsedAsDecorator("set"); var o = asCreateObservableOptions(options); return new ObservableSet(initialValues, getEnhancerFromOptions(o), o.name); }, object: function (props, decorators, options) { if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("object"); var o = asCreateObservableOptions(options); if (o.proxy === false) { return extendObservable({}, props, decorators, o); } else { var defaultDecorator = getDefaultDecoratorFromObjectOptions(o); var base = extendObservable({}, undefined, undefined, o); var proxy = createDynamicObservableObject(base); extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator); return proxy; } }, ref: refDecorator, shallow: shallowDecorator, deep: deepDecorator, struct: refStructDecorator }; var observable = createObservable; // weird trick to keep our typings nicely with our funcs, and still extend the observable function Object.keys(observableFactories).forEach(function (name) { return (observable[name] = observableFactories[name]); }); function incorrectlyUsedAsDecorator(methodName) { fail( // process.env.NODE_ENV !== "production" && "Expected one or two arguments to observable." + methodName + ". Did you accidentally try to use observable." + methodName + " as decorator?"); } var computedDecorator = createPropDecorator(false, function (instance, propertyName, descriptor, decoratorTarget, decoratorArgs) { var get = descriptor.get, set = descriptor.set; // initialValue is the descriptor for get / set props // Optimization: faster on decorator target or instance? Assuming target // Optimization: find out if declaring on instance isn't just faster. (also makes the property descriptor simpler). But, more memory usage.. // Forcing instance now, fixes hot reloadig issues on React Native: var options = decoratorArgs[0] || {}; asObservableObject(instance).addComputedProp(instance, propertyName, __assign({ get: get, set: set, context: instance }, options)); }); var computedStructDecorator = computedDecorator({ equals: comparer.structural }); /** * Decorator for class properties: @computed get value() { return expr; }. * For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`; */ var computed = function computed(arg1, arg2, arg3) { if (typeof arg2 === "string") { // @computed return computedDecorator.apply(null, arguments); } if (arg1 !== null && typeof arg1 === "object" && arguments.length === 1) { // @computed({ options }) return computedDecorator.apply(null, arguments); } // computed(expr, options?) if (process.env.NODE_ENV !== "production") { invariant(typeof arg1 === "function", "First argument to `computed` should be an expression."); invariant(arguments.length < 3, "Computed takes one or two arguments if used as function"); } var opts = typeof arg2 === "object" ? arg2 : {}; opts.get = arg1; opts.set = typeof arg2 === "function" ? arg2 : opts.set; opts.name = opts.name || arg1.name || ""; /* for generated name */ return new ComputedValue(opts); }; computed.struct = computedStructDecorator; function createAction(actionName, fn, ref) { if (process.env.NODE_ENV !== "production") { invariant(typeof fn === "function", "`action` can only be invoked on functions"); if (typeof actionName !== "string" || !actionName) fail("actions should have valid names, got: '" + actionName + "'"); } var res = function () { return executeAction(actionName, fn, ref || this, arguments); }; res.isMobxAction = true; return res; } function executeAction(actionName, fn, scope, args) { var runInfo = startAction(actionName, fn, scope, args); var shouldSupressReactionError = true; try { var res = fn.apply(scope, args); shouldSupressReactionError = false; return res; } finally { if (shouldSupressReactionError) { globalState.suppressReactionErrors = shouldSupressReactionError; endAction(runInfo); globalState.suppressReactionErrors = false; } else { endAction(runInfo); } } } function startAction(actionName, fn, scope, args) { var notifySpy = isSpyEnabled() && !!actionName; var startTime = 0; if (notifySpy && process.env.NODE_ENV !== "production") { startTime = Date.now(); var l = (args && args.length) || 0; var flattendArgs = new Array(l); if (l > 0) for (var i = 0; i < l; i++) flattendArgs[i] = args[i]; spyReportStart({ type: "action", name: actionName, object: scope, arguments: flattendArgs }); } var prevDerivation = untrackedStart(); startBatch(); var prevAllowStateChanges = allowStateChangesStart(true); return { prevDerivation: prevDerivation, prevAllowStateChanges: prevAllowStateChanges, notifySpy: notifySpy, startTime: startTime }; } function endAction(runInfo) { allowStateChangesEnd(runInfo.prevAllowStateChanges); endBatch(); untrackedEnd(runInfo.prevDerivation); if (runInfo.notifySpy && process.env.NODE_ENV !== "production") spyReportEnd({ time: Date.now() - runInfo.startTime }); } function allowStateChanges(allowStateChanges, func) { var prev = allowStateChangesStart(allowStateChanges); var res; try { res = func(); } finally { allowStateChangesEnd(prev); } return res; } function allowStateChangesStart(allowStateChanges) { var prev = globalState.allowStateChanges; globalState.allowStateChanges = allowStateChanges; return prev; } function allowStateChangesEnd(prev) { globalState.allowStateChanges = prev; } function allowStateChangesInsideComputed(func) { var prev = globalState.computationDepth; globalState.computationDepth = 0; var res; try { res = func(); } finally { globalState.computationDepth = prev; } return res; } var ObservableValue = /** @class */ (function (_super) { __extends(ObservableValue, _super); function ObservableValue(value, enhancer, name, notifySpy, equals) { if (name === void 0) { name = "ObservableValue@" + getNextId(); } if (notifySpy === void 0) { notifySpy = true; } if (equals === void 0) { equals = comparer.default; } var _this = _super.call(this, name) || this; _this.enhancer = enhancer; _this.name = name; _this.equals = equals; _this.hasUnreportedChange = false; _this.value = enhancer(value, undefined, name); if (notifySpy && isSpyEnabled() && process.env.NODE_ENV !== "production") { // only notify spy if this is a stand-alone observable spyReport({ type: "create", name: _this.name, newValue: "" + _this.value }); } return _this; } ObservableValue.prototype.dehanceValue = function (value) { if (this.dehancer !== undefined) return this.dehancer(value); return value; }; ObservableValue.prototype.set = function (newValue) { var oldValue = this.value; newValue = this.prepareNewValue(newValue); if (newValue !== globalState.UNCHANGED) { var notifySpy = isSpyEnabled(); if (notifySpy && process.env.NODE_ENV !== "production") { spyReportStart({ type: "update", name: this.name, newValue: newValue, oldValue: oldValue }); } this.setNewValue(newValue); if (notifySpy && process.env.NODE_ENV !== "production") spyReportEnd(); } }; ObservableValue.prototype.prepareNewValue = function (newValue) { checkIfStateModificationsAreAllowed(this); if (hasInterceptors(this)) { var change = interceptChange(this, { object: this, type: "update", newValue: newValue }); if (!change) return globalState.UNCHANGED; newValue = change.newValue; } // apply modifier newValue = this.enhancer(newValue, this.value, this.name); return this.equals(this.value, newValue) ? globalState.UNCHANGED : newValue; }; ObservableValue.prototype.setNewValue = function (newValue) { var oldValue = this.value; this.value = newValue; this.reportChanged(); if (hasListeners(this)) { notifyListeners(this, { type: "update", object: this, newValue: newValue, oldValue: oldValue }); } }; ObservableValue.prototype.get = function () { this.reportObserved(); return this.dehanceValue(this.value); }; ObservableValue.prototype.intercept = function (handler) { return registerInterceptor(this, handler); }; ObservableValue.prototype.observe = function (listener, fireImmediately) { if (fireImmediately) listener({ object: this, type: "update", newValue: this.value, oldValue: undefined }); return registerListener(this, listener); }; ObservableValue.prototype.toJSON = function () { return this.get(); }; ObservableValue.prototype.toString = function () { return this.name + "[" + this.value + "]"; }; ObservableValue.prototype.valueOf = function () { return toPrimitive(this.get()); }; ObservableValue.prototype[Symbol.toPrimitive] = function () { return this.valueOf(); }; return ObservableValue; }(Atom)); var isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue); /** * A node in the state dependency root that observes other nodes, and can be observed itself. * * ComputedValue will remember the result of the computation for the duration of the batch, or * while being observed. * * During this time it will recompute only when one of its direct dependencies changed, * but only when it is being accessed with `ComputedValue.get()`. * * Implementation description: * 1. First time it's being accessed it will compute and remember result * give back remembered result until 2. happens * 2. First time any deep dependency change, propagate POSSIBLY_STALE to all observers, wait for 3. * 3. When it's being accessed, recompute if any shallow dependency changed. * if result changed: propagate STALE to all observers, that were POSSIBLY_STALE from the last step. * go to step 2. either way * * If at any point it's outside batch and it isn't observed: reset everything and go to 1. */ var ComputedValue = /** @class */ (function () { /** * Create a new computed value based on a function expression. * * The `name` property is for debug purposes only. * * The `equals` property specifies the comparer function to use to determine if a newly produced * value differs from the previous value. Two comparers are provided in the library; `defaultComparer` * compares based on identity comparison (===), and `structualComparer` deeply compares the structure. * Structural comparison can be convenient if you always produce a new aggregated object and * don't want to notify observers if it is structurally the same. * This is useful for working with vectors, mouse coordinates etc. */ function ComputedValue(options) { this.dependenciesState = exports.IDerivationState.NOT_TRACKING; this.observing = []; // nodes we are looking at. Our value depends on these nodes this.newObserving = null; // during tracking it's an array with new observed observers this.isBeingObserved = false; this.isPendingUnobservation = false; this.observers = new Set(); this.diffValue = 0; this.runId = 0; this.lastAccessedBy = 0; this.lowestObserverState = exports.IDerivationState.UP_TO_DATE; this.unboundDepsCount = 0; this.__mapid = "#" + getNextId(); this.value = new CaughtException(null); this.isComputing = false; // to check for cycles this.isRunningSetter = false; this.isTracing = TraceMode.NONE; if (process.env.NODE_ENV !== "production" && !options.get) throw "[mobx] missing option for computed: get"; this.derivation = options.get; this.name = options.name || "ComputedValue@" + getNextId(); if (options.set) this.setter = createAction(this.name + "-setter", options.set); this.equals = options.equals || (options.compareStructural || options.struct ? comparer.structural : comparer.default); this.scope = options.context; this.requiresReaction = !!options.requiresReaction; this.keepAlive = !!options.keepAlive; } ComputedValue.prototype.onBecomeStale = function () { propagateMaybeChanged(this); }; ComputedValue.prototype.onBecomeObserved = function () { if (this.onBecomeObservedListeners) { this.onBecomeObservedListeners.forEach(function (listener) { return listener(); }); } }; ComputedValue.prototype.onBecomeUnobserved = function () { if (this.onBecomeUnobservedListeners) { this.onBecomeUnobservedListeners.forEach(function (listener) { return listener(); }); } }; /** * Returns the current value of this computed value. * Will evaluate its computation first if needed. */ ComputedValue.prototype.get = function () { if (this.isComputing) fail("Cycle detected in computation " + this.name + ": " + this.derivation); if (globalState.inBatch === 0 && this.observers.size === 0 && !this.keepAlive) { if (shouldCompute(this)) { this.warnAboutUntrackedRead(); startBatch(); // See perf test 'computed memoization' this.value = this.computeValue(false); endBatch(); } } else { reportObserved(this); if (shouldCompute(this)) if (this.trackAndCompute()) propagateChangeConfirmed(this); } var result = this.value; if (isCaughtException(result)) throw result.cause; return result; }; ComputedValue.prototype.peek = function () { var res = this.computeValue(false); if (isCaughtException(res)) throw res.cause; return res; }; ComputedValue.prototype.set = function (value) { if (this.setter) { invariant(!this.isRunningSetter, "The setter of computed value '" + this.name + "' is trying to update itself. Did you intend to update an _observable_ value, instead of the computed property?"); this.isRunningSetter = true; try { this.setter.call(this.scope, value); } finally { this.isRunningSetter = false; } } else invariant(false, process.env.NODE_ENV !== "production" && "[ComputedValue '" + this.name + "'] It is not possible to assign a new value to a computed value."); }; ComputedValue.prototype.trackAndCompute = function () { if (isSpyEnabled() && process.env.NODE_ENV !== "production") { spyReport({ object: this.scope, type: "compute", name: this.name }); } var oldValue = this.value; var wasSuspended = /* see #1208 */ this.dependenciesState === exports.IDerivationState.NOT_TRACKING; var newValue = this.computeValue(true); var changed = wasSuspended || isCaughtException(oldValue) || isCaughtException(newValue) || !this.equals(oldValue, newValue); if (changed) { this.value = newValue; } return changed; }; ComputedValue.prototype.computeValue = function (track) { this.isComputing = true; globalState.computationDepth++; var res; if (track) { res = trackDerivedFunction(this, this.derivation, this.scope); } else { if (globalState.disableErrorBoundaries === true) { res = this.derivation.call(this.scope); } else { try { res = this.derivation.call(this.scope); } catch (e) { res = new CaughtException(e); } } } globalState.computationDepth--; this.isComputing = false; return res; }; ComputedValue.prototype.suspend = function () { if (!this.keepAlive) { clearObserving(this); this.value = undefined; // don't hold on to computed value! } }; ComputedValue.prototype.observe = function (listener, fireImmediately) { var _this = this; var firstTime = true; var prevValue = undefined; return autorun(function () { var newValue = _this.get(); if (!firstTime || fireImmediately) { var prevU = untrackedStart(); listener({ type: "update", object: _this, newValue: newValue, oldValue: prevValue }); untrackedEnd(prevU); } firstTime = false; prevValue = newValue; }); }; ComputedValue.prototype.warnAboutUntrackedRead = function () { if (process.env.NODE_ENV === "production") return; if (this.requiresReaction === true) { fail("[mobx] Computed value " + this.name + " is read outside a reactive context"); } if (this.isTracing !== TraceMode.NONE) { console.log("[mobx.trace] '" + this.name + "' is being read outside a reactive context. Doing a full recompute"); } if (globalState.computedRequiresReaction) { console.warn("[mobx] Computed value " + this.name + " is being read outside a reactive context. Doing a full recompute"); } }; ComputedValue.prototype.toJSON = function () { return this.get(); }; ComputedValue.prototype.toString = function () { return this.name + "[" + this.derivation.toString() + "]"; }; ComputedValue.prototype.valueOf = function () { return toPrimitive(this.get()); }; ComputedValue.prototype[Symbol.toPrimitive] = function () { return this.valueOf(); }; return ComputedValue; }()); var isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue); (function (IDerivationState) { // before being run or (outside batch and not being observed) // at this point derivation is not holding any data about dependency tree IDerivationState[IDerivationState["NOT_TRACKING"] = -1] = "NOT_TRACKING"; // no shallow dependency changed since last computation // won't recalculate derivation // this is what makes mobx fast IDerivationState[IDerivationState["UP_TO_DATE"] = 0] = "UP_TO_DATE"; // some deep dependency changed, but don't know if shallow dependency changed // will require to check first if UP_TO_DATE or POSSIBLY_STALE // currently only ComputedValue will propagate POSSIBLY_STALE // // having this state is second big optimization: // don't have to recompute on every dependency change, but only when it's needed IDerivationState[IDerivationState["POSSIBLY_STALE"] = 1] = "POSSIBLY_STALE"; // A shallow dependency has changed since last computation and the derivation // will need to recompute when it's needed next. IDerivationState[IDerivationState["STALE"] = 2] = "STALE"; })(exports.IDerivationState || (exports.IDerivationState = {})); var TraceMode; (function (TraceMode) { TraceMode[TraceMode["NONE"] = 0] = "NONE"; TraceMode[TraceMode["LOG"] = 1] = "LOG"; TraceMode[TraceMode["BREAK"] = 2] = "BREAK"; })(TraceMode || (TraceMode = {})); var CaughtException = /** @class */ (function () { function CaughtException(cause) { this.cause = cause; // Empty } return CaughtException; }()); function isCaughtException(e) { return e instanceof CaughtException; } /** * Finds out whether any dependency of the derivation has actually changed. * If dependenciesState is 1 then it will recalculate dependencies, * if any dependency changed it will propagate it by changing dependenciesState to 2. * * By iterating over the dependencies in the same order that they were reported and * stopping on the first change, all the recalculations are only called for ComputedValues * that will be tracked by derivation. That is because we assume that if the first x * dependencies of the derivation doesn't change then the derivation should run the same way * up until accessing x-th dependency. */ function shouldCompute(derivation) { switch (derivation.dependenciesState) { case exports.IDerivationState.UP_TO_DATE: return false; case exports.IDerivationState.NOT_TRACKING: case exports.IDerivationState.STALE: return true; case exports.IDerivationState.POSSIBLY_STALE: { var prevUntracked = untrackedStart(); // no need for those computeds to be reported, they will be picked up in trackDerivedFunction. var obs = derivation.observing, l = obs.length; for (var i = 0; i < l; i++) { var obj = obs[i]; if (isComputedValue(obj)) { if (globalState.disableErrorBoundaries) { obj.get(); } else { try { obj.get(); } catch (e) { // we are not interested in the value *or* exception at this moment, but if there is one, notify all untrackedEnd(prevUntracked); return true; } } // if ComputedValue `obj` actually changed it will be computed and propagated to its observers. // and `derivation` is an observer of `obj` // invariantShouldCompute(derivation) if (derivation.dependenciesState === exports.IDerivationState.STALE) { untrackedEnd(prevUntracked); return true; } } } changeDependenciesStateTo0(derivation); untrackedEnd(prevUntracked); return false; } } } // function invariantShouldCompute(derivation: IDerivation) { // const newDepState = (derivation as any).dependenciesState // if ( // process.env.NODE_ENV === "production" && // (newDepState === IDerivationState.POSSIBLY_STALE || // newDepState === IDerivationState.NOT_TRACKING) // ) // fail("Illegal dependency state") // } function isComputingDerivation() { return globalState.trackingDerivation !== null; // filter out actions inside computations } function checkIfStateModificationsAreAllowed(atom) { var hasObservers = atom.observers.size > 0; // Should never be possible to change an observed observable from inside computed, see #798 if (globalState.computationDepth > 0 && hasObservers) fail(process.env.NODE_ENV !== "production" && "Computed values are not allowed to cause side effects by changing observables that are already being observed. Tried to modify: " + atom.name); // Should not be possible to change observed state outside strict mode, except during initialization, see #563 if (!globalState.allowStateChanges && (hasObservers || globalState.enforceActions === "strict")) fail(process.env.NODE_ENV !== "production" && (globalState.enforceActions ? "Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: " : "Side effects like changing state are not allowed at this point. Are you trying to modify state from, for example, the render function of a React component? Tried to modify: ") + atom.name); } /** * Executes the provided function `f` and tracks which observables are being accessed. * The tracking information is stored on the `derivation` object and the derivation is registered * as observer of any of the accessed observables. */ function trackDerivedFunction(derivation, f, context) { // pre allocate array allocation + room for variation in deps // array will be trimmed by bindDependencies changeDependenciesStateTo0(derivation); derivation.newObserving = new Array(derivation.observing.length + 100); derivation.unboundDepsCount = 0; derivation.runId = ++globalState.runId; var prevTracking = globalState.trackingDerivation; globalState.trackingDerivation = derivation; var result; if (globalState.disableErrorBoundaries === true) { result = f.call(context); } else { try { result = f.call(context); } catch (e) { result = new CaughtException(e); } } globalState.trackingDerivation = prevTracking; bindDependencies(derivation); return result; } /** * diffs newObserving with observing. * update observing to be newObserving with unique observables * notify observers that become observed/unobserved */ function bindDependencies(derivation) { // invariant(derivation.dependenciesState !== IDerivationState.NOT_TRACKING, "INTERNAL ERROR bindDependencies expects derivation.dependenciesState !== -1"); var prevObserving = derivation.observing; var observing = (derivation.observing = derivation.newObserving); var lowestNewObservingDerivationState = exports.IDerivationState.UP_TO_DATE; // Go through all new observables and check diffValue: (this list can contain duplicates): // 0: first occurrence, change to 1 and keep it // 1: extra occurrence, drop it var i0 = 0, l = derivation.unboundDepsCount; for (var i = 0; i < l; i++) { var dep = observing[i]; if (dep.diffValue === 0) { dep.diffValue = 1; if (i0 !== i) observing[i0] = dep; i0++; } // Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined, // not hitting the condition if (dep.dependenciesState > lowestNewObservingDerivationState) { lowestNewObservingDerivationState = dep.dependenciesState; } } observing.length = i0; derivation.newObserving = null; // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614) // Go through all old observables and check diffValue: (it is unique after last bindDependencies) // 0: it's not in new observables, unobserve it // 1: it keeps being observed, don't want to notify it. change to 0 l = prevObserving.length; while (l--) { var dep = prevObserving[l]; if (dep.diffValue === 0) { removeObserver(dep, derivation); } dep.diffValue = 0; } // Go through all new observables and check diffValue: (now it should be unique) // 0: it was set to 0 in last loop. don't need to do anything. // 1: it wasn't observed, let's observe it. set back to 0 while (i0--) { var dep = observing[i0]; if (dep.diffValue === 1) { dep.diffValue = 0; addObserver(dep, derivation); } } // Some new observed derivations may become stale during this derivation computation // so they have had no chance to propagate staleness (#916) if (lowestNewObservingDerivationState !== exports.IDerivationState.UP_TO_DATE) { derivation.dependenciesState = lowestNewObservingDerivationState; derivation.onBecomeStale(); } } function clearObserving(derivation) { // invariant(globalState.inBatch > 0, "INTERNAL ERROR clearObserving should be called only inside batch"); var obs = derivation.observing; derivation.observing = []; var i = obs.length; while (i--) removeObserver(obs[i], derivation); derivation.dependenciesState = exports.IDerivationState.NOT_TRACKING; } function untracked(action) { var prev = untrackedStart(); try { return action(); } finally { untrackedEnd(prev); } } function untrackedStart() { var prev = globalState.trackingDerivation; globalState.trackingDerivation = null; return prev; } function untrackedEnd(prev) { globalState.trackingDerivation = prev; } /** * needed to keep `lowestObserverState` correct. when changing from (2 or 1) to 0 * */ function changeDependenciesStateTo0(derivation) { if (derivation.dependenciesState === exports.IDerivationState.UP_TO_DATE) return; derivation.dependenciesState = exports.IDerivationState.UP_TO_DATE; var obs = derivation.observing; var i = obs.length; while (i--) obs[i].lowestObserverState = exports.IDerivationState.UP_TO_DATE; } /** * These values will persist if global state is rese