From 5a084a62fda9ebf39ffa969458d9950b4f051688 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 12 Feb 2023 14:47:15 -0500 Subject: [PATCH] Fix type of next parameter in StoreEnhancer type --- src/applyMiddleware.ts | 9 +--- src/createStore.ts | 33 +++++++++---- src/index.ts | 3 +- src/types/store.ts | 43 ++++++----------- test/typescript/enhancers.ts | 92 +++++++++++++++++++++++++++++------- test/typescript/store.ts | 43 +---------------- 6 files changed, 118 insertions(+), 105 deletions(-) diff --git a/src/applyMiddleware.ts b/src/applyMiddleware.ts index af68589ab0..744ed27895 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 9e13aa2291..921b723c5f 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -39,7 +39,12 @@ import { kindOf } from './utils/kindOf' * `import { legacy_createStore as createStore} from 'redux'` * */ -export function createStore( +export function createStore< + S, + A extends Action, + Ext extends {} = {}, + StateExt extends {} = {} +>( reducer: Reducer, enhancer?: StoreEnhancer ): Store & Ext @@ -68,12 +73,22 @@ export function createStore( * `import { legacy_createStore as createStore} from 'redux'` * */ -export function createStore( +export function createStore< + S, + A extends Action, + Ext extends {} = {}, + StateExt extends {} = {} +>( reducer: Reducer, preloadedState?: PreloadedState, enhancer?: StoreEnhancer ): Store & Ext -export function createStore( +export function createStore< + S, + A extends Action, + Ext extends {} = {}, + StateExt extends {} = {} +>( reducer: Reducer, preloadedState?: PreloadedState | StoreEnhancer, enhancer?: StoreEnhancer @@ -401,8 +416,8 @@ export function createStore( export function legacy_createStore< S, A extends Action, - Ext = {}, - StateExt = never + Ext extends {} = {}, + StateExt extends {} = {} >( reducer: Reducer, enhancer?: StoreEnhancer @@ -440,8 +455,8 @@ export function legacy_createStore< export function legacy_createStore< S, A extends Action, - Ext = {}, - StateExt = never + Ext extends {} = {}, + StateExt extends {} = {} >( reducer: Reducer, preloadedState?: PreloadedState, @@ -450,8 +465,8 @@ export function legacy_createStore< export function legacy_createStore< S, A extends Action, - Ext = {}, - StateExt = never + Ext extends {} = {}, + StateExt extends {} = {} >( reducer: Reducer, preloadedState?: PreloadedState | StoreEnhancer, diff --git a/src/index.ts b/src/index.ts index 926be4d143..0fa083343b 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 fb5b1a1d5f..c477cb6471 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,13 +246,16 @@ 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 = < - S = any, - A extends Action = AnyAction +export type StoreEnhancer = < + NextExt extends {}, + NextStateExt extends {} >( + next: StoreEnhancerStoreCreator +) => StoreEnhancerStoreCreator +export type StoreEnhancerStoreCreator< + Ext extends {} = {}, + StateExt extends {} = {} +> = ( reducer: Reducer, preloadedState?: PreloadedState ) => Store & Ext diff --git a/test/typescript/enhancers.ts b/test/typescript/enhancers.ts index ece858f652..5acdbb8d0a 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 3cdb6b769c..5d4158e526 100644 --- a/test/typescript/store.ts +++ b/test/typescript/store.ts @@ -5,8 +5,7 @@ import { Action, StoreEnhancer, Unsubscribe, - Observer, - ExtendState + Observer } from '../../src' 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'