UNPKG

@turbox3d/reactivity

Version:

Large-scale reactive state management library

536 lines 20.6 kB
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _readOnlyError from "@babel/runtime/helpers/esm/readOnlyError"; import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import { isPlainObject, hasOwn, isObject, bind, includes, invariant, generateUUID, canObserve } from '@turbox3d/shared'; import { NAMESPACE, EMPTY_ACTION_NAME, TURBOX_PREFIX } from '../const/symbol'; import { depCollector, triggerCollector } from './collector'; import { store } from './store'; import { meta } from '../decorators/reactor'; import { ECollectType, ESpecialReservedKey, EMaterialType } from '../const/enums'; import { createReaction } from './reactive'; import { computed } from '../decorators/computed'; import { ctx } from '../const/config'; import { mutation } from '../decorators/mutation'; import { action } from '../decorators/action'; import { materialCallStack } from '../utils/materialCallStack'; import { isDomain } from '../utils/common'; export var KeyPathType; (function (KeyPathType) { KeyPathType["property"] = "property"; KeyPathType["index"] = "index"; })(KeyPathType || (KeyPathType = {})); export var proxyCache = new WeakMap(); export var rawCache = new WeakMap(); export var keyPathCache = new WeakMap(); export var collectionTypes = [Map, Set, WeakMap, WeakSet]; /** * Framework base class 'Domain', class must be extends this base class which is need to be observable. */ export var Domain = /*#__PURE__*/function () { function Domain() { var _this2 = this; _classCallCheck(this, Domain); // prompt: add property do not forget sync to black list this.$$turboxProperties = {}; this.reactorConfigMap = {}; this.computedProperties = {}; this.getCollectionHandlerMap = function (target, proxyKey) { return _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({ get size() { var proto = Reflect.getPrototypeOf(target); depCollector.collect(target, ESpecialReservedKey.ITERATE); return Reflect.get(proto, proxyKey, target); }, get: function get(key) { var _Reflect$getPrototype = Reflect.getPrototypeOf(target), get = _Reflect$getPrototype.get; depCollector.collect(target, key); return get.call(target, key); }, has: function has(key) { var _Reflect$getPrototype2 = Reflect.getPrototypeOf(target), has = _Reflect$getPrototype2.has; depCollector.collect(target, key); return has.call(target, key); }, forEach: function forEach(callbackfn) { var _Reflect$getPrototype3 = Reflect.getPrototypeOf(target), forEach = _Reflect$getPrototype3.forEach; depCollector.collect(target, ESpecialReservedKey.ITERATE); return forEach.call(target, callbackfn); }, values: function values() { var _Reflect$getPrototype4 = Reflect.getPrototypeOf(target), values = _Reflect$getPrototype4.values; depCollector.collect(target, ESpecialReservedKey.ITERATE); return values.call(target); }, keys: function keys() { var _Reflect$getPrototype5 = Reflect.getPrototypeOf(target), keys = _Reflect$getPrototype5.keys; depCollector.collect(target, ESpecialReservedKey.ITERATE); return keys.call(target); }, entries: function entries() { var _Reflect$getPrototype6 = Reflect.getPrototypeOf(target), entries = _Reflect$getPrototype6.entries; depCollector.collect(target, 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) { triggerCollector.trigger(target, value, { type: ECollectType.SET_ADD, beforeUpdate: void 0, didUpdate: value }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); triggerCollector.trigger(target, ESpecialReservedKey.ITERATE, { type: 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) { triggerCollector.trigger(target, key, { type: ECollectType.MAP_SET, beforeUpdate: oldValue, didUpdate: value }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); } if (!hadKey) { triggerCollector.trigger(target, ESpecialReservedKey.ITERATE, { type: 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); triggerCollector.trigger(target, key, { type: ECollectType.MAP_DELETE, beforeUpdate: oldValue }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); triggerCollector.trigger(target, ESpecialReservedKey.ITERATE, { type: ECollectType.MAP_DELETE }, _this2.reactorConfigMap[rootKey].isNeedRecord); } if (proto.constructor === Set || proto.constructor === WeakSet) { triggerCollector.trigger(target, key, { type: ECollectType.SET_DELETE, beforeUpdate: key }, _this2.reactorConfigMap[rootKey].isNeedRecord, { keyPath: keyPath, domain: _this2.constructor.name }); triggerCollector.trigger(target, ESpecialReservedKey.ITERATE, { type: 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(generateUUID()); this[NAMESPACE] = namespace; this.context = this.initDomainContext(); } return _createClass(Domain, [{ key: "initDomainContext", value: function initDomainContext() { return { isNeedRecord: 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; depCollector.collect(this, key); return isObject(v) && !isDomain(v) && mergedConfig.deepProxy ? this.proxyReactive(v, [{ type: KeyPathType.property, value: key }]) : v; } }, { key: "propertySet", value: function propertySet(key, v, 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; triggerCollector.trigger(this, key, { type: 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 = createReaction(function () { cc.dirty = true; if (cc.needReComputed) { cc.value = cc.computeRunner(); } if (cc.needTrigger) { triggerCollector.trigger(_this3, key, { type: ECollectType.SET, beforeUpdate: void 0, didUpdate: void 0 }, false); } }, { name: key, computed: true, lazy: lazy }); } if (!cc.computeRunner && descriptor) { cc.computeRunner = 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; } } 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 = bind(original, this); } /** * the syntax sweet of updating state out of mutation */ }, { key: "$update", value: function $update(obj, actionName, displayName, forceSaveHistory, isNeedRecord, immediately) { invariant(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[ESpecialReservedKey.ARRAY_LENGTH]; } ctx.strictMode && this.illegalAssignmentCheck(target, key); var hadKey = 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 === ESpecialReservedKey.ARRAY_LENGTH || value !== oldValue) { triggerCollector.trigger(target, key, { type: ECollectType.SET, beforeUpdate: key === ESpecialReservedKey.ARRAY_LENGTH ? this.originalArrayLength : oldValue, didUpdate: value }, this.reactorConfigMap[rootKey].isNeedRecord, { keyPath: [{ type: KeyPathType.property, value: key }], domain: this.constructor.name }); if (key === ESpecialReservedKey.ARRAY_LENGTH) { this.currentTarget = void 0; this.originalArrayLength = void 0; } } if (!hadKey) { triggerCollector.trigger(target, key, { type: 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; depCollector.collect(target, key); if (this.reactorConfigMap[rootKey].callback) { var f = function f() { meta.freeze = true; _this4.reactorConfigMap[rootKey].callback && _this4.reactorConfigMap[rootKey].callback.call(_this4, target, key); meta.freeze = false; }; !meta.freeze && f(); } return isObject(res) && !isDomain(res) ? this.proxyReactive(res, [].concat(_toConsumableArray(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]; triggerCollector.trigger(target, key, { type: 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) { depCollector.collect(target, 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 (!canObserve(raw)) { return raw; } var proxyHandler = includes(collectionTypes, raw.constructor) ? { get: bind(_this.collectionProxyHandler, _this) } : { get: bind(_this.proxyGet, _this), set: bind(_this.proxySet, _this), ownKeys: bind(_this.proxyOwnKeys, _this), deleteProperty: 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 (depCollector.isObserved(target, stringKey)) { var length = materialCallStack.stack.length; var firstLevelMaterial = materialCallStack.stack[length - 1] || EMaterialType.DEFAULT; invariant(firstLevelMaterial.type === EMaterialType.MUTATION || firstLevelMaterial.type === EMaterialType.UPDATE || firstLevelMaterial.type === EMaterialType.UNDO || firstLevelMaterial.type === 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 === void 0) { original.call(this); materialCallStack.pop(-1); return; } // update state after store init store.dispatch({ name: actionName || "".concat(TURBOX_PREFIX, "UPDATE"), displayName: displayName || EMPTY_ACTION_NAME, payload: [], type: EMaterialType.UPDATE, domain: this, original: bind(original, this), forceSaveHistory: forceSaveHistory, isNeedRecord: isNeedRecord, immediately: immediately }); } }]); }(); export 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 = mutation(methodKey, bind(original, domain)); domain[methodKey] = f; }); domainComputed && Object.keys(domainComputed).forEach(function (property) { var original = domainComputed[property]; var ref = computed(bind(original, domain)); domain[property] = ref; }); domainAction && Object.keys(domainAction).forEach(function (methodKey) { var original = domainAction[methodKey]; var f = action(methodKey, bind(original, domain)); domain[methodKey] = f; }); return domain; }