@turbox3d/reactivity
Version:
Large-scale reactive state management library
536 lines • 20.6 kB
JavaScript
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;
}