Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port latest reselect #37

Open
alex-dixon opened this issue Sep 23, 2019 · 4 comments
Open

Port latest reselect #37

alex-dixon opened this issue Sep 23, 2019 · 4 comments
Assignees
Labels

Comments

@alex-dixon
Copy link
Contributor

@mikegai Let me know the best place to get this from

@mikegai
Copy link
Member

mikegai commented Sep 23, 2019

latest remote-dm master

@mikegai
Copy link
Member

mikegai commented Sep 23, 2019

import { lastInputOutputMemoize } from './reselect-raw'

function defaultEqualityCheck(a, b) {
  return a === b
}

function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
  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.
  const length = prev.length
  for (let i = 0; i < length; i++) {
    if (!equalityCheck(prev[i], next[i])) {
      return false
    }
  }

  return true
}

export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null
  let lastResult = null
  // we reference arguments instead of spreading them for performance reasons
  return function() {
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      // apply arguments instead of spreading for performance.
      lastResult = func.apply(null, arguments)
    }

    lastArgs = arguments
    return lastResult
  }
}

function getDependencies(funcs) {
  const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs

  if (!dependencies.every(dep => typeof dep === 'function')) {
    const dependencyTypes = dependencies.map(dep => typeof dep).join(', ')
    throw new Error(
      'Selector creators expect all input-selectors to be functions, ' +
        `instead received the following types: [${dependencyTypes}]`
    )
  }

  return dependencies
}

export function makeSelectorCreator(memoize, outerMemoize, ...memoizeOptions) {
  return (...funcs) => {
    let recomputations = 0
    const resultFunc = funcs.pop()
    const dependencies = getDependencies(funcs)

    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
    const memoizedResultFunc = memoize(function() {
      recomputations++
      // apply arguments instead of spreading for performance.
      return resultFunc.apply(null, arguments)
    }, ...memoizeOptions)

    const memoizeDb = outerMemoize || memoize
    // this is the selector itself -- we don't pass in anything but a DB bag to our selectors...
    const selector = memoizeDb(function() {
      const params = []
      const length = dependencies.length

      for (let i = 0; i < length; i++) {
        // apply arguments instead of spreading and mutate a local list of params for performance.
        params.push(dependencies[i].apply(null, arguments))
      }

      // apply arguments instead of spreading for performance.
      return memoizedResultFunc.apply(null, params)
    })

    selector.resultFunc = resultFunc
    selector.dependencies = dependencies
    selector.recomputations = () => recomputations
    selector.resetRecomputations = () => (recomputations = 0)
    return selector
  }
}

export const createSelector = makeSelectorCreator(defaultMemoize)

export function createStructuredSelector(
  selectors,
  selectorCreator = createSelector
) {
  if (typeof selectors !== 'object') {
    throw new Error(
      'createStructuredSelector expects first argument to be an object ' +
        `where each property is a selector, instead received a ${typeof selectors}`
    )
  }
  const objectKeys = Object.keys(selectors)
  return selectorCreator(objectKeys.map(key => selectors[key]), (...values) => {
    return values.reduce((composition, value, index) => {
      composition[objectKeys[index]] = value
      return composition
    }, {})
  })
}

/**
 * Stateful selectors with an "accumulator bag"
 */

const defaultIs = (a, b) =>
  areArgumentsShallowlyEqual(defaultEqualityCheck, a, b)
/*
 * experiment # 2 in fancy diff selectors
 * return in the form [acc, item]
 * */
export function accumulatorMemoize(func) {
  let lastArgs = null
  let lastResult = null
  let acc = {}
  return function() {
    if (!defaultIs(lastArgs, arguments)) {
      const fullArgs = [acc].concat(Array.from(arguments))
      const ret = func.apply(null, fullArgs)
      if (!ret)
        throw new Error(
          'Stateful selectors must return a stream accumulator as first argument'
        )
      acc = ret[0]
      lastResult = ret[1]
    }
    lastArgs = arguments

    return lastResult
  }
}
export const createStatefulSelector = makeSelectorCreator(
  accumulatorMemoize,
  defaultMemoize,
  { diff: true }
)

@mikegai
Copy link
Member

mikegai commented Sep 23, 2019

exact same as latest reselect, except makeSelectorCreator takes two different memoization functions instead of one

@alex-dixon
Copy link
Contributor Author

Removing as release blocker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants