UNPKG

@fantastic-utils/memo

Version:

powerful memorization lib, which inspired by memoize-one, proxy-memoize

199 lines (148 loc) 5.5 kB
# @fantastic-utils/memo A memo library which can be used in many cases, this library is inspired by `memoize-one`, `proxy-memoize`, `async-memoize-one`, and have more feature to support different use cases. [![NPM version](https://img.shields.io/npm/v/@fantastic-utils/memo.svg?style=flat)](https://npmjs.org/package/@fantastic-utils/memo) [![NPM downloads](http://img.shields.io/npm/dm/@fantastic-utils/memo.svg?style=flat)](https://npmjs.org/package/@fantastic-utils/memo) ## Difference with others `memoize-one` and `proxy-memoize` are great, but they are only for some general cases, as many tools and libraries are designed in a special way, so some corner case can't be handled correctly, so this library build up for solving this issue. - `memoize-one`: use shallow compare as a straightforward way. - `proxy-memoize`: is build on `proxy-compare` which author designed as a stateless library, and `proxy-memoize` use its markup key to compare old object and new object. As many library now use the `Proxy` feature, and `Proxy` always work with `Scope` feature, so we can't always simply treat object as a immutable value, especially in some library design, maybe that's we wanted, so This library give an another choice to control the logic, not just a dead code. ## Installation ```bash npm install @fantastic-utils/memo ``` ## Usage this library contains `memo` and `memoAsync`. ```javascript import { memo, memoAsync, original } from '@fantastic-utils/memo'; // Basic cache const add = (a, b) => a + b; const memoAdd = memo(add); memoAdd(1, 2); // 3 memoAdd(1, 2); // 3 from cache // Deep cache const add = (objA, arrayB) => objA.a + objB[0]; const memoAdd = memo(add); memoAdd({ a: 1 }, [2]); // 3 memoAdd({ a: 1 }, [2]); // 3 from cache // Async cache const add = async (a, b) => await (a + b); const memoAdd = memoAsync(add); await memoAdd(1, 2); // 3 await memoAdd(1, 2); // 3 // Shallow compare const add = (objA, b) => objA.a + b; const memoAdd = memo(add, { objectShallowCompare: true }); const obj = { a: 1 }; memoAdd(obj, 2); // 3 obj.a = 2; memoAdd(obj, 2); // 3 from cache // console log raw data, as argument is wrapped by proxy const add = (objA, b) => { console.log(original(objA)); return objA.a + b; }; ``` ### Use with `React` pure component ```javascript import { memo, original } from '@fantastic-utils/memo'; const TestComponent = memo((props) => { // Take care of console.log, as console.log will construct proxy values // use original to get the raw value. // Note that `.detail` will be marked as used console.log(original(props.detail)); return ( <> <h3>{props.detail.address}</h3> </> ); }); export default TestComponent; ``` ### Reference ```ts // memo function memo(fn, memo: MemoConfig): fn // async memo function memoAsync(fn, memo: MemoConfig): fn // Get raw object, as arg are wrapped by proxy original(arg): object ``` ```ts export interface MemoConfig { objectShallowCompare?: boolean; shouldCompare?: (args?: any[], cachedArgsCfg?: NormalizeArgCfg[]) => boolean; isChanged?: (args?: any[], cachedArgsCfg?: NormalizeArgCfg[]) => boolean; } /** * @param t The Type * @param v The normalized value * @param r The raw value * @param a The affected info */ export interface NormalizeArgCfg { t: string; v: unknown; r: unknown; a: Affected; } ``` ## Advance Usage ### shouldCompare **API: `function(newArgs: any[], cachedArgsCfg: NormalizeArgCfg[]): boolean`.** `shouldCompare` let user to determine whether to compare args with default compare logic or not, ```javascript import { memo } from '@fantastic-utils/memo'; const add = (a, b) => a + b; const memoAdd = memo(add, { shouldCompare(newArgs, cachedArgCfg) { return newArgs[0] === 1; }, }); memoAdd(1, 2); // 3 memoAdd(1, 2); // 3 from cache expect(memoAdd).toBeCalledTimes(1); // only call once ``` ### isChanged **API: `function(newArgs: any[], cachedArgsCfg: NormalizeArgCfg[]): boolean`.** `isChanged` is a high level api than `shouldCompare` to control all compare logic. ```javascript import { memo } from '@fantastic-utils/memo'; const add = (a, b) => a + b; const memoAdd = memo(add, { isChanged(newArgs, cachedArgCfg) { return newArgs[0] === 1; }, }); memoAdd(1, 2); // 3 memoAdd(1, 2); // 3 from re-run function expect(memoAdd).toBeCalledTimes(2); // call twice ``` ### Difference between `shouldCompare` and `isChanged` The biggest difference between `shouldCompare` and `isChanged` is that `shouldCompare` control how to use cache, and `isChanged` control how to compare. sometimes `shouldCompare` used to improve performance, and `isChanged` used to customize compare logic. Please refer to above code see the compare detail. ## Performance compare Benchmark code pls refer to `__benchmark__` folder. ```bash @fantastic-utils/memo deep x 82,670,951 ops/sec ±2.32% (85 runs sampled) proxy-memoize deep x 779,043 ops/sec ±3.83% (85 runs sampled) memoize-state deep x 22,624,061 ops/sec ±1.82% (81 runs sampled) Fastest is @fantastic-utils/memo deep ``` ```bash @fantastic-utils/memo shallow x 26,180,311 ops/sec ±1.04% (89 runs sampled) proxy-memoize shallow x 1,120,131 ops/sec ±1.29% (86 runs sampled) memoize-one shallow x 17,569,314 ops/sec ±1.66% (83 runs sampled) memoize-state shallow x 18,697,192 ops/sec ±3.24% (86 runs sampled) Fastest is @fantastic-utils/memo shallow ``` ## Develop ```bash $ npm install ``` ```bash $ npm run dev $ npm run build ``` ## LICENSE MIT