Skip to content

Commit

Permalink
Add PreloadedState generic
Browse files Browse the repository at this point in the history
  • Loading branch information
Methuselah96 committed Mar 26, 2023
1 parent f503238 commit ac804a5
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 176 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
}
}
}
34 changes: 17 additions & 17 deletions src/combineReducers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { AnyAction, Action } from './types/actions'
import {
ActionFromReducersMapObject,
PreloadedStateShapeFromReducersMapObject,
Reducer,
ReducersMapObject,
StateFromReducersMapObject
} from './types/reducers'
import { CombinedState } from './types/store'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
Expand All @@ -14,7 +13,7 @@ import { kindOf } from './utils/kindOf'

function getUnexpectedStateShapeWarningMessage(
inputState: object,
reducers: ReducersMapObject,
reducers: { [key: string]: Reducer<any, any, any> },
action: Action,
unexpectedKeyCache: { [key: string]: true }
) {
Expand Down Expand Up @@ -60,7 +59,9 @@ function getUnexpectedStateShapeWarningMessage(
}
}

function assertReducerShape(reducers: ReducersMapObject) {
function assertReducerShape(reducers: {
[key: string]: Reducer<any, any, any>
}) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
Expand Down Expand Up @@ -110,21 +111,20 @@ function assertReducerShape(reducers: ReducersMapObject) {
* @returns A reducer function that invokes every reducer inside the passed
* 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>
export default function combineReducers<M extends ReducersMapObject>(
export default function combineReducers<M>(
reducers: M
): Reducer<
CombinedState<StateFromReducersMapObject<M>>,
ActionFromReducersMapObject<M>
>
export default function combineReducers(reducers: ReducersMapObject) {
): M[keyof M] extends Reducer<any, any, any> | undefined
? Reducer<
StateFromReducersMapObject<M>,
ActionFromReducersMapObject<M>,
Partial<PreloadedStateShapeFromReducersMapObject<M>>
>
: never
export default function combineReducers(reducers: {
[key: string]: Reducer<any, any, any>
}) {
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
const finalReducers: { [key: string]: Reducer<any, any, any> } = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]

Expand Down
37 changes: 21 additions & 16 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 @@ -77,20 +76,22 @@ export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
StateExt extends {} = {},
PreloadedState = S
>(
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,
Ext extends {} = {},
StateExt extends {} = {}
StateExt extends {} = {},
PreloadedState = S
>(
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 @@ -456,20 +459,22 @@ export function legacy_createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
StateExt extends {} = {},
PreloadedState = S
>(
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,
Ext extends {} = {},
StateExt extends {} = {}
StateExt extends {} = {},
PreloadedState = S
>(
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
7 changes: 3 additions & 4 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,11 +21,12 @@ export {
// reducers
export {
Reducer,
ReducerFromReducersMapObject,
ReducersMapObject,
StateFromReducersMapObject,
ReducerFromReducersMapObject,
ActionFromReducer,
ActionFromReducersMapObject
ActionFromReducersMapObject,
PreloadedStateShapeFromReducersMapObject
} from './types/reducers'
// action creators
export { ActionCreator, ActionCreatorsMapObject } from './types/actions'
Expand Down
74 changes: 56 additions & 18 deletions src/types/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,55 +25,93 @@ 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
> = keyof PreloadedState extends keyof S
? {
[K in keyof S]: Reducer<
S[K],
A,
K extends keyof PreloadedState ? PreloadedState[K] : never
>
}
: 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 }
export type StateFromReducersMapObject<M> = M[keyof M] extends
| Reducer<any, any, any>
| undefined
? {
[P in keyof M]: M[P] extends Reducer<infer S> ? S : 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> = M extends {
[P in keyof M]: infer R
}
? R extends Reducer<any, any>
? R
: never
export type ReducerFromReducersMapObject<M> = M[keyof M] extends
| Reducer<any, any, any>
| undefined
? M[keyof M]
: never

/**
* 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> = R extends
| Reducer<any, infer A, any>
| undefined
? 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>>
export type ActionFromReducersMapObject<M> = ActionFromReducer<
ReducerFromReducersMapObject<M>
>

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

0 comments on commit ac804a5

Please sign in to comment.