UNPKG

@turbox3d/reactivity

Version:

Large-scale reactive state management library

545 lines (544 loc) 22.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.collectionTypes = exports.KeyPathType = exports.Domain = void 0; exports.createDomain = createDomain; exports.rawCache = exports.proxyCache = exports.keyPathCache = void 0; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _readOnlyError2 = _interopRequireDefault(require("@babel/runtime/helpers/readOnlyError")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _shared = require("@turbox3d/shared"); var _symbol = require("../const/symbol"); var _collector = require("./collector"); var _store = require("./store"); var _reactor = require("../decorators/reactor"); var _enums = require("../const/enums"); var _reactive = require("./reactive"); var _computed = require("../decorators/computed"); var _config = require("../const/config"); var _mutation = require("../decorators/mutation"); var _action = require("../decorators/action"); var _materialCallStack = require("../utils/materialCallStack"); var _common = require("../utils/common"); var KeyPathType; (function (KeyPathType) { KeyPathType["property"] = "property"; KeyPathType["index"] = "index"; })(KeyPathType || (exports.KeyPathType = KeyPathType = {})); var proxyCache = exports.proxyCache = new WeakMap(); var rawCache = exports.rawCache = new WeakMap(); var keyPathCache = exports.keyPathCache = new WeakMap(); var collectionTypes = exports.collectionTypes = [Map, Set, WeakMap, WeakSet]; /** * Framework base class 'Domain', class must be extends this base class which is need to be observable. */ var Domain = exports.Domain = /*#__PURE__*/function () { function Domain() { var _this2 = this; (0, _classCallCheck2["default"])(this, Domain); // prompt: add property do not forget sync to black list this.$$turboxProperties = {}; this.reactorConfigMap = {}; this.computedProperties = {}; this.getCollectionHandlerMap = function (target, proxyKey) { return (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({ get size() { var proto = Reflect.getPrototypeOf(target); _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return Reflect.get(proto, proxyKey, target); }, get: function get(key) { var _Reflect$getPrototype = Reflect.getPrototypeOf(target), get = _Reflect$getPrototype.get; _collector.depCollector.collect(target, key); return get.call(target, key); }, has: function has(key) { var _Reflect$getPrototype2 = Reflect.getPrototypeOf(target), has = _Reflect$getPrototype2.has; _collector.depCollector.collect(target, key); return has.call(target, key); }, forEach: function forEach(callbackfn) { var _Reflect$getPrototype3 = Reflect.getPrototypeOf(target), forEach = _Reflect$getPrototype3.forEach; _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return forEach.call(target, callbackfn); }, values: function values() { var _Reflect$getPrototype4 = Reflect.getPrototypeOf(target), values = _Reflect$getPrototype4.values; _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return values.call(target); }, keys: function keys() { var _Reflect$getPrototype5 = Reflect.getPrototypeOf(target), keys = _Reflect$getPrototype5.keys; _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return keys.call(target); }, entries: function entries() { var _Reflect$getPrototype6 = Reflect.getPrototypeOf(target), entries = _Reflect$getPrototype6.entries; _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return entries.call(target); } }, Symbol.iterator, function () { if (target.constructor === Set) { return _this2.getCollectionHandlerMap(target, 'values').values(); } if (target.constructor === Map) { return _this2.getCollectionHandlerMap(target, 'entries').entries(); } }), "add", function add(value) { var _Reflect$getPrototype7 = Reflect.getPrototypeOf(target), add = _Reflect$getPrototype7.add, has = _Reflect$getPrototype7.has; var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; var hadValue = has.call(target, value); if (!hadValue) { _collector.triggerCollector.trigger(target, value, { type: _enums.ECollectType.SET_ADD, beforeUpdate: void 0, didUpdate: value }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); _collector.triggerCollector.trigger(target, _enums.ESpecialReservedKey.ITERATE, { type: _enums.ECollectType.SET_ADD }, _this2.reactorConfigMap[rootKey].isNeedRecord); } return add.call(target, value); }), "set", function set(key, value) { var _Reflect$getPrototype8 = Reflect.getPrototypeOf(target), set = _Reflect$getPrototype8.set, get = _Reflect$getPrototype8.get, has = _Reflect$getPrototype8.has; var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; var hadKey = has.call(target, key); var oldValue = get.call(target, key); if (value !== oldValue) { _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.MAP_SET, beforeUpdate: oldValue, didUpdate: value }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); } if (!hadKey) { _collector.triggerCollector.trigger(target, _enums.ESpecialReservedKey.ITERATE, { type: _enums.ECollectType.MAP_SET }, _this2.reactorConfigMap[rootKey].isNeedRecord); } return set.call(target, key, value); }), "delete", function _delete(key) { var proto = Reflect.getPrototypeOf(target); var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; var hadKey = proto.has.call(target, key); if (!hadKey) { return proto["delete"].call(target, key); } if (proto.constructor === Map || proto.constructor === WeakMap) { var oldValue = proto.get.call(target, key); _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.MAP_DELETE, beforeUpdate: oldValue }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); _collector.triggerCollector.trigger(target, _enums.ESpecialReservedKey.ITERATE, { type: _enums.ECollectType.MAP_DELETE }, _this2.reactorConfigMap[rootKey].isNeedRecord); } if (proto.constructor === Set || proto.constructor === WeakSet) { _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.SET_DELETE, beforeUpdate: key }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); _collector.triggerCollector.trigger(target, _enums.ESpecialReservedKey.ITERATE, { type: _enums.ECollectType.SET_DELETE }, _this2.reactorConfigMap[rootKey].isNeedRecord); } return proto["delete"].call(target, key); }), "clear", function clear() { var _Reflect$getPrototype9 = Reflect.getPrototypeOf(target), clear = _Reflect$getPrototype9.clear, forEach = _Reflect$getPrototype9.forEach; forEach.call(target, function (value, key) { _this2.getCollectionHandlerMap(target, key)["delete"](key); }); return clear.call(target); }); }; var target = Object.getPrototypeOf(this); var domainName = target.constructor.name || 'TURBOX_DOMAIN'; var namespace = "".concat(domainName, "_").concat((0, _shared.generateUUID)()); this[_symbol.NAMESPACE] = namespace; this.context = this.initDomainContext(); } return (0, _createClass2["default"])(Domain, [{ key: "initDomainContext", value: function initDomainContext() { return { isNeedRecord: _config.ctx.timeTravel.isNeedRecord }; } }, { key: "propertyGet", value: function propertyGet(key, config) { var hasInitializer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var defaultValue = arguments.length > 3 ? arguments[3] : undefined; var hasKey = key in this.$$turboxProperties; // if first initialize, will be false var useDefault = hasInitializer && !hasKey; var v = useDefault ? defaultValue : this.$$turboxProperties[key]; if (useDefault) { // hasInitializer won't trigger setter when first init value, need trigger it manually this.propertySet(key, v, config); } var mergedConfig = Object.assign({}, { isNeedRecord: this.context.isNeedRecord }, config); this.reactorConfigMap[key] = mergedConfig; _collector.depCollector.collect(this, key); return (0, _shared.isObject)(v) && !(0, _common.isDomain)(v) && mergedConfig.deepProxy ? this.proxyReactive(v, [{ type: KeyPathType.property, value: key }]) : v; } }, { key: "propertySet", value: function propertySet(key, v, config) { _config.ctx.strictMode && this.illegalAssignmentCheck(this, key); var oldValue = this.$$turboxProperties[key]; var mergedConfig = Object.assign({}, { isNeedRecord: this.context.isNeedRecord }, config); this.reactorConfigMap[key] = mergedConfig; if (oldValue !== v) { this.$$turboxProperties[key] = v; _collector.triggerCollector.trigger(this, key, { type: _enums.ECollectType.SET, beforeUpdate: oldValue, didUpdate: v }, mergedConfig.isNeedRecord, { keyPath: [{ type: KeyPathType.property, value: key }], domain: this.constructor.name }); } } }, { key: "computedPropertyGet", value: function computedPropertyGet(key, options, descriptor) { var _this3 = this; if (!this.computedProperties[key]) { this.computedProperties[key] = { dirty: true, needReComputed: false, needTrigger: false }; } var cc = this.computedProperties[key]; var lazy = options && options.lazy !== void 0 ? options.lazy : false; if (!cc.reaction) { cc.reaction = (0, _reactive.createReaction)(function () { cc.dirty = true; if (cc.needReComputed) { cc.value = cc.computeRunner(); } if (cc.needTrigger) { _collector.triggerCollector.trigger(_this3, key, { type: _enums.ECollectType.SET, beforeUpdate: void 0, didUpdate: void 0 }, false); } }, { name: key, computed: true, lazy: lazy }); } if (!cc.computeRunner && descriptor) { cc.computeRunner = (0, _shared.bind)(descriptor.get, this); } if (cc.dirty) { cc.needReComputed = true; cc.needTrigger = false; try { cc.reaction.runner(); } finally { cc.dirty = false; cc.needReComputed = false; cc.needTrigger = true; } } _collector.depCollector.collect(this, key); return cc.value; } }, { key: "computedPropertySet", value: function computedPropertySet(key, original) { if (!this.computedProperties[key]) { this.computedProperties[key] = { dirty: true, needReComputed: false, needTrigger: false }; } var computedConfig = this.computedProperties[key]; if (computedConfig.computeRunner) { return; } this.computedProperties[key].computeRunner = (0, _shared.bind)(original, this); } /** * the syntax sweet of updating state out of mutation */ }, { key: "$update", value: function $update(obj, actionName, displayName, forceSaveHistory, isNeedRecord, immediately) { (0, _shared.invariant)((0, _shared.isPlainObject)(obj), 'resetState(...) param type error. Param should be a plain object.'); this.dispatch(obj, actionName, displayName, forceSaveHistory, isNeedRecord, immediately); } }, { key: "proxySet", value: function proxySet(target, key, value, receiver) { // array length need hack if (Array.isArray(target) && target !== this.currentTarget) { this.currentTarget = target; this.originalArrayLength = target[_enums.ESpecialReservedKey.ARRAY_LENGTH]; } _config.ctx.strictMode && this.illegalAssignmentCheck(target, key); var hadKey = (0, _shared.hasOwn)(target, key); var oldValue = target[key]; var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; // do nothing if target is in the prototype chain if (target === proxyCache.get(receiver)) { if (key === _enums.ESpecialReservedKey.ARRAY_LENGTH || value !== oldValue) { _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.SET, beforeUpdate: key === _enums.ESpecialReservedKey.ARRAY_LENGTH ? this.originalArrayLength : oldValue, didUpdate: value }, this.reactorConfigMap[rootKey].isNeedRecord, { keyPath: [{ type: KeyPathType.property, value: key }], domain: this.constructor.name }); if (key === _enums.ESpecialReservedKey.ARRAY_LENGTH) { this.currentTarget = void 0; this.originalArrayLength = void 0; } } if (!hadKey) { _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.ADD, beforeUpdate: oldValue, didUpdate: value }, this.reactorConfigMap[rootKey].isNeedRecord, { keyPath: [{ type: KeyPathType.property, value: key }], domain: this.constructor.name }); } var result = Reflect.set(target, key, value, receiver); return result; } return false; } }, { key: "proxyGet", value: function proxyGet(target, key, receiver) { var _this4 = this; var res = Reflect.get(target, key, receiver); var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; _collector.depCollector.collect(target, key); if (this.reactorConfigMap[rootKey].callback) { var f = function f() { _reactor.meta.freeze = true; _this4.reactorConfigMap[rootKey].callback && _this4.reactorConfigMap[rootKey].callback.call(_this4, target, key); _reactor.meta.freeze = false; }; !_reactor.meta.freeze && f(); } return (0, _shared.isObject)(res) && !(0, _common.isDomain)(res) ? this.proxyReactive(res, [].concat((0, _toConsumableArray2["default"])(keyPath), [{ type: Array.isArray(target) ? KeyPathType.index : KeyPathType.property, value: key }])) : res; } }, { key: "proxyDeleteProperty", value: function proxyDeleteProperty(target, key) { var keyPath = keyPathCache.get(target); var rootKey = keyPath[0].value; var oldValue = target[key]; _collector.triggerCollector.trigger(target, key, { type: _enums.ECollectType.DELETE, beforeUpdate: oldValue, didUpdate: void 0 }, this.reactorConfigMap[rootKey].isNeedRecord, { keyPath: [{ type: KeyPathType.property, value: key }], domain: this.constructor.name }); return Reflect.deleteProperty(target, key); } }, { key: "proxyOwnKeys", value: function proxyOwnKeys(target) { _collector.depCollector.collect(target, _enums.ESpecialReservedKey.ITERATE); return Reflect.ownKeys(target); } /** * proxy value could be * boolean, string, number, undefined, null, custom instance, array[], plainObject{}, Map, Set, WeakMap, WeakSet */ }, { key: "proxyReactive", value: function proxyReactive(raw, keyPath) { // eslint-disable-next-line @typescript-eslint/no-this-alias var _this = this; keyPathCache.set(raw, keyPath); // different props use same ref var refProxy = rawCache.get(raw); if (refProxy !== void 0) { return refProxy; } // raw is already a Proxy if (proxyCache.has(raw)) { return raw; } if (!(0, _shared.canObserve)(raw)) { return raw; } var proxyHandler = (0, _shared.includes)(collectionTypes, raw.constructor) ? { get: (0, _shared.bind)(_this.collectionProxyHandler, _this) } : { get: (0, _shared.bind)(_this.proxyGet, _this), set: (0, _shared.bind)(_this.proxySet, _this), ownKeys: (0, _shared.bind)(_this.proxyOwnKeys, _this), deleteProperty: (0, _shared.bind)(_this.proxyDeleteProperty, _this) }; var proxy = new Proxy(raw, proxyHandler); proxyCache.set(proxy, raw); rawCache.set(raw, proxy); return proxy; } }, { key: "collectionProxyHandler", value: function collectionProxyHandler(target, key) { var handlers = this.getCollectionHandlerMap(target, key); var targetObj = key in target && handlers[key] ? handlers : target; return Reflect.get(targetObj, key); } /** * observed value could be assigned value to @reactor only in @mutation/$update, otherwise throw error. */ }, { key: "illegalAssignmentCheck", value: function illegalAssignmentCheck(target, stringKey) { if (_collector.depCollector.isObserved(target, stringKey)) { var length = _materialCallStack.materialCallStack.stack.length; var firstLevelMaterial = _materialCallStack.materialCallStack.stack[length - 1] || _enums.EMaterialType.DEFAULT; (0, _shared.invariant)(firstLevelMaterial.type === _enums.EMaterialType.MUTATION || firstLevelMaterial.type === _enums.EMaterialType.UPDATE || firstLevelMaterial.type === _enums.EMaterialType.UNDO || firstLevelMaterial.type === _enums.EMaterialType.REDO, "You cannot update value to observed '@reactor property' directly. Please use mutation or $update({})."); } } }, { key: "dispatch", value: function dispatch(obj, actionName, displayName, forceSaveHistory, isNeedRecord, immediately) { var original = function original() { var keys = Object.keys(obj); for (var i = 0, len = keys.length; i < len; i++) { var key = keys[i]; this[key] = obj[key]; } }; // update state before store init if (_store.store === void 0) { original.call(this); _materialCallStack.materialCallStack.pop(-1); return; } // update state after store init _store.store.dispatch({ name: actionName || "".concat(_symbol.TURBOX_PREFIX, "UPDATE"), displayName: displayName || _symbol.EMPTY_ACTION_NAME, payload: [], type: _enums.EMaterialType.UPDATE, domain: this, original: (0, _shared.bind)(original, this), forceSaveHistory: forceSaveHistory, isNeedRecord: isNeedRecord, immediately: immediately }); } }]); }(); function createDomain(domainConfig) { var reactor = domainConfig.reactor, domainMutation = domainConfig.mutation, domainComputed = domainConfig.computed, domainAction = domainConfig.action; var domain = new Domain(); var config = { deepProxy: true, displayName: '', isNeedRecord: true }; Object.keys(reactor).forEach(function (property) { var value = reactor[property]; Object.defineProperty(domain, property, { enumerable: true, configurable: true, get: function get() { var current = this; return current.propertyGet(property, config); }, set: function set(newVal) { var current = this; current.propertySet(property, newVal, config); } }); domain[property] = value; }); Object.keys(domainMutation).forEach(function (methodKey) { var original = domainMutation[methodKey]; var f = (0, _mutation.mutation)(methodKey, (0, _shared.bind)(original, domain)); domain[methodKey] = f; }); domainComputed && Object.keys(domainComputed).forEach(function (property) { var original = domainComputed[property]; var ref = (0, _computed.computed)((0, _shared.bind)(original, domain)); domain[property] = ref; }); domainAction && Object.keys(domainAction).forEach(function (methodKey) { var original = domainAction[methodKey]; var f = (0, _action.action)(methodKey, (0, _shared.bind)(original, domain)); domain[methodKey] = f; }); return domain; }