mobx-utils
Advanced tools
Comparing version 5.3.0 to 5.4.0
@@ -0,1 +1,5 @@ | ||
# 5.4.0 | ||
Introduced `computedFn`, to support using arbitrary functions as computed! | ||
# 5.3.0 | ||
@@ -2,0 +6,0 @@ |
@@ -18,1 +18,2 @@ export * from "./from-promise"; | ||
export * from "./deepObserve"; | ||
export { computedFn } from "./computedFn"; |
@@ -18,1 +18,2 @@ export * from "./from-promise"; | ||
export * from "./deepObserve"; | ||
export { computedFn } from "./computedFn"; |
@@ -1255,2 +1255,170 @@ import { $mobx, _allowStateChanges, _getAdministration, _isComputingDerivation, action, autorun, computed, createAtom, entries, extendObservable, flow, getAtom, isAction, isComputed, isComputedProp, isObservableArray, isObservableMap, isObservableObject, keys, observable, observe, onBecomeUnobserved, runInAction, values, when } from 'mobx'; | ||
export { PENDING, FULFILLED, REJECTED, fromPromise, isPromiseBasedObservable, moveItem, lazyObservable, fromResource, toStream, fromStream, ViewModel, createViewModel, whenWithTimeout, keepAlive, queueProcessor, chunkProcessor, now, NOOP, IDENTITY, invariant, deprecated, addHiddenProp, getAllMethodsAndProperties, asyncAction, whenAsync, expr, createTransformer, deepObserve }; | ||
/** | ||
* @private | ||
*/ | ||
var DeepMapEntry = /** @class */ (function () { | ||
function DeepMapEntry(base, args) { | ||
this.base = base; | ||
this.args = args; | ||
this.closestIdx = 0; | ||
this.isDisposed = false; | ||
var current = this.closest = this.root = base; | ||
var i = 0; | ||
for (; i < this.args.length - 1; i++) { | ||
current = current.get(args[i]); | ||
if (current) | ||
this.closest = current; | ||
else | ||
break; | ||
} | ||
this.closestIdx = i; | ||
} | ||
DeepMapEntry.prototype.exists = function () { | ||
this.assertNotDisposed(); | ||
var l = this.args.length; | ||
return this.closestIdx >= l - 1 && this.closest.has(this.args[l - 1]); | ||
}; | ||
DeepMapEntry.prototype.get = function () { | ||
this.assertNotDisposed(); | ||
if (!this.exists()) | ||
throw new Error("Entry doesn't exist"); | ||
return this.closest.get(this.args[this.args.length - 1]); | ||
}; | ||
DeepMapEntry.prototype.set = function (value) { | ||
this.assertNotDisposed(); | ||
var l = this.args.length; | ||
var current = this.closest; | ||
// create remaining maps | ||
for (var i = this.closestIdx; i < l - 1; i++) { | ||
var m = new Map(); | ||
current.set(this.args[i], m); | ||
current = m; | ||
} | ||
this.closestIdx = l - 1; | ||
this.closest = current; | ||
current.set(this.args[l - 1], value); | ||
}; | ||
DeepMapEntry.prototype.delete = function () { | ||
this.assertNotDisposed(); | ||
if (!this.exists()) | ||
throw new Error("Entry doesn't exist"); | ||
var l = this.args.length; | ||
this.closest.delete(this.args[l - 1]); | ||
// clean up remaining maps if needed (reconstruct stack first) | ||
var c = this.root; | ||
var maps = [c]; | ||
for (var i = 0; i < l - 1; i++) { | ||
c = c.get(this.args[i]); | ||
maps.push(c); | ||
} | ||
for (var i = maps.length - 1; i > 0; i--) { | ||
if (maps[i].size === 0) | ||
maps[i - 1].delete(this.args[i - 1]); | ||
} | ||
this.isDisposed = true; | ||
}; | ||
DeepMapEntry.prototype.assertNotDisposed = function () { | ||
// TODO: once this becomes annoying, we should introduce a reset method to re-run the constructor logic | ||
if (this.isDisposed) | ||
throw new Error("Concurrent modification exception"); | ||
}; | ||
return DeepMapEntry; | ||
}()); | ||
/** | ||
* @private | ||
*/ | ||
var DeepMap = /** @class */ (function () { | ||
function DeepMap() { | ||
this.store = new Map(); | ||
this.argsLength = -1; | ||
} | ||
DeepMap.prototype.entry = function (args) { | ||
if (this.argsLength === -1) | ||
this.argsLength = args.length; | ||
else if (this.argsLength !== args.length) | ||
throw new Error("DeepMap should be used with functions with a consistent length, expected: " + this.argsLength + ", got: " + args.length); | ||
if (this.last) | ||
this.last.isDisposed = true; | ||
return this.last = new DeepMapEntry(this.store, args); | ||
}; | ||
return DeepMap; | ||
}()); | ||
/** | ||
* computedFn takes a function with an arbitrarily amount of arguments, | ||
* and memoized the output of the function based on the arguments passed in. | ||
* | ||
* computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments | ||
* that is accepted. However, the amount of arguments must be consistent and default arguments are not supported. | ||
* | ||
* By default the output of a function call will only be memoized as long as the | ||
* output is being observed. | ||
* | ||
* The function passes into `computedFn` should be pure, not be an action and only be relying on | ||
* observables. | ||
* | ||
* Setting `keepAlive` to `true` will cause the output to be forcefully cached forever. | ||
* Note that this might introduce memory leaks! | ||
* | ||
* @example | ||
* const store = observable({ | ||
a: 1, | ||
b: 2, | ||
c: 3, | ||
m: computedFn(function(x) { | ||
return this.a * this.b * x | ||
}) | ||
}) | ||
const d = autorun(() => { | ||
// store.m(3) will be cached as long as this autorun is running | ||
console.log((store.m(3) * store.c)) | ||
}) | ||
* | ||
* @param fn | ||
* @param keepAlive | ||
*/ | ||
function computedFn(fn, keepAlive) { | ||
if (keepAlive === void 0) { keepAlive = false; } | ||
if (isAction(fn)) | ||
throw new Error("computedFn shouldn't be used on actions"); | ||
var memoWarned = false; | ||
var i = 0; | ||
var d = new DeepMap(); | ||
return function () { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i] = arguments[_i]; | ||
} | ||
var self = this; | ||
var entry = d.entry(args); | ||
// cache hit, return | ||
if (entry.exists()) | ||
return entry.get().get(); | ||
// if function is invoked, and its a cache miss without reactive, there is no point in caching... | ||
if (!keepAlive && !_isComputingDerivation()) { | ||
if (!memoWarned) { | ||
console.warn("invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set"); | ||
memoWarned = true; | ||
} | ||
return fn.apply(self, args); | ||
} | ||
// create new entry | ||
var c = computed(function () { | ||
return fn.apply(self, args); | ||
}, { | ||
name: "computedFn(" + fn.name + "#" + (++i) + ")", | ||
keepAlive: keepAlive | ||
}); | ||
entry.set(c); | ||
// clean up if no longer observed | ||
if (!keepAlive) | ||
onBecomeUnobserved(c, function () { | ||
d.entry(args).delete(); | ||
}); | ||
// return current val | ||
return c.get(); | ||
}; | ||
} | ||
export { computedFn, PENDING, FULFILLED, REJECTED, fromPromise, isPromiseBasedObservable, moveItem, lazyObservable, fromResource, toStream, fromStream, ViewModel, createViewModel, whenWithTimeout, keepAlive, queueProcessor, chunkProcessor, now, NOOP, IDENTITY, invariant, deprecated, addHiddenProp, getAllMethodsAndProperties, asyncAction, whenAsync, expr, createTransformer, deepObserve }; |
@@ -1259,2 +1259,171 @@ (function (global, factory) { | ||
/** | ||
* @private | ||
*/ | ||
var DeepMapEntry = /** @class */ (function () { | ||
function DeepMapEntry(base, args) { | ||
this.base = base; | ||
this.args = args; | ||
this.closestIdx = 0; | ||
this.isDisposed = false; | ||
var current = this.closest = this.root = base; | ||
var i = 0; | ||
for (; i < this.args.length - 1; i++) { | ||
current = current.get(args[i]); | ||
if (current) | ||
this.closest = current; | ||
else | ||
break; | ||
} | ||
this.closestIdx = i; | ||
} | ||
DeepMapEntry.prototype.exists = function () { | ||
this.assertNotDisposed(); | ||
var l = this.args.length; | ||
return this.closestIdx >= l - 1 && this.closest.has(this.args[l - 1]); | ||
}; | ||
DeepMapEntry.prototype.get = function () { | ||
this.assertNotDisposed(); | ||
if (!this.exists()) | ||
throw new Error("Entry doesn't exist"); | ||
return this.closest.get(this.args[this.args.length - 1]); | ||
}; | ||
DeepMapEntry.prototype.set = function (value) { | ||
this.assertNotDisposed(); | ||
var l = this.args.length; | ||
var current = this.closest; | ||
// create remaining maps | ||
for (var i = this.closestIdx; i < l - 1; i++) { | ||
var m = new Map(); | ||
current.set(this.args[i], m); | ||
current = m; | ||
} | ||
this.closestIdx = l - 1; | ||
this.closest = current; | ||
current.set(this.args[l - 1], value); | ||
}; | ||
DeepMapEntry.prototype.delete = function () { | ||
this.assertNotDisposed(); | ||
if (!this.exists()) | ||
throw new Error("Entry doesn't exist"); | ||
var l = this.args.length; | ||
this.closest.delete(this.args[l - 1]); | ||
// clean up remaining maps if needed (reconstruct stack first) | ||
var c = this.root; | ||
var maps = [c]; | ||
for (var i = 0; i < l - 1; i++) { | ||
c = c.get(this.args[i]); | ||
maps.push(c); | ||
} | ||
for (var i = maps.length - 1; i > 0; i--) { | ||
if (maps[i].size === 0) | ||
maps[i - 1].delete(this.args[i - 1]); | ||
} | ||
this.isDisposed = true; | ||
}; | ||
DeepMapEntry.prototype.assertNotDisposed = function () { | ||
// TODO: once this becomes annoying, we should introduce a reset method to re-run the constructor logic | ||
if (this.isDisposed) | ||
throw new Error("Concurrent modification exception"); | ||
}; | ||
return DeepMapEntry; | ||
}()); | ||
/** | ||
* @private | ||
*/ | ||
var DeepMap = /** @class */ (function () { | ||
function DeepMap() { | ||
this.store = new Map(); | ||
this.argsLength = -1; | ||
} | ||
DeepMap.prototype.entry = function (args) { | ||
if (this.argsLength === -1) | ||
this.argsLength = args.length; | ||
else if (this.argsLength !== args.length) | ||
throw new Error("DeepMap should be used with functions with a consistent length, expected: " + this.argsLength + ", got: " + args.length); | ||
if (this.last) | ||
this.last.isDisposed = true; | ||
return this.last = new DeepMapEntry(this.store, args); | ||
}; | ||
return DeepMap; | ||
}()); | ||
/** | ||
* computedFn takes a function with an arbitrarily amount of arguments, | ||
* and memoized the output of the function based on the arguments passed in. | ||
* | ||
* computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments | ||
* that is accepted. However, the amount of arguments must be consistent and default arguments are not supported. | ||
* | ||
* By default the output of a function call will only be memoized as long as the | ||
* output is being observed. | ||
* | ||
* The function passes into `computedFn` should be pure, not be an action and only be relying on | ||
* observables. | ||
* | ||
* Setting `keepAlive` to `true` will cause the output to be forcefully cached forever. | ||
* Note that this might introduce memory leaks! | ||
* | ||
* @example | ||
* const store = observable({ | ||
a: 1, | ||
b: 2, | ||
c: 3, | ||
m: computedFn(function(x) { | ||
return this.a * this.b * x | ||
}) | ||
}) | ||
const d = autorun(() => { | ||
// store.m(3) will be cached as long as this autorun is running | ||
console.log((store.m(3) * store.c)) | ||
}) | ||
* | ||
* @param fn | ||
* @param keepAlive | ||
*/ | ||
function computedFn(fn, keepAlive) { | ||
if (keepAlive === void 0) { keepAlive = false; } | ||
if (mobx.isAction(fn)) | ||
throw new Error("computedFn shouldn't be used on actions"); | ||
var memoWarned = false; | ||
var i = 0; | ||
var d = new DeepMap(); | ||
return function () { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i] = arguments[_i]; | ||
} | ||
var self = this; | ||
var entry = d.entry(args); | ||
// cache hit, return | ||
if (entry.exists()) | ||
return entry.get().get(); | ||
// if function is invoked, and its a cache miss without reactive, there is no point in caching... | ||
if (!keepAlive && !mobx._isComputingDerivation()) { | ||
if (!memoWarned) { | ||
console.warn("invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set"); | ||
memoWarned = true; | ||
} | ||
return fn.apply(self, args); | ||
} | ||
// create new entry | ||
var c = mobx.computed(function () { | ||
return fn.apply(self, args); | ||
}, { | ||
name: "computedFn(" + fn.name + "#" + (++i) + ")", | ||
keepAlive: keepAlive | ||
}); | ||
entry.set(c); | ||
// clean up if no longer observed | ||
if (!keepAlive) | ||
mobx.onBecomeUnobserved(c, function () { | ||
d.entry(args).delete(); | ||
}); | ||
// return current val | ||
return c.get(); | ||
}; | ||
} | ||
exports.computedFn = computedFn; | ||
exports.PENDING = PENDING; | ||
@@ -1261,0 +1430,0 @@ exports.FULFILLED = FULFILLED; |
{ | ||
"name": "mobx-utils", | ||
"version": "5.3.0", | ||
"version": "5.4.0", | ||
"description": "Utility functions and common patterns for MobX", | ||
@@ -5,0 +5,0 @@ "main": "mobx-utils.umd.js", |
@@ -82,2 +82,7 @@ # MobX-utils | ||
- [Examples](#examples-16) | ||
- [computedFn](#computedfn) | ||
- [Parameters](#parameters-18) | ||
- [Examples](#examples-17) | ||
- [DeepMapEntry](#deepmapentry) | ||
- [DeepMap](#deepmap) | ||
@@ -700,1 +705,45 @@ ## fromPromise | ||
``` | ||
## computedFn | ||
computedFn takes a function with an arbitrarily amount of arguments, | ||
and memoized the output of the function based on the arguments passed in. | ||
computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments | ||
that is accepted. However, the amount of arguments must be consistent and default arguments are not supported. | ||
By default the output of a function call will only be memoized as long as the | ||
output is being observed. | ||
The function passes into `computedFn` should be pure, not be an action and only be relying on | ||
observables. | ||
Setting `keepAlive` to `true` will cause the output to be forcefully cached forever. | ||
Note that this might introduce memory leaks! | ||
### Parameters | ||
- `fn` | ||
- `keepAlive` | ||
### Examples | ||
```javascript | ||
const store = observable({ | ||
a: 1, | ||
b: 2, | ||
c: 3, | ||
m: computedFn(function(x) { | ||
return this.a * this.b * x | ||
}) | ||
}) | ||
const d = autorun(() => { | ||
// store.m(3) will be cached as long as this autorun is running | ||
console.log((store.m(3) * store.c)) | ||
}) | ||
``` | ||
## DeepMapEntry | ||
## DeepMap |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
238520
46
4966
748