-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
TypeScript definition for StoreEnhancer prevents state type extension #1648
Comments
We could probably add some signatures that would support state type alteration. I'm on vacation in trip now and if noone has done it before I return, I'll make a change then. |
@fdecampredon Unfortunately I don't have much time right now, can you please make a PR? |
@fdecampredon I took a closer look and got a bit confused. Suppose we have a reducer enhancer, as you said: function wrapReducer<S>(reducer: Reducer<S>): Reducer<S & {foo: string}>; I assume that what we want to achieve is to get function createStore<S>(reducer: Reducer<S>, initialState: S): Store<S & {foo: string}>; This already differs from what you wrote, note So I assume you meant something like this: function wrapState<S>(state: S): S & {foo: string};
function enhancer<S>(next: StoreEnhancerStoreCreator<S & {foo: string}>) {
return (reducer: Reducer<S>, initialState: S) =>
next(wrapReducer(reducer), wrapState(initialState));
} The return type of Building new typesLet's try to rewrite our typings to allow such Store Creators: type StoreEnhancerStoreCreator<S, R> =
(reducer: Reducer<S>, initialState: S) => Store<R>; Pull this new type to the interface StoreCreator {
// ...
<S, R>(reducer: Reducer<S>, initialState: S,
enhancer: (createStore: StoreEnhancerStoreCreator<R, R>) =>
StoreEnhancerStoreCreator<S, R>
): Store<R>; Note that Let's extract type StoreCreatorEnhancer<S, R> =
(createStore: StoreEnhancerStoreCreator<R, R>) =>
StoreEnhancerStoreCreator<S, R>;
interface StoreCreator {
// ...
<S, R>(reducer: Reducer<S>, initialState: S,
enhancer: StoreCreatorEnhancer<S, R>): Store<R>; Note that our new Generic EnhancersThe most generic version of type StoreEnhancer =
(createStore: StoreEnhancerStoreCreator<any, any>) =>
StoreEnhancerStoreCreator<any, any>; i.e. no restriction on input/output types. Middleware Enhancers
type MiddlewareEnhancer =
<S, R>(createStore: StoreEnhancerStoreCreator<S, R>) =>
StoreEnhancerStoreCreator<S, R>; ExperimentingAfter doing some experimentation I found out that TypeScript compiler is not smart enough to infer some types: type State = {foo: string};
const reducer: Reducer<State>;
const storeWithThunkMiddleware: Store<State> = createStore(
reducer,
applyMiddleware(thunkMiddleware)
); gives following error:
To fix this I had to explicitly declare type parameters for const storeWithThunkMiddleware: Store<State> = createStore<State, State>(
reducer,
applyMiddleware(thunkMiddleware)
); That's not very convenient considering that enhancers usually don't alter state type. So I added two more types for such enhancers: type StoreEnhancerStoreCreator<S> =
(reducer: Reducer<S>, initialState: S) => Store<S>;
type StoreCreatorEnhancer<S> =
(createStore: StoreEnhancerStoreCreator<S>) => StoreEnhancerStoreCreator<S>;
type AlteringStoreEnhancerStoreCreator<S, R> =
(reducer: Reducer<S>, initialState: S) => Store<R>;
type AlteringStoreCreatorEnhancer<S, R> =
(createStore: AlteringStoreEnhancerStoreCreator<R, R>) =>
AlteringStoreEnhancerStoreCreator<S, R>; And two overloads for interface StoreCreator {
<S>(reducer: Reducer<S>, enhancer?: StoreCreatorEnhancer<S>): Store<S>;
<S>(reducer: Reducer<S>, initialState: S,
enhancer?: StoreCreatorEnhancer<S>): Store<S>;
<S, R>(reducer: Reducer<S>, enhancer: AlteringStoreCreatorEnhancer<S, R>): Store<R>;
<S, R>(reducer: Reducer<S>, initialState: S,
enhancer: AlteringStoreCreatorEnhancer<S, R>): Store<R>;
} Summing upI made everything work, although it seems kinda heavy. Maybe we should enforce that |
Note that we’ll be changing how store enhancers work in a month or two: #1702 |
Simpler versionAfter doing some more juggling I came up with the following proposal:
// used by Store Enhancers
type StoreCreator<S> = (reducer: Reducer<S>, initialState: S) => Store<S>;
type StoreEnhancer =
<S, R>(createStore: StoreCreator<S>) => StoreCreator<R>;
type StrictStoreEnhancer<S, R> =
(createStore: StoreCreator<S>) => StoreCreator<R>;
function createStore<S>(reducer: Reducer<S>,
enhancer?: StrictStoreEnhancer<S, S>): Store<S>;
function createStore<S>(reducer: Reducer<S>, initialState: S,
enhancer?: StrictStoreEnhancer<S, S>): Store<S>;
function createStore<S, R>(reducer: Reducer<S>,
enhancer: StrictStoreEnhancer<S, R>): Store<R>;
function createStore<S, R>(reducer: Reducer<S>, initialState: S,
enhancer: StrictStoreEnhancer<S, R>): Store<R>; This greatly reduces the number of exported types and allows to solve initial problem: declare function wrapReducer<S>(reducer: Reducer<S>): Reducer<S & { foo: string }>;
declare function wrapState<S>(state: S): S & { foo: string };
function enhancerChangingStateShape<S>(next: StoreCreator<S & { foo: string }>) {
return (reducer: Reducer<S>, initialState: S) =>
next(wrapReducer(reducer), wrapState(initialState));
}
const storeWithChangingEnhancer =
createStore<State, State & {foo: string}>(reducer, enhancerChangingStateShape);
typeof storeWithChangingEnhancer.getState().foo // string |
let middlewares = [immutableStateInvariantMiddleware()];
let middleware = applyMiddleware(...middlewares);
let enhancer = compose(
middleware,
DevTools.instrument()
); gives error in compose |
@Rohitlodha What's the return type of Try this: import {GenericStoreEnhancer} from "redux";
let enhancer = compose(
middleware,
DevTools.instrument() as GenericStoreEnhancer
); |
With the current type definition of
StoreEnhancer
:We cannot define an enhancer that change the type of the sate :
Here we try to create an enhancer that add a
foo
field to the sate, but typescript errors saying (in short) thatS
is not compatible withS & { foo: string }
.This is due to the fact that the
next
function passed as arguments is not generic and so cannot capture the type from arguments.Perhaps the solution would be to add a generic version of the
StoreEnhancerStoreCreator
type, and to change the signature of StoreEnhancer to use that type for thenext
argument.definitions :
example:
The text was updated successfully, but these errors were encountered: