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

Add PreloadedState generic #4491

Merged
merged 1 commit into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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