Skip to content

Commit

Permalink
Add PreloadedState generic
Browse files Browse the repository at this point in the history
  • Loading branch information
Methuselah96 committed Feb 22, 2023
1 parent 44e4798 commit a6d4308
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 148 deletions.
44 changes: 19 additions & 25 deletions src/applyMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, Dispatch, PreloadedState } from './types/store'
import { Reducer } from './types/reducers'
import { StoreEnhancer, Dispatch } from './types/store'

/**
* Creates a store enhancer that applies middleware to the dispatch method
Expand Down Expand Up @@ -55,29 +53,25 @@ export default function applyMiddleware<Ext, S = any>(
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return createStore =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
return createStore => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}

const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

return {
...store,
dispatch
}
return {
...store,
dispatch
}
}
}
42 changes: 29 additions & 13 deletions src/combineReducers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AnyAction, Action } from './types/actions'
import {
ActionFromReducersMapObject,
PreloadedStateFromReducersMapObject,
Reducer,
ReducersMapObject,
StateFromReducersMapObject
} from './types/reducers'
import { CombinedState } from './types/store'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
Expand Down Expand Up @@ -111,18 +111,32 @@ function assertReducerShape(reducers: ReducersMapObject) {
* object, and builds a state object with the same shape.
*/
export default function combineReducers<S>(
reducers: ReducersMapObject<S, any>
): Reducer<CombinedState<S>>
export default function combineReducers<S, A extends Action = AnyAction>(
reducers: ReducersMapObject<S, A>
): Reducer<CombinedState<S>, A>
reducers: ReducersMapObject<S, any, any>
): Reducer<S, AnyAction, Partial<S>>
export default function combineReducers<S, PreloadedState = S>(
reducers: ReducersMapObject<S, any, PreloadedState>
): Reducer<S, AnyAction, Partial<PreloadedState>>
export default function combineReducers<
S,
A extends Action = AnyAction,
PreloadedState = S
>(
reducers: ReducersMapObject<S, A, PreloadedState>
): Reducer<S, A, Partial<PreloadedState>>
export default function combineReducers<M extends ReducersMapObject>(
reducers: M
): Reducer<
CombinedState<StateFromReducersMapObject<M>>,
ActionFromReducersMapObject<M>
StateFromReducersMapObject<M>,
ActionFromReducersMapObject<M>,
Partial<PreloadedStateFromReducersMapObject<M>>
>
export default function combineReducers(reducers: ReducersMapObject) {
export default function combineReducers<M extends ReducersMapObject>(
reducers: M
): Reducer<
StateFromReducersMapObject<M>,
ActionFromReducersMapObject<M>,
Partial<PreloadedStateFromReducersMapObject<M>>
> {
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
Expand Down Expand Up @@ -155,7 +169,9 @@ export default function combineReducers(reducers: ReducersMapObject) {
}

return function combination(
state: StateFromReducersMapObject<typeof reducers> = {},
state:
| StateFromReducersMapObject<typeof reducers>
| Partial<PreloadedStateFromReducersMapObject<typeof reducers>> = {},
action: AnyAction
) {
if (shapeAssertionError) {
Expand All @@ -175,7 +191,7 @@ export default function combineReducers(reducers: ReducersMapObject) {
}

let hasChanged = false
const nextState: StateFromReducersMapObject<typeof reducers> = {}
const nextState: Partial<StateFromReducersMapObject<typeof reducers>> = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
Expand All @@ -191,11 +207,11 @@ export default function combineReducers(reducers: ReducersMapObject) {
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
nextState[key] = nextStateForKey
nextState[key as keyof typeof nextState] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
return (hasChanged ? nextState : state) as StateFromReducersMapObject<M>
}
}
29 changes: 17 additions & 12 deletions src/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import $$observable from './utils/symbol-observable'

import {
Store,
PreloadedState,
StoreEnhancer,
Dispatch,
Observer,
Expand Down Expand Up @@ -76,21 +75,23 @@ export function createStore<
export function createStore<
S,
A extends Action,
PreloadedState,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
reducer: Reducer<S, A, PreloadedState>,
preloadedState?: PreloadedState | undefined,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext
export function createStore<
S,
A extends Action,
PreloadedState,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
reducer: Reducer<S, A, PreloadedState>,
preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext {
if (typeof reducer !== 'function') {
Expand Down Expand Up @@ -128,12 +129,14 @@ export function createStore<

return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<S, A, StateExt> & Ext
preloadedState as PreloadedState | undefined
)
}

let currentReducer = reducer
let currentState = preloadedState as S
let currentState: S | PreloadedState | undefined = preloadedState as
| PreloadedState
| undefined
let currentListeners: Map<number, ListenerCallback> | null = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
Expand Down Expand Up @@ -315,7 +318,7 @@ export function createStore<
)
}

currentReducer = nextReducer
currentReducer = nextReducer as unknown as Reducer<S, A, PreloadedState>

// This action has a similar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
Expand Down Expand Up @@ -455,21 +458,23 @@ export function legacy_createStore<
export function legacy_createStore<
S,
A extends Action,
PreloadedState,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
reducer: Reducer<S, A, PreloadedState>,
preloadedState?: PreloadedState | undefined,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext
export function legacy_createStore<
S,
A extends Action,
PreloadedState,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext {
return createStore(reducer, preloadedState as any, enhancer)
Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
// types
// store
export {
CombinedState,
PreloadedState,
Dispatch,
Unsubscribe,
Observable,
Expand All @@ -23,9 +21,10 @@ export {
// reducers
export {
Reducer,
ReducerFromReducersMapObject,
ReducersMapObject,
StateFromReducersMapObject,
PreloadedStateFromReducersMapObject,
ReducerFromReducersMapObject,
ActionFromReducer,
ActionFromReducersMapObject
} from './types/reducers'
Expand Down
73 changes: 53 additions & 20 deletions src/types/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,55 +25,88 @@ import { Action, AnyAction } from './actions'
*
* @template S The type of state consumed and produced by this reducer.
* @template A The type of actions the reducer can potentially respond to.
* @template PreloadedState The type of state consumed by this reducer the first time it's called.
*/
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
export type Reducer<
S = any,
A extends Action = AnyAction,
PreloadedState = S
> = (state: S | PreloadedState | undefined, action: A) => S

/**
* Object whose values correspond to different reducer functions.
*
* @template S The combined state of the reducers.
* @template A The type of actions the reducers can potentially respond to.
* @template PreloadedState The combined preloaded state of the reducers.
*/
export type ReducersMapObject<S = any, A extends Action = AnyAction> = {
[K in keyof S]: Reducer<S[K], A>
export type ReducersMapObject<
S = any,
A extends Action = AnyAction,
PreloadedState = S
> = {
[K in keyof S]: Reducer<
S[K],
A,
K extends keyof PreloadedState ? PreloadedState[K] : never
>
}

/**
* Infer a combined state shape from a `ReducersMapObject`.
*
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
*/
export type StateFromReducersMapObject<M> = M extends ReducersMapObject
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
: never
export type StateFromReducersMapObject<
M extends ReducersMapObject<any, any, any>
> = {
[P in keyof M]: M[P] extends Reducer<infer S, any, any> | undefined
? S
: never
}

/**
* Infer reducer union type from a `ReducersMapObject`.
* Infer a combined preloaded state shape from a `ReducersMapObject`.
*
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
*/
export type ReducerFromReducersMapObject<M> = M extends {
[P in keyof M]: infer R
}
? R extends Reducer<any, any>
? R
export type PreloadedStateFromReducersMapObject<
M extends ReducersMapObject<any, any, any>
> = {
[P in keyof M]: M[P] extends
| Reducer<any, any, infer PreloadedState>
| undefined
? PreloadedState
: never
: never
}

/**
* Infer reducer union type from a `ReducersMapObject`.
*
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
*/
export type ReducerFromReducersMapObject<
M extends ReducersMapObject<any, any, any>
> = M[keyof M]

/**
* Infer action type from a reducer function.
*
* @template R Type of reducer.
*/
export type ActionFromReducer<R> = R extends Reducer<any, infer A> ? A : never
export type ActionFromReducer<R extends Reducer<any, any>> = R extends Reducer<
any,
infer A,
any
>
? A
: never

/**
* Infer action union type from a `ReducersMapObject`.
*
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
*/
export type ActionFromReducersMapObject<M> = M extends ReducersMapObject
? ActionFromReducer<ReducerFromReducersMapObject<M>>
: never
export type ActionFromReducersMapObject<
M extends ReducersMapObject<any, any, any>
> = ActionFromReducer<ReducerFromReducersMapObject<M>>
Loading

0 comments on commit a6d4308

Please sign in to comment.