diff --git a/src/applyMiddleware.ts b/src/applyMiddleware.ts index af68589ab0d..744ed27895c 100644 --- a/src/applyMiddleware.ts +++ b/src/applyMiddleware.ts @@ -1,12 +1,7 @@ import compose from './compose' import { Middleware, MiddlewareAPI } from './types/middleware' import { AnyAction } from './types/actions' -import { - StoreEnhancer, - Dispatch, - PreloadedState, - StoreEnhancerStoreCreator -} from './types/store' +import { StoreEnhancer, Dispatch, PreloadedState } from './types/store' import { Reducer } from './types/reducers' /** @@ -60,7 +55,7 @@ export default function applyMiddleware( export default function applyMiddleware( ...middlewares: Middleware[] ): StoreEnhancer { - return (createStore: StoreEnhancerStoreCreator) => + return createStore => ( reducer: Reducer, preloadedState?: PreloadedState diff --git a/src/createStore.ts b/src/createStore.ts index 9e13aa22917..f704e8a61a7 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -39,7 +39,7 @@ import { kindOf } from './utils/kindOf' * `import { legacy_createStore as createStore} from 'redux'` * */ -export function createStore( +export function createStore( reducer: Reducer, enhancer?: StoreEnhancer ): Store & Ext @@ -68,12 +68,12 @@ export function createStore( * `import { legacy_createStore as createStore} from 'redux'` * */ -export function createStore( +export function createStore( reducer: Reducer, preloadedState?: PreloadedState, enhancer?: StoreEnhancer ): Store & Ext -export function createStore( +export function createStore( reducer: Reducer, preloadedState?: PreloadedState | StoreEnhancer, enhancer?: StoreEnhancer @@ -402,7 +402,7 @@ export function legacy_createStore< S, A extends Action, Ext = {}, - StateExt = never + StateExt = {} >( reducer: Reducer, enhancer?: StoreEnhancer @@ -441,7 +441,7 @@ export function legacy_createStore< S, A extends Action, Ext = {}, - StateExt = never + StateExt = {} >( reducer: Reducer, preloadedState?: PreloadedState, @@ -451,7 +451,7 @@ export function legacy_createStore< S, A extends Action, Ext = {}, - StateExt = never + StateExt = {} >( reducer: Reducer, preloadedState?: PreloadedState | StoreEnhancer, diff --git a/src/index.ts b/src/index.ts index 926be4d143e..0fa083343b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,8 +18,7 @@ export { Store, StoreCreator, StoreEnhancer, - StoreEnhancerStoreCreator, - ExtendState + StoreEnhancerStoreCreator } from './types/store' // reducers export { diff --git a/src/types/store.ts b/src/types/store.ts index fb5b1a1d5f9..9837a287190 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -2,20 +2,6 @@ import { Action, AnyAction } from './actions' import { Reducer } from './reducers' import '../utils/symbol-observable' -/** - * Extend the state - * - * This is used by store enhancers and store creators to extend state. - * If there is no state extension, it just returns the state, as is, otherwise - * it returns the state joined with its extension. - * - * Reference for future devs: - * https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919 - */ -export type ExtendState = [Extension] extends [never] - ? State - : State & Extension - /** * Internal "virtual" symbol used to make the `CombinedState` type unique. */ @@ -134,11 +120,7 @@ export type Observer = { * @template A the type of actions which may be dispatched by this store. * @template StateExt any extension to state from store enhancers */ -export interface Store< - S = any, - A extends Action = AnyAction, - StateExt = never -> { +export interface Store { /** * Dispatches an action. It is the only way to trigger a state change. * @@ -172,7 +154,7 @@ export interface Store< * * @returns The current state tree of your application. */ - getState(): ExtendState + getState(): S & StateExt /** * Adds a change listener. It will be called any time an action is @@ -217,7 +199,7 @@ export interface Store< * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */ - [Symbol.observable](): Observable> + [Symbol.observable](): Observable } /** @@ -232,11 +214,11 @@ export interface Store< * @template StateExt State extension that is mixed into the state type. */ export interface StoreCreator { - ( + ( reducer: Reducer, enhancer?: StoreEnhancer ): Store & Ext - ( + ( reducer: Reducer, preloadedState?: PreloadedState, enhancer?: StoreEnhancer @@ -264,10 +246,10 @@ export interface StoreCreator { * @template Ext Store extension that is mixed into the Store type. * @template StateExt State extension that is mixed into the state type. */ -export type StoreEnhancer = ( - next: StoreEnhancerStoreCreator -) => StoreEnhancerStoreCreator -export type StoreEnhancerStoreCreator = < +export type StoreEnhancer = ( + next: StoreEnhancerStoreCreator +) => StoreEnhancerStoreCreator +export type StoreEnhancerStoreCreator = < S = any, A extends Action = AnyAction >( diff --git a/test/typescript/enhancers.ts b/test/typescript/enhancers.ts index 16cb9b3f3c5..d9439baf71e 100644 --- a/test/typescript/enhancers.ts +++ b/test/typescript/enhancers.ts @@ -64,11 +64,13 @@ function stateExtension() { reducer: Reducer, preloadedState?: any ) => { - const wrappedReducer: Reducer = (state, action) => { - const newState = reducer(state, action) - return { - ...newState, - extraField: 'extra' + function wrapReducer(reducer: Reducer): Reducer { + return (state, action) => { + const newState = reducer(state, action) + return { + ...newState, + extraField: 'extra' + } } } const wrappedPreloadedState = preloadedState @@ -77,7 +79,13 @@ function stateExtension() { extraField: 'extra' } : undefined - return createStore(wrappedReducer, wrappedPreloadedState) + const store = createStore(wrapReducer(reducer), wrappedPreloadedState) + return { + ...store, + replaceReducer(nextReducer: Reducer) { + store.replaceReducer(wrapReducer(nextReducer)) + } + } } const store = createStore(reducer, enhancer) @@ -96,8 +104,10 @@ function extraMethods() { createStore => (...args) => { const store = createStore(...args) - store.method = () => 'foo' - return store + return { + ...store, + method: () => 'foo' + } } const store = createStore(reducer, enhancer) @@ -122,11 +132,13 @@ function replaceReducerExtender() { reducer: Reducer, preloadedState?: any ) => { - const wrappedReducer: Reducer = (state, action) => { - const newState = reducer(state, action) - return { - ...newState, - extraField: 'extra' + function wrapReducer(reducer: Reducer): Reducer { + return (state, action) => { + const newState = reducer(state, action) + return { + ...newState, + extraField: 'extra' + } } } const wrappedPreloadedState = preloadedState @@ -135,7 +147,14 @@ function replaceReducerExtender() { extraField: 'extra' } : undefined - return createStore(wrappedReducer, wrappedPreloadedState) + const store = createStore(wrapReducer(reducer), wrappedPreloadedState) + return { + ...store, + replaceReducer(nextReducer: Reducer) { + store.replaceReducer(wrapReducer(nextReducer)) + }, + method: () => 'foo' + } } const store = createStore< @@ -270,14 +289,14 @@ function finalHelmersonExample() { >( reducer: Reducer, preloadedState?: any - ): Store & { persistor: Store } => { + ) => { const persistedReducer = persistReducer(persistConfig, reducer) const store = createStore(persistedReducer, preloadedState) const persistor = persistStore(store) return { ...store, - replaceReducer: nextReducer => { + replaceReducer: (nextReducer: Reducer) => { store.replaceReducer(persistReducer(persistConfig, nextReducer)) }, persistor @@ -308,3 +327,44 @@ function finalHelmersonExample() { // @ts-expect-error store.getState().wrongField } + +function composedEnhancers() { + interface State { + someState: string + } + const reducer: Reducer = null as any + + interface Ext1 { + enhancer1: string + } + interface Ext2 { + enhancer2: number + } + + const enhancer1: StoreEnhancer = + createStore => (reducer, preloadedState) => { + const store = createStore(reducer, preloadedState) + return { + ...store, + enhancer1: 'foo' + } + } + + const enhancer2: StoreEnhancer = + createStore => (reducer, preloadedState) => { + const store = createStore(reducer, preloadedState) + return { + ...store, + enhancer2: 5 + } + } + + const composedEnhancer: StoreEnhancer = createStore => + enhancer2(enhancer1(createStore)) + + const enhancedStore = createStore(reducer, composedEnhancer) + enhancedStore.enhancer1 + enhancedStore.enhancer2 + // @ts-expect-error + enhancedStore.enhancer3 +} diff --git a/test/typescript/store.ts b/test/typescript/store.ts index f3b18fca831..5f0ff97983f 100644 --- a/test/typescript/store.ts +++ b/test/typescript/store.ts @@ -5,8 +5,7 @@ import { Action, StoreEnhancer, Unsubscribe, - Observer, - ExtendState + Observer } from '../..' import 'symbol-observable' @@ -22,46 +21,6 @@ type State = { e: BrandedString } -/* extended state */ -const noExtend: ExtendState = { - a: 'a', - b: { - c: 'c', - d: 'd' - }, - e: brandedString -} - -const noExtendError: ExtendState = { - a: 'a', - b: { - c: 'c', - d: 'd' - }, - e: brandedString, - // @ts-expect-error - f: 'oops' -} - -const yesExtend: ExtendState = { - a: 'a', - b: { - c: 'c', - d: 'd' - }, - e: brandedString, - yes: 'we can' -} -// @ts-expect-error -const yesExtendError: ExtendState = { - a: 'a', - b: { - c: 'c', - d: 'd' - }, - e: brandedString -} - interface DerivedAction extends Action { type: 'a' b: 'b'