Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"url": "https://github.com/reduxjs/reselect/issues"
},
"scripts": {
"build:commonjs": "cross-env BABEL_ENV=commonjs babel src/index.ts --extensions .ts --out-dir lib ",
"build:es": "babel src/index.ts --extensions .ts --out-dir es",
"build:commonjs": "cross-env BABEL_ENV=commonjs babel src/*.ts --ignore src/types.ts --extensions .ts --out-dir lib ",
"build:es": "babel src/*.ts --ignore src/types.ts --extensions .ts --out-dir es",
"build:umd": "cross-env NODE_ENV=development rollup -c -o dist/reselect.js",
"build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/reselect.min.js",
"build:types": "tsc",
Expand Down Expand Up @@ -110,5 +110,8 @@
"ts-jest": "26.5.6",
"typescript": "^4.4.0"
},
"dependencies": {}
"dependencies": {
"memoize-one": "^6.0.0-beta.1",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6.0.0 has just been released 🎉

"micro-memoize": "^4.0.9"
}
}
153 changes: 153 additions & 0 deletions src/defaultMemoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import type { EqualityFn } from './types'

// Cache implementation based on Erik Rasmussen's `lru-memoize`:
// https://github.com/erikras/lru-memoize

interface Entry {
key: unknown
value: unknown
}

interface Cache {
get(key: unknown): unknown | undefined
put(key: unknown, value: unknown): void
getValues(): unknown[]
}

function createSingletonCache(equals: EqualityFn): Cache {
let entry: Entry
return {
get(key: unknown) {
if (entry && equals(entry.key, key)) {
return entry.value
}
},

put(key: unknown, value: unknown) {
entry = { key, value }
},

getValues() {
return entry ? [entry.value] : []
}
}
}

function createLruCache(maxSize: number, equals: EqualityFn): Cache {
const entries: Entry[] = []

function get(key: unknown) {
const cacheIndex = entries.findIndex(entry => equals(key, entry.key))

// We found a cached entry
if (cacheIndex > -1) {
const entry = entries[cacheIndex]

// Cached entry not at top of cache, move it to the top
if (cacheIndex > 0) {
entries.splice(cacheIndex, 1)
entries.unshift(entry)
}

return entry.value
}

// No entry found in cache, return null
return undefined
}

function put(key: unknown, value: unknown) {
if (!get(key)) {
// TODO Is unshift slow?
entries.unshift({ key, value })
if (entries.length > maxSize) {
entries.pop()
}
}
}

function getValues() {
return entries.map(entry => entry.value)
}

return { get, put, getValues }
}

export const defaultEqualityCheck: EqualityFn = (a, b): boolean => {
return a === b
}

export function createCacheKeyComparator(equalityCheck: EqualityFn) {
return function areArgumentsShallowlyEqual(
prev: unknown[] | IArguments | null,
next: unknown[] | IArguments | null
): boolean {
if (prev === null || next === null || prev.length !== next.length) {
return false
}

// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice!

const length = prev.length
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false
}
}

return true
}
}

export interface DefaultMemoizeOptions {
equalityCheck?: EqualityFn
resultEqualityCheck?: EqualityFn
maxSize?: number
}

// defaultMemoize now supports a configurable cache size with LRU behavior,
// and optional comparison of the result value with existing values
export function defaultMemoize<F extends (...args: any[]) => any>(
func: F,
equalityCheckOrOptions?: EqualityFn | DefaultMemoizeOptions
): F {
const providedOptions =
typeof equalityCheckOrOptions === 'object'
? equalityCheckOrOptions
: { equalityCheck: equalityCheckOrOptions }

const {
equalityCheck = defaultEqualityCheck,
maxSize = 1,
resultEqualityCheck
} = providedOptions

const comparator = createCacheKeyComparator(equalityCheck)

const cache =
maxSize === 1
? createSingletonCache(comparator)
: createLruCache(maxSize, comparator)

// we reference arguments instead of spreading them for performance reasons
return function () {
let value = cache.get(arguments)
if (value === undefined) {
// @ts-ignore
value = func.apply(null, arguments)

if (resultEqualityCheck) {
const existingValues = cache.getValues()
const matchingValue = existingValues.find(ev =>
resultEqualityCheck(ev, value)
)

if (matchingValue !== undefined) {
return matchingValue
}
}

cache.put(arguments, value)
}
return value
} as F
}
Loading