Skip to content

Commit

Permalink
Merge pull request #514 from reduxjs/feature/additional-4.1-tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Oct 20, 2021
2 parents 581c084 + c9a5207 commit 099b2b9
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 23 deletions.
24 changes: 20 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ function getDependencies(funcs: unknown[]) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs

if (!dependencies.every(dep => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(dep => typeof dep).join(', ')
const dependencyTypes = dependencies
.map(dep =>
typeof dep === 'function'
? `function ${dep.name || 'unnamed'}()`
: typeof dep
)
.join(', ')

throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`
`createSelector expects all input-selectors to be functions, but received the following types: [${dependencyTypes}]`
)
}

Expand All @@ -56,6 +62,7 @@ export function createSelectorCreator<
// (memoize: MemoizeFunction, ...memoizeOptions: MemoizerOptions) {
const createSelector = (...funcs: Function[]) => {
let recomputations = 0
let lastResult: unknown

// Due to the intricacies of rest params, we can't do an optional arg after `...funcs`.
// So, start by declaring the default value here.
Expand All @@ -74,6 +81,12 @@ export function createSelectorCreator<
resultFunc = funcs.pop()
}

if (typeof resultFunc !== 'function') {
throw new Error(
`createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
)
}

// Determine which set of options we're using. Prefer options passed directly,
// but fall back to options given to createSelectorCreator.
const { memoizeOptions = memoizeOptionsFromArgs } = directlyPassedOptions
Expand Down Expand Up @@ -109,11 +122,14 @@ export function createSelectorCreator<
}

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

selector.resultFunc = resultFunc
selector.memoizedResultFunc = memoizedResultFunc
selector.dependencies = dependencies
selector.lastResult = () => lastResult
selector.recomputations = () => recomputations
selector.resetRecomputations = () => (recomputations = 0)
return selector
Expand Down
36 changes: 20 additions & 16 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@ export type Selector<
R = unknown,
P extends never | readonly any[] = any[]
> = [P] extends [never] ? (state: S) => R : (state: S, ...params: P) => R
export type OutputSelector<
S extends SelectorArray,
Result,
Params extends readonly any[],
Combiner
> = Selector<GetStateFromSelectors<S>, Result, Params> & {

interface OutputSelectorFields<Combiner, Result> {
resultFunc: Combiner
memoizedResultFunc: Combiner
lastResult: () => Result
dependencies: SelectorArray
recomputations: () => number
resetRecomputations: () => number
}

export type ParametricSelector<S, P, R> = Selector<S, R, [P, ...any]>
export type OutputSelector<
S extends SelectorArray,
Result,
Params extends readonly any[],
Combiner
> = Selector<GetStateFromSelectors<S>, Result, Params> &
OutputSelectorFields<Combiner, Result>

export type ParametricSelector<State, Props, Result> = Selector<
State,
Result,
[Props, ...any]
>

export type OutputParametricSelector<S, P, R, C> = ParametricSelector<
S,
P,
R
> & {
resultFunc: C
recomputations: () => number
resetRecomputations: () => number
}
export type OutputParametricSelector<State, Props, Result, Combiner> =
ParametricSelector<State, Props, Result> &
OutputSelectorFields<Combiner, Result>

export type SelectorArray = ReadonlyArray<Selector>

Expand Down
32 changes: 29 additions & 3 deletions test/test_selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,25 @@ describe('Basic selector behavior', () => {

test('basic selector invalid input selector', () => {
expect(() =>
// @ts-ignore
createSelector(
// @ts-ignore
(state: StateAB) => state.a,
function input2(state: StateAB) {
return state.b
},
'not a function',
(a, b) => a + b
)
).toThrow(/input-selectors to be functions.*function, string/)
).toThrow(
'createSelector expects all input-selectors to be functions, but received the following types: [function unnamed(), function input2(), string]'
)

expect(() =>
// @ts-ignore
createSelector((state: StateAB) => state.a, 'not a function')
).toThrow(
'createSelector expects an output function after the inputs, but received: [string]'
)
})

test('basic selector cache hit performance', () => {
Expand Down Expand Up @@ -655,7 +667,9 @@ describe('createStructureSelector', () => {
// @ts-expect-error
c: 'd'
})
).toThrow(/input-selectors to be functions.*function, string/)
).toThrow(
'createSelector expects all input-selectors to be functions, but received the following types: [function a(), string]'
)
})

test('structured selector with custom selector creator', () => {
Expand Down Expand Up @@ -716,4 +730,16 @@ describe('createSelector exposed utils', () => {
const selector = createSelector(dependency1, dependency2, () => {})
expect(selector.dependencies).toEqual([dependency1, dependency2])
})

test('export lastResult function', () => {
const selector = createSelector(
(state: StateAB) => state.a,
(state: StateAB) => state.b,
(a, b) => a + b
)

const result = selector({ a: 1, b: 2 })
expect(result).toBe(3)
expect(selector.lastResult()).toBe(3)
})
})

0 comments on commit 099b2b9

Please sign in to comment.