alexreardon / memoize-one Goto Github PK
View Code? Open in Web Editor NEWA memoization library which only remembers the latest invocation
License: MIT License
A memoization library which only remembers the latest invocation
License: MIT License
I have zipped an example project to demonstrate the bug.
The issue only occurs when I run a test using npm test
. If you run the project normally it works as expected (npm start
)
Tally is the value that gets incremented using memoize when the button clicks value changes
Within my test environment, it appears the comparison of memoize is ignored and the underlying function is called. I have not set up any mocks to alter the behaviour. Because I use memoize within componentDidUpdate and am updating the state within my memoized function I am receiving an infinite loop ONLY
in my test environment
Dependencies (as listed in the examples package.json)
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"memoize-one": "^5.1.1",
Environment:
Node: Tested on: 12.18.1 (lts, stable) and 14.4.0 (latest)
Actually the problem is not with typescript - the problem is with rollup.
https://unpkg.com/[email protected]/dist/memoize-one.cjs.js contains
module.exports = index;
which should be imported as import * as memoizeOne
while https://unpkg.com/[email protected]/dist/memoize-one.esm.js contains
export default index;
which should be imported as import memoizeOne
Result - the code for nodejs (~jest) require one behaviour, while the code for webpack(uses esm bundle) requires another.
It is not a problem for js/flow, as long babel is doing some around magic default imports, but typescript is quite strict about it.
The cjs bundle should match esm bundle.
(and fixing this would introduce a breaking change)
Thanks for the great library! Just a heads up and something I think worth considering; adding the index to the equality callback causes a lot of commonly used equality functions to break.
For instance the most popular one, shallowEqual
has as a third parameter a custom comparison function: shallowequal(value, other, [customizer], [thisArg])
. Same is true for the lodash deep comparison function.
Of course it is not a huge problem to change this in our code, but it breaks a lot of existing code like this: memoizeOne(params, shallowEqual)
It would be really nice if a React useMemo
style API was supported, e.g. memoize<X>(func: () => X, deps: any[]): X
.
Probably some way to achieve the same with memoize-one
but it's not immediately clear how.
Hi,
I'm using https://github.com/atlassian/react-beautiful-dnd which depends on this project. With the release of 3.1, this library appears to break IE11 with an error cannot redefine non-configurable property length
which I traced back to https://github.com/alexreardon/memoize-one/blob/master/src/index.js#L45 which was added in 3.1
If I forcefully revert to 3.0.1, IE11 works well again. Sorry I do not have a good test-case, so just heads up that there might be an issue with 3.1 which gets pulled into react-beautiful-dnd
by default now.
I've been using memoize-one from CJS for many years, but when I try to convert the calling code to ESM, I now get this error:
src/lib.ts:73:21 - error TS2349: This expression is not callable.
Type 'typeof import(".../common/temp/node_modules/.pnpm/[email protected]/node_modules/memoize-one/dist/memoize-one")' has no call signatures.
I'm pretty sure my import is correct:
import memoizeOne from 'memoize-one';
I'm not getting this error with any other dependency, so it seems specific to memoize-one. My package still builds correctly, which means it's maybe just an error with the types. I'm using the latest version (6.0.0).
Add some recipes to demonstrate some of the use cases of this library. Some ones I can think of write now
react-redux
state queriesreselect
Hey, don't know if this is the proper way to bring this up, but I was using this library to memoize data filtering functions before render in React, and for most purposes, this library has worked great.
I had 3 inputs to the memoized function: an object, an array, and a string. When the array and string would change, the memoized function would work as expected (that is, it would re-execute). However, when an attribute nested several levels deep in the object would change, the memoized function would return the cached result rather than re-execute (and from what I understand, this would imply that the under-the-hood implementation may have an issue doing deep object comparison on the inputs, which is already a pain in the ass in Javascript)
For anyone experiencing a similar issue, the workaround I used was to JSON.stringify the input object and use that as an additional memoized function argument. Since the string representation changes when the object changes, the library understands to re-execute the memoized function
I ran into typing issues when trying to use memoize-one for a function with generics. I played around and came up with a partial solution I want to share for future reference.
I think it's only a partial solution and not worthy of a PR because I don't know how to make it support a dynamic number of generics.
Play with it in the TypeScript playground here
// The overload signatures handle when the first arg is generic and when there are no generics.
function memoize<F extends <T extends any>(...args: [T, ...any[]]) => ReturnType<F>>(fn: F): F;
function memoize<F extends (...args: any[]) => ReturnType<F>>(fn: F): F;
function memoize<F extends <T extends any>(...args: [T, ...any[]]) => ReturnType<F>>(fn: F): F {
let lastArgs: unknown[] = [];
let lastResult: ReturnType<F>;
let calledOnce = false;
/**
* Compare arguments for shallow identity equality.
*/
const argsEqual = (a: unknown[], b: unknown[]): boolean => {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
};
/**
* Wrap function in memoized function.
*/
const memoized = <U extends any>(...newArgs: [U, ...any[]]): ReturnType<F> => {
if (!calledOnce || !argsEqual(lastArgs, newArgs)) {
const newResult = fn(...newArgs);
lastResult = newResult;
lastArgs = newArgs;
calledOnce = true;
console.log('NOT MEMOIZED', newArgs);
}
return lastResult;
};
return memoized as F;
}
/**
* Examples
*/
// Generic used.
const areEqual = <T>(a: T, b: T) => a === b;
const memoAreEqual = memoize(areEqual);
console.log(memoAreEqual(1, 1));
console.log(memoAreEqual(2, 1));
console.log(memoAreEqual('a', 'a'));
// No generic used.
const isGreater = (a: number, b: number) => a > b;
const memoIsGreater = memoize(isGreater);
console.log(memoIsGreater(1, 2));
console.log(memoIsGreater(2, 2));
console.log(memoIsGreater(2, 3));
I am interested to see how this library compares to other memoization libraries for caching single results. I expect it will be quite fast given that it has a restricted cache size of 1. Although, some of the babel transformation code might slow it down.
Here is what I am thinking so far:
export type EqualityFn = (newArgs: unknown[], lastArgs: unknown[]) => boolean;
function areInputsEqual(
newInputs: unknown[],
lastInputs: unknown[],
): boolean {
// no checks needed if the inputs length has changed
if (newInputs.length !== lastInputs.length) {
return false;
}
// Using for loop for speed. It generally performs better than array.every
// https://github.com/alexreardon/memoize-one/pull/59
for (let i = 0; i < newInputs.length; i++) {
// using shallow equality check
if (newInputs[i] !== lastInputs[i]) {
return false;
}
}
return true;
}
export default function memoizeOne<ResultFn extends (this: unknown, ...newArgs: unknown[]) => ReturnType<ResultFn>>(
this: unknown,
resultFn: ResultFn,
isEqual: EqualityFn = areInputsEqual,
): ResultFn {
let lastThis: unknown;
let lastArgs: unknown[] = [];
let lastResult: ReturnType<ResultFn>;
let calledOnce: boolean = false;
// breaking cache when context (this) or arguments change
function memoized(this: unknown, ...newArgs: unknown[]): ReturnType<ResultFn> {
if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
return lastResult;
}
// Throwing during an assignment aborts the assignment: https://codepen.io/alexreardon/pen/RYKoaz
// Doing the lastResult assignment first so that if it throws
// nothing will be overwritten
lastResult = resultFn.apply(this, newArgs);
calledOnce = true;
lastThis = this;
lastArgs = newArgs;
return lastResult;
}
return (memoized as ResultFn);
}
The "Play with this example" are broken, webpackbin.com is now for sale...
Doc has statement:
Tested with all built in JavaScript types.
But it is not working properly with NaN value, example:
let i = 0;
function getIncrementedNumber(parameter) {
return i++;
}
const memoizedIncrementedNumber = memoize(getIncrementedNumber);
const value = NaN;
console.log(memoizedIncrementedNumber(value)); // log 0
console.log(memoizedIncrementedNumber(value)); // log 1
We use memoize-one for almost two years with promises just fine, but looking at the code now I realized that it maybe is not intended or tested to work with them. But maybe all that's missing is an description of fn
in the readme (?)
The mentioned error handling likely never was triggered, since promises fall into an error state instead of throwing an error.
Memoizing a function sets its length property to zero:
import memoizeOne from 'memoize-one'
f = (a, b, c) => a*b*c
memoF = memoizeOne(f)
console.log(f.length) // 3
console.log(memoF.length) // 0
This is not really a big deal: length is a configurable property, so if you care about f.length
you can just set it manually afterwards using Object.definiteProperty
.
Should memoizeOne
automatically configure the memoized functions length property?
A little context 😜 :
// This behaviour is debatable.
it('should memoize the previous result even if the this context changes', () => {
function getA() {
return this.a;
}
const memoized = memoizeOne(getA);
const temp1 = {
a: 20,
getMemoizedA: memoized,
};
const temp2 = {
a: 30,
getMemoizedA: memoized,
};
expect(temp1.getMemoizedA()).to.equal(20);
// this might be unexpected
expect(temp2.getMemoizedA()).to.equal(20);
});
The decision is: should this
be treated as an argument for the purpose of memoization?
(do a comparison between the current context this
and the previous execution context lastThis
.
new
, call
, apply
, bind
, or implicit control obj.foo()
(do not do any context comparison)
this
can have unexpected results (see example above)this
- otherwise there is a chance of invalid resultsMotivation:
memoize-one stores inside closure the last this, result and arguments. This may lead to the fact that references to large objects will be cached, and large objects won't be collected by the garbage collector.
Suggestion:
For instance, to add the static "reset" method to the returned function, which will reset lastThis, lastArgs, lastResult, calledOnce to the initial values.
memoizeOne seems not to throw errors when the function call is memoized.
For example, in the function below I would expect the second call to throwingDivide
to throw an error, but it does not. Is this behavior expected?
var memoizeOne = require("memoize-one")
function _throwingDivide(x, y) {
if (y===0) {
throw Error('cannot divide by zero')
}
return x/y
}
const throwingDivide = memoizeOne(_throwingDivide)
try {
throwingDivide(4, 0)
}
catch (error) {
console.log(error)
}
console.log(throwingDivide(4, 0)) // does not throw !
Thanks for this library—love it's simplicity!
When used with react-hot-loader
it throws this error
VM37389:13 Uncaught ReferenceError: calledOnce is not defined
at ProxyComponent.result [as getDateFilterMinDateRefinement] (eval at __reactstandin__regenerateByEval (VM36190 CourseSearch.js:NaN), <anonymous>:13:5)
at eval (eval at __reactstandin__regenerateByEval (VM36190 CourseSearch.js:NaN), <anonymous>:45:36)
at ProxyFacade (react-hot-loader.development.js:647)
at mountIndeterminateComponent (react-dom.development.js:13380)
at beginWork (react-dom.development.js:13820)
at performUnitOfWork (react-dom.development.js:15863)
at workLoop (react-dom.development.js:15902)
at HTMLUnknownElement.callCallback (react-dom.development.js:100)
at Object.invokeGuardedCallbackDev (react-dom.development.js:138)
at invokeGuardedCallback (react-dom.development.js:187)
getDateFilterMinDateRefinement()
is a function that's wrapped in memoize()
.
The error happens, for example, when the component containing memoization is removed from code and then hit "Save" so that it hot-reloads.
Hi @alexreardon, v5.2.0 was release 3 days ago but it cannot be installed via npm.
Can you help, please?
Doc has statement:
Tested with all built in JavaScript types.
But it is not working properly with arrays, example:
function sum(parameter: number[]) {
return parameter.reduce((acc, elem) => acc + elem, 0);
}
const memoizedSum = memoize(sum);
const myArr = [7];
console.log(memoizedSum(myArr)); // 7
myArr.push(7);
console.log(myArr); // [7,7]
console.log(memoizedSum(myArr)); //7
What is the possibility to implement MD5 checksum to evaluate equality for object parameters? I have used this to validate changes in reactjs from large objects but I don't know if it is feasible to implement in this project.
According to the official example:
const add = (a, b) => a + b;
const memoizedAdd = memoizeOne(add);
memoizedAdd(1, 2); // 3
When I try to switch the position of the argument, instead of taking the cached result, I get a new result, which is also 3!
Like this:
memoizedAdd(2, 1); // 3
Read the source code, found when comparing the parameters of the code, should not be such a comparison. If it's a shallow comparison, I think it should be like this:
export default function areInputsEqual(
newInputs: readonly unknown[],
lastInputs: readonly unknown[],
): boolean {
// no checks needed if the inputs length has changed
if (newInputs.length !== lastInputs.length) {
return false;
}
// Using for loop for speed. It generally performs better than array.every
// https://github.com/alexreardon/memoize-one/pull/59
// Like this
if (!newInputs.every(arg => lastInputs.includes(arg))) {
return false;
}
return true;
}
In this way, even if the same parameter, and the parameter position changes, the result should be the result of the cache, not the new result! After all, the results are the same.
Welcome the author of the library to communicate and correct, thank you!
What about an optional argument to memoize all function calls?
That could be very helpful for event handler creators.
For example:
<Line
key={line.id}
onMouseClick={this.createOnMouseClickLine(line.id)}
/>
Comparing objects with custom comparator not working properly, example:
function nestedXtoString(myObj) {
return myObj.x.toString();
}
const memoizedNestedXtoString = memoize(nestedXtoString, ([prev], [curr]) => {
console.log("compared", prev, curr);
return prev.x === curr.x;
});
const myObj = {x: 55};
console.log(memoizedNestedXtoString(myObj)); // "55"
myObj.x = 7;
console.log(myObj); // {x: 7}
console.log(memoizedNestedXtoString(myObj)); // 55
Comparator gives log:
compared {x: 7} {x: 7}
const shallowMemoized = memoizeOne(identity);
const deepMemoized = memoizeOne(identity, isDeepEqual);
const result1 = shallowMemoized({ foo: 'bar' });
const result2 = shallowMemoized({ foo: 'bar' });
This isn't a shallow comparison - it's a reference comparison. Checking each key in the object for equality (by reference) would be a shallow comparison.
Firstly, thanks for the great library!
Just to start a discussion: locally i've been using this typing for this lib
declare export function memoize<A, B, C, D, R>(
func: (a: A, b: B, c: C, d: D) => R,
equalityCheck?: (newArgs: [A, B, C, D], lastArgs: [A, B, C, D]) => boolean
): (a: A, b: B, c: C, d: D) => R
this lets the equality check be more typed than mixed
A downside is if you export a memoized function you need to declare all your types
export default memoize<number, string, void, void, _>((num, str) => num + str)
(maybe this could be better if facebook/flow#6875 was fixed)
has anyone else attempted a strong flow type?
Hi there, I'm getting this error after upgrading, flow version is 0.92.1
.
Cannot assign function to `result` because function [1] is incompatible with `ResultFn` [2].
node_modules/memoize-one/src/index.js:34:28
v------------------------------
34| const result: ResultFn = function(...newArgs: mixed[]) {
35| if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
36| return lastResult;
37| }
38|
39| // Throwing during an assignment aborts the assignment: https://codepen.io/alexreardon/pen/RYKoaz
40| // Doing the lastResult assignment first so that if it throws
41| // nothing will be overwritten
42| lastResult = resultFn.apply(this, newArgs);
43| calledOnce = true;
44| lastThis = this;
45| lastArgs = newArgs;
46| return lastResult;
47| };
^
References:
node_modules/memoize-one/src/index.js:34:28
34| const result: ResultFn = function(...newArgs: mixed[]) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
node_modules/memoize-one/src/index.js:34:17
34| const result: ResultFn = function(...newArgs: mixed[]) {
^^^^^^^^ [2]
The doc says:
The default equality function also does not check anything if the length of the arguments changes.
which is not correct according to the current source code.
The arguments of EqualityFn
are defined as being unknown
even though they are known (i.e. the same as ...newArgs of ResultFn).
Our build did fail today due to the recently added TypeScript types. My suggestion would be to type the args of EqualityFn
as any (just like the typings on DefinitelyTyped did which does make that users do not have to cast the unknown type) or to strong-type it, e.g.:
export type EqualityFn<Args = any> = (
newArgs: Args,
lastArgs: Args,
) => boolean;
export default function memoizeOne<
ResultFn extends (this: any, ...newArgs: T) => ReturnType<ResultFn>,
T extends any[]
>(resultFn: ResultFn, isEqual: EqualityFn<T> = areInputsEqual): ResultFn {
...
Currently, it will remember the rejected promise.
I plan on removing ie11 support from memoize-one
This will allow the library to more gracefully set the .length
of a memoized function, as well as ship a (smaller*) bundle as less language features will need to be compiled
*Exact size drop TBC
Still to be confirmed, but a sufficiently low common build target (eg supports es6-module
)
This change will be done in a major
. If you need to support ie11, then you can use the not-latest major
No timeline as yet. Will confirm at a later date
Hey there 👋
Our bot, Adaptly, found that 7 out of 11 currently open dependency update PRs can be merged.
That's 64% right there:
I guess I am not using it correctly,
import memoizeOne from 'memoize-one';
const print = (list: Array<number>): void => list.forEach(num => {
console.log(num);
});
const mem_print = memoizeOne(print);
let list = [1,2,3];
mem_print(list);
mem_print(list);
list = [...list, 4];
mem_print(list);
Compiled from TS 3.8 to JS with tsc index.js
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
exports.__esModule = true;
var memoize_one_1 = require("memoize-one");
var print = function (list) { return list.forEach(function (num) {
console.log(num);
}); };
var mem_print = memoize_one_1["default"](print);
var list = [1, 2, 3];
mem_print(list);
mem_print(list);
list = __spreadArrays(list, [4]);
mem_print(list);
Output:
var mem_print = memoize_one_1["default"](print);
^
TypeError: memoize_one_1.default is not a function
at Object.<anonymous> (D:\MyGit\plaints\index.js:14:41)
at Module._compile (internal/modules/cjs/loader.js:1158:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
at Module.load (internal/modules/cjs/loader.js:1002:32)
at Function.Module._load (internal/modules/cjs/loader.js:901:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
at internal/main/run_main_module.js:18:47
tsconfig
{
"compilerOptions": {
"target": "ES2018",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"module": "esnext",
"moduleResolution": "node",
}
}
Hi and thanks for this amazing lib!
The benchmarks section:
Could it be updated?
Thanks!
The 5.2.0
release accidentally introduced a breaking change when using CommonJS require()
.
5.1.1
:
> require('memoize-one')
[Function: memoizeOne]
5.2.0
:
> require('memoize-one')
{ default: [Function: memoizeOne], memoizeOne: [Function: memoizeOne] }
i have the following code:
const {
data = { merchant: null, items: null, categories: null },
isPending,
isRejected,
isResolved,
isSettled,
error,
run
} = useAsync({
deferFn: _loadMerchantData
});
console.log(props);
const {
match: { params }
} = props;
const isEdit = params.id === 'edit';
useEffect(() => {
debugger;
console.log(isEdit, params.id, run);
if (!isEdit) {
run(params.id, isEdit, run);
}
}, [run]);
i declared _loadMerchantData outside of the component as recommended by the docs. Still, run is causing a infinite loop. Any idea why?
It would be good to know some information about cache breaking for debugging purposes. The big idea is that it could expose a lot of debug information on the console
I am not sure what the api would be, but here is some initial thoughts:
import memoizeOne, { trace } from 'memoizeOne';
const times = (arg1: number, arg2: number): number => arg1 * arg2;
const memoized = memoizeOne(times);
trace(times(1, 2));
// first time call
// new arguments: [1, 2]
// returning new result: 2
trace(times(1, 2));
// new arguments [1, 2] match previous arguments
// returning previous result: 2
trace(times(1, 3));
// cache bust!
// new arguments [1, 3] do not match previous arguments [1, 2]
// argument change: [1, *2*]
// calculating new result and returning: 3
I would like to be able to provide a function:
shouldRecompute: (lastResult: T, lastArgs: Array<unknown>, newArgs: Array<unknown>) => boolean
instead of custom equality function. In my case, whether the result should be recomputed depends on the previous result.
Use case:
I am building a calendar with monthly view. For input data (events etc.) and given date, I filter events and compute begin and end, which are first and last timestamp for that month. In shouldRecompute
, I would like just to check if new date is between these two values. Alternative is to have a custom equality which would compare months of previous and new date argument, but this is pretty costly (in general they are unix timestamps).
In general, my opinion is that shouldRecompute
is better than custom equality even for general case. Most of the time, arguments have different types, so custom equality ends up being a branchy if
statement checking for types of arguments and performing duty.
With shouldRecompute
, it would be possible to test all arguments at once, which is much easier to reason about.
E.g. before:
if (typeof a === 'string') {
return a === b;
} else if (a instanceof Date) {
return isSameMonth(a, b);
} else {
...
}
after:
return lastArgs[0] === newArgs[0] && isSameMonth(lastArgs[1], newArgs[1]) && ...;
According to Are the Types Wrong, memoize-one has an incorrect default export.
This can cause errors with TypeScript, depending on tsconfig.json settings. For example:
"type": "commonjs"
) and tsconfig.json "module": "node16", "moduleResolution": "node16"
, then everything works."type": "module"
) and tsconfig.json "module": "node16", "moduleResolution": "node16"
, problems arise:
memoizeOne
directly, the TypeScript compiler throws an error at compile time:
import memoizeOne from 'memoize-one';
// This expression is not callable.
// Type 'typeof import("node_modules/memoize-one/dist/memoize-one")' has no call signatures.ts(2349)
const memoized = memoizeOne((a: number, b: number) => a + b);
.default
, you get an error at runtime:
import memoizeOne from 'memoize-one';
// TypeError: memoizeOne.default is not a function
const memoized = memoizeOne.default((a: number, b: number) => a + b);
As I understand it, there are two possible solutions:
.d.ts
file that reflects the CommonJS export assignment approach used by the Rollup-generated memoize-one build. (There may be a way to get tsc to do this for you, but I haven't been able to figure it out.) I believe that this could be done in a semver-patch release.declare namespace memoizeOne {
export type EqualityFn<TFunc extends (...args: any[]) => any> = (newArgs: Parameters<TFunc>, lastArgs: Parameters<TFunc>) => boolean;
export type MemoizedFn<TFunc extends (this: any, ...args: any[]) => any> = {
clear: () => void;
(this: ThisParameterType<TFunc>, ...args: Parameters<TFunc>): ReturnType<TFunc>;
};
}
declare function memoizeOne<TFunc extends (this: any, ...newArgs: any[]) => any>(
resultFn: TFunc, isEqual?: memoizeOne.EqualityFn<TFunc>
): memoizeOne.MemoizedFn<TFunc>;
export = memoizeOne;
exports
(so Node.js, and TypeScript when using node16
module resolution, can see it), with ESM-specific types. This may be a semver-major change.If you're interested in one of these approaches, I can try and open a PR.
Error when packaging using Webpack.
e:\weizhi\server\dist\webpack:\node_modules\@metascraper\helpers\index.js:234
const jsonld = memoizeOne((url, $) => {
^
TypeError: memoizeOne is not a function
at Object../node_modules/@metascraper/helpers/index.js (e:\weizhi\server\dist\webpack:\node_modules\@metascraper\helpers\index.js:234:1)
at __webpack_require__ (e:\weizhi\server\dist\webpack:\webpack\bootstrap:25:1)
at Object../node_modules/metascraper/src/index.js (e:\weizhi\server\dist\webpack:\node_modules\metascraper\src\index.js:3:19)
at __webpack_require__ (e:\weizhi\server\dist\webpack:\webpack\bootstrap:25:1)
When "module" (ESM) is used in the "package.json" file of "memoize-one", Webpack will parse it first, but Nodejs "require" no support "exports default",
I can only do this require('memoize-one').default to get rid of errors.
I saw this trick in React 18 types:
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210/files#diff-32cfd8cb197872bcba371f5018185d2e75fa540b52cda2dd7d8ac12dcc021299R1068
And the discussion DefinitelyTyped/DefinitelyTyped#52873 (comment)
I think we should ban implicit any as well. The only issue is that it might be a breaking change, But I don't think so, from looking at memoize-one
definitions. It might only force some users to add types or explicit any
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.