index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. function defaultEqualityCheck(a, b) {
  2. return a === b
  3. }
  4. function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
  5. if (prev === null || next === null || prev.length !== next.length) {
  6. return false
  7. }
  8. // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
  9. const length = prev.length
  10. for (let i = 0; i < length; i++) {
  11. if (!equalityCheck(prev[i], next[i])) {
  12. return false
  13. }
  14. }
  15. return true
  16. }
  17. export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  18. let lastArgs = null
  19. let lastResult = null
  20. // we reference arguments instead of spreading them for performance reasons
  21. return function () {
  22. if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
  23. // apply arguments instead of spreading for performance.
  24. lastResult = func.apply(null, arguments)
  25. }
  26. lastArgs = arguments
  27. return lastResult
  28. }
  29. }
  30. function getDependencies(funcs) {
  31. const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
  32. if (!dependencies.every(dep => typeof dep === 'function')) {
  33. const dependencyTypes = dependencies.map(
  34. dep => typeof dep
  35. ).join(', ')
  36. throw new Error(
  37. 'Selector creators expect all input-selectors to be functions, ' +
  38. `instead received the following types: [${dependencyTypes}]`
  39. )
  40. }
  41. return dependencies
  42. }
  43. export function createSelectorCreator(memoize, ...memoizeOptions) {
  44. return (...funcs) => {
  45. let recomputations = 0
  46. const resultFunc = funcs.pop()
  47. const dependencies = getDependencies(funcs)
  48. const memoizedResultFunc = memoize(
  49. function () {
  50. recomputations++
  51. // apply arguments instead of spreading for performance.
  52. return resultFunc.apply(null, arguments)
  53. },
  54. ...memoizeOptions
  55. )
  56. // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
  57. const selector = defaultMemoize(function () {
  58. const params = []
  59. const length = dependencies.length
  60. for (let i = 0; i < length; i++) {
  61. // apply arguments instead of spreading and mutate a local list of params for performance.
  62. params.push(dependencies[i].apply(null, arguments))
  63. }
  64. // apply arguments instead of spreading for performance.
  65. return memoizedResultFunc.apply(null, params)
  66. })
  67. selector.resultFunc = resultFunc
  68. selector.recomputations = () => recomputations
  69. selector.resetRecomputations = () => recomputations = 0
  70. return selector
  71. }
  72. }
  73. export const createSelector = createSelectorCreator(defaultMemoize)
  74. export function createStructuredSelector(selectors, selectorCreator = createSelector) {
  75. if (typeof selectors !== 'object') {
  76. throw new Error(
  77. 'createStructuredSelector expects first argument to be an object ' +
  78. `where each property is a selector, instead received a ${typeof selectors}`
  79. )
  80. }
  81. const objectKeys = Object.keys(selectors)
  82. return selectorCreator(
  83. objectKeys.map(key => selectors[key]),
  84. (...values) => {
  85. return values.reduce((composition, value, index) => {
  86. composition[objectKeys[index]] = value
  87. return composition
  88. }, {})
  89. }
  90. )
  91. }