Comparing version 0.2.0-0 to 0.3.0
135
lib/index.js
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -6,3 +6,18 @@ Object.defineProperty(exports, "__esModule", { | ||
}); | ||
exports.defaultMemoize = defaultMemoize; | ||
var isObject = function isObject(a) { | ||
return typeof a == 'object' && a !== null; | ||
}; | ||
var isArray = function isArray(a) { | ||
return Array.isArray(a); | ||
}; | ||
var isFunction = function isFunction(a) { | ||
return typeof a === 'function'; | ||
}; | ||
var identity = function identity(a) { | ||
return a; | ||
}; | ||
var defaultEqualityCheck = function defaultEqualityCheck(a, b) { | ||
@@ -12,9 +27,9 @@ return a === b; | ||
function areArgumentsShallowlyEqual(equalityCheck, prev, next) { | ||
if (prev === null || next === null || prev.length !== next.length) { | ||
function areArgumentsEqual(equalityCheck, a, b) { | ||
if (a === null || b === null || a.length !== b.length) { | ||
return false; | ||
} | ||
for (var i = prev.length - 1; i >= 0; --i) { | ||
if (!equalityCheck(prev[i], next[i])) { | ||
for (var i = a.length - 1; i >= 0; --i) { | ||
if (!equalityCheck(a[i], b[i])) { | ||
return false; | ||
@@ -27,15 +42,16 @@ } | ||
function defaultMemoize(fn) { | ||
var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck; | ||
var lastArgs = null; | ||
var lastResult = null; | ||
return function () { | ||
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { | ||
lastResult = fn.apply(null, arguments); | ||
} | ||
lastArgs = arguments; | ||
return lastResult; | ||
var createMemoizor = exports.createMemoizor = function createMemoizor() { | ||
var equalityCheck = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultEqualityCheck; | ||
return function (fn) { | ||
var lastArgs = null; | ||
var lastResult = null; | ||
return function () { | ||
if (!areArgumentsEqual(equalityCheck, lastArgs, arguments)) { | ||
lastResult = fn.apply(null, arguments); | ||
} | ||
lastArgs = arguments; | ||
return lastResult; | ||
}; | ||
}; | ||
} | ||
}; | ||
@@ -54,56 +70,53 @@ function makeDependenciesFn(fns, next) { | ||
function createNestedSelectors(create, fns) { | ||
if (Array.isArray(fns)) { | ||
return fns.map(function (fn) { | ||
return Array.isArray(fn) ? create(fn) : fn; | ||
function createNestedSelectors(selectors, next) { | ||
return selectors.map(function (fn) { | ||
return isFunction(fn) ? fn : next(fn); | ||
}); | ||
} | ||
function normalizeSelector(selector) { | ||
if (isArray(selector)) { | ||
return selector.map(function (nestedSelector) { | ||
return (isArray(nestedSelector) ? nestedSelector : [nestedSelector]).map(normalizeSelector); | ||
}); | ||
} | ||
return fns; | ||
} | ||
if (isObject(selector)) { | ||
var objectKeys = Object.keys(selector); | ||
return [objectKeys.map(function (key) { | ||
return normalizeSelector(selector[key]); | ||
}), [function () { | ||
for (var _len = arguments.length, values = Array(_len), _key = 0; _key < _len; _key++) { | ||
values[_key] = arguments[_key]; | ||
} | ||
var createSelectorCreator = exports.createSelectorCreator = function createSelectorCreator(memoize, memoizeOptions) { | ||
function createSelector(fns) { | ||
var initialRecomputations = Array(fns.length).fill(0); | ||
var recomputations = initialRecomputations; | ||
return values.reduce(function (composition, value, index) { | ||
return Object.assign(composition, { | ||
[objectKeys[index]]: value | ||
}); | ||
}, {}); | ||
}]]; | ||
} | ||
var selector = fns.reduceRight(function (next, currentFns, index) { | ||
currentFns = createNestedSelectors(createSelector, currentFns); | ||
if (isFunction(selector)) { | ||
return selector; | ||
} | ||
var selectors = next ? makeDependenciesFn(currentFns, next) : currentFns; | ||
throw new Error(`Invalid value of type ${typeof selector} for creating a selector`); | ||
} | ||
return memoize(function () { | ||
recomputations[index] += 1; | ||
return selectors.apply(null, arguments); | ||
}, memoizeOptions); | ||
}, null); | ||
var createSelectorCreator = exports.createSelectorCreator = function createSelectorCreator(memoize) { | ||
function createSelector(selector) { | ||
var selectorNormalized = normalizeSelector(isFunction(selector) ? [selector, identity] : selector); | ||
selector.recomputations = function () { | ||
return recomputations; | ||
}; | ||
selector.resetRecomputations = function () { | ||
return recomputations = initialRecomputations; | ||
}; | ||
return selector; | ||
return selectorNormalized.reduceRight(function (next, cur, index) { | ||
var dependenciesFn = makeDependenciesFn(createNestedSelectors(cur, createSelector), next); | ||
return memoize(dependenciesFn); | ||
}, identity); | ||
} | ||
return createSelector; | ||
}; | ||
var createSelectorsCreator = exports.createSelectorsCreator = function createSelectorsCreator(createSelector) { | ||
return function (selectors) { | ||
if (Array.isArray(selectors)) { | ||
return createSelector(selectors); | ||
} | ||
var keys = Object.keys(selectors); | ||
var memoizedSelectors = {}; | ||
for (var i = keys.length - 1; i >= 0; --i) { | ||
var key = keys[i]; | ||
memoizedSelectors[key] = createSelector(selectors[key]); | ||
} | ||
return memoizedSelectors; | ||
}; | ||
}; | ||
var createSelector = exports.createSelector = createSelectorCreator(defaultMemoize); | ||
var createSelectors = exports.createSelectors = createSelectorsCreator(createSelector); | ||
var createSelector = exports.createSelector = createSelectorCreator(createMemoizor()); | ||
exports.default = createSelector; |
{ | ||
"name": "re-select", | ||
"version": "0.2.0-0", | ||
"version": "0.3.0", | ||
"description": "Memoized selector library", | ||
@@ -8,2 +8,3 @@ "main": "lib/index.js", | ||
"test": "mocha --compilers js:babel-register --ui tdd --recursive", | ||
"test:cov": "nyc --reporter=text mocha --compilers js:babel-register --ui tdd --recursive", | ||
"compile": "babel -d lib/ src/", | ||
@@ -37,4 +38,5 @@ "prepublish": "npm test && npm run compile" | ||
"babel-register": "^6.24.1", | ||
"mocha": "^3.4.2" | ||
"mocha": "^3.4.2", | ||
"nyc": "^11.4.1" | ||
} | ||
} |
168
README.md
# re-select | ||
Memoized selector library | ||
[![Travis branch](https://img.shields.io/travis/bearyinnovative/re-select/master.svg?style=flat-square)](https://travis-ci.org/bearyinnovative/re-select) | ||
[![npm](https://img.shields.io/npm/v/re-select.svg?style=flat-square)](https://www.npmjs.com/package/re-select) | ||
The motivation behind this library is to improve [reselect](https://github.com/reactjs/reselect) usage to be more resilient and concise. By using the new way to construct selectors, you should be able to write selectors that are easier to combine with. | ||
```JavaScript | ||
/* selectors.js | ||
** | ||
** You never call createSelector in your `selectors.js` | ||
** import { createSelector } from 'reselect' | ||
*/ | ||
const shopItemsSelector = state => state.shop.items | ||
const taxPercentSelector = state => state.shop.taxPercent | ||
// Notice how to use an array structure to describe a selector | ||
const subtotalSelector = [ | ||
shopItemsSelector, | ||
items => items.reduce((acc, item) => acc + item.value, 0) | ||
] | ||
const taxSelector = [ | ||
[subtotalSelector, taxPercentSelector], | ||
(subtotal, taxPercent) => subtotal * (taxPercent / 100) | ||
] | ||
let exampleState = { | ||
shop: { | ||
taxPercent: 8, | ||
items: [ | ||
{ name: 'apple', value: 1.20 }, | ||
{ name: 'orange', value: 0.95 }, | ||
] | ||
} | ||
} | ||
// container.js | ||
// On your view layer where the selectors actually get called | ||
import createSelector from 're-select'; | ||
connect(createSelector({ | ||
subtotal: subtotalSelector, // 2.15 | ||
tax: taxSelector, // 0.172 | ||
})) | ||
``` | ||
## Installation | ||
``` | ||
npm install re-select | ||
``` | ||
## Examples | ||
This introduction will assume you have a basic understanding of [reselect](https://github.com/reactjs/reselect#example). The major difference between re-select and reselect is how you describe/combine a selector. | ||
```JavaScript | ||
const getVisibilityFilter = (state) => state.visibilityFilter | ||
const getTodos = (state) => state.todos | ||
/* | ||
const getVisibleTodosFilteredByKeyword = createSelector( | ||
[ getVisibleTodos, getKeyword ], | ||
(visibleTodos, keyword) => visibleTodos.filter( | ||
todo => todo.text.includes(keyword) | ||
) | ||
) | ||
*/ | ||
const filteredVisibleTodosSelector = [ | ||
[ getVisibleTodos, getKeyword ], | ||
(visibleTodos, keyword) => visibleTodos.filter( | ||
todo => todo.text.includes(keyword) | ||
) | ||
] | ||
``` | ||
A very common selector shown in the example above. Instead of calling `createSelector` to create a selector function, you construct an array structure to describe a selector. | ||
Let's take a closer look on how it works. | ||
The selectors consisted of two parts. | ||
The first element is an array of functions, each of which takes the store as input. The second element is a plain function, it takes the value of selectors in the first element as input. So the first argument it takes will be the result of `getVisibleTodos` and the second will be `getKeyword`. The Values of each element are cached so if the values of one element are the same as previous the following element will not get called and return the previously computed value. In this example, the second element function will not run until the result of `getVisibleTodos` or `getKeyword` changed. | ||
### Nested Selector | ||
On the previous example, all input selectors are functions. | ||
But they don't have to be. | ||
```JavaScript | ||
const todoAuthorSelector = [ | ||
[filteredVisibleTodosSelector], | ||
todos => todos.map(todo => todo.author) | ||
] | ||
``` | ||
A valid selector description can be a function, an array or a plain object(we'll see it later). | ||
### Multiple Pipeline Selector | ||
Let's implement the `todoAuthorSelector` without nested selectors this time. | ||
```JavaScript | ||
const todoAuthorSelector = [ | ||
[ getVisibleTodos, getKeyword ], | ||
(visibleTodos, keyword) => visibleTodos.filter( | ||
todo => todo.text.includes(keyword) | ||
), | ||
todos => todos.map(todo => todo.author) | ||
] | ||
``` | ||
You can have even more element in a selector. | ||
Like what we have known, each element is memorized and each element takes the previous as input. | ||
You should also notice the second element have not been wrapped in `[]`. It's fine, `[]` is optional when there is only one selector that is **not an array**. | ||
### Structured Selector | ||
When working on a react/redux project, it's a common pattern that selecting data from the redux store and passing it as props to a component. A selector might look like this: | ||
```JavaScript | ||
const usernameSelector = state => state.user.name | ||
const postsSelector = state => state.posts | ||
connect(createSelector([ | ||
[usernameSelector, postsSelector], | ||
(username, posts) => ({ | ||
username, | ||
posts, | ||
}) | ||
])) | ||
``` | ||
It is when structured selectors come into play. | ||
Structured selectors are objects whose properties are a selector. A structured selector equivalent to above can be: | ||
```JavaScript | ||
connect(createSelector({ | ||
username: usernameSelector, | ||
posts: postsSelector, | ||
})) | ||
``` | ||
## API | ||
### createSelector: selector => memoizedFunction | ||
Takes one selector, return a memoized function. | ||
A selector can be a function, an array or a plain object. It determines if the value has changed using reference equality(`===`). | ||
### createMemoizor: equalityCheck => function => memoizedFunction | ||
Passing in an `equalityCheck` function, return a function that transforms a function to the memoized version. | ||
### createSelectorCreator: memoize => createSelector | ||
`createSelectorCreator` takes a `memoize` function as input and returns a customized version of `createSelector`. | ||
Here is an example for using [`Immutable.is`](https://facebook.github.io/immutable-js/docs/#/is) as `equalityCheck` | ||
```JavaScript | ||
import { is } from 'immutable' | ||
import { createSelectorCreator, createMemoizor } from 're-select' | ||
const createImmutableSelector = createSelectorCreator(createMemoizor(is)) | ||
``` | ||
## License | ||
MIT |
104
src/index.js
@@ -0,10 +1,18 @@ | ||
const isObject = a => typeof a == 'object' && a !== null; | ||
const isArray = a => Array.isArray(a); | ||
const isFunction = a => typeof a === 'function'; | ||
const identity = a => a; | ||
const defaultEqualityCheck = (a, b) => a === b; | ||
function areArgumentsShallowlyEqual(equalityCheck, prev, next) { | ||
if (prev === null || next === null || prev.length !== next.length) { | ||
function areArgumentsEqual(equalityCheck, a, b) { | ||
if (a === null || b === null || a.length !== b.length) { | ||
return false; | ||
} | ||
for (let i = prev.length - 1; i >= 0; --i) { | ||
if (!equalityCheck(prev[i], next[i])) { | ||
for (let i = a.length - 1; i >= 0; --i) { | ||
if (!equalityCheck(a[i], b[i])) { | ||
return false; | ||
@@ -14,13 +22,13 @@ } | ||
return true | ||
return true; | ||
} | ||
export function defaultMemoize(fn, equalityCheck = defaultEqualityCheck) { | ||
export const createMemoizor = (equalityCheck = defaultEqualityCheck) => fn => { | ||
let lastArgs = null; | ||
let lastResult = null; | ||
return function() { | ||
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { | ||
if (!areArgumentsEqual(equalityCheck, lastArgs, arguments)) { | ||
lastResult = fn.apply(null, arguments); | ||
} | ||
lastArgs = arguments | ||
lastArgs = arguments; | ||
return lastResult; | ||
@@ -38,50 +46,56 @@ } | ||
function createNestedSelectors(create, fns) { | ||
if (Array.isArray(fns)) { | ||
return fns.map(fn => Array.isArray(fn) ? create(fn) : fn) | ||
} | ||
return fns; | ||
function createNestedSelectors(selectors, next) { | ||
return selectors.map(fn => isFunction(fn) ? fn : next(fn)) | ||
} | ||
export const createSelectorCreator = (memoize, memoizeOptions) => { | ||
function createSelector(fns) { | ||
const initialRecomputations = Array(fns.length).fill(0); | ||
let recomputations = initialRecomputations; | ||
function normalizeSelector(selector) { | ||
if (isArray(selector)) { | ||
return selector.map( | ||
nestedSelector => ( | ||
isArray(nestedSelector) ? nestedSelector : [nestedSelector] | ||
).map(normalizeSelector) | ||
) | ||
} | ||
const selector = fns.reduceRight(function(next, currentFns, index) { | ||
currentFns = createNestedSelectors(createSelector, currentFns); | ||
if (isObject(selector)) { | ||
const objectKeys = Object.keys(selector); | ||
return [ | ||
objectKeys.map(key => normalizeSelector(selector[key])), | ||
[(...values) => values.reduce( | ||
(composition, value, index) => Object.assign(composition, { | ||
[objectKeys[index]]: value | ||
}), | ||
{} | ||
)] | ||
] | ||
} | ||
const selectors = next | ||
? makeDependenciesFn(currentFns, next) | ||
: currentFns; | ||
return memoize(function() { | ||
recomputations[index] += 1; | ||
return selectors.apply(null, arguments); | ||
}, memoizeOptions); | ||
}, null) | ||
selector.recomputations = () => recomputations; | ||
selector.resetRecomputations = () => recomputations = initialRecomputations; | ||
if (isFunction(selector)) { | ||
return selector; | ||
} | ||
return createSelector; | ||
throw new Error( | ||
`Invalid value of type ${typeof selector} for creating a selector` | ||
); | ||
} | ||
export const createSelectorsCreator = createSelector => selectors => { | ||
if (Array.isArray(selectors)) { | ||
return createSelector(selectors); | ||
export const createSelectorCreator = memoize => { | ||
function createSelector(selector) { | ||
const selectorNormalized = normalizeSelector( | ||
isFunction(selector) ? [selector, identity] : selector | ||
) | ||
return selectorNormalized.reduceRight(function(next, cur, index) { | ||
const dependenciesFn = makeDependenciesFn( | ||
createNestedSelectors(cur, createSelector), | ||
next | ||
); | ||
return memoize(dependenciesFn); | ||
}, identity); | ||
} | ||
const keys = Object.keys(selectors); | ||
const memoizedSelectors = {}; | ||
for (let i = keys.length - 1; i >= 0; --i) { | ||
const key = keys[i]; | ||
memoizedSelectors[key] = createSelector(selectors[key]) | ||
} | ||
return memoizedSelectors; | ||
return createSelector; | ||
} | ||
export const createSelector = createSelectorCreator(defaultMemoize); | ||
export const createSelectors = createSelectorsCreator(createSelector); | ||
export const createSelector = createSelectorCreator(createMemoizor()); | ||
export default createSelector; |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
70524
17
592
171
11
1