-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 rewrite #73
Conversation
Deploy preview for redux-starter-kit-docs ready! Built with commit 497babf https://deploy-preview-73--redux-starter-kit-docs.netlify.com |
@Dudeonyx I just finished turning everything to TypeScript. I would really appreciate a review by you, especially regarding the interfaces and type arguments used (they are a bit different from the ones you used in your fork). I also added some typings-tester tests that make it easy to verify that e.g. type argument inference works the way we envision. |
@markerikson In this PR I added doc comments to all functions so that they show up during auto-completion in Visual Studio Code and other editors. These comments are taken from the current docs, but are modified and extended a bit. I would be happy to backport these improvements to the docs pages (likely in a separate PR) if they look good to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass of reviews. Looks good so far, might come back here if I see more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote these mostly from the point of view of a consuming the API; I'm not sure if some of the things I commented on are non-public APIs.
src/configureStore.ts
Outdated
*/ | ||
export function getDefaultMiddleware( | ||
isProduction = IS_PRODUCTION | ||
): Middleware[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This erases the type of the thunk middleware, so you need to somehow add it back later, otherwise its enhanced dispatch
type gets lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing it like this
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
export function getDefaultMiddleware(isProduction = IS_PRODUCTION) {
const middlewareArray = [thunk,];
let middlewareArrayPlus;
if (!isProduction) {
middlewareArrayPlus = [
createImmutableStateInvariantMiddleware(),
thunk,
createSerializableStateInvariantMiddleware(),
];
}
return middlewareArrayPlus === undefined
? middlewareArray
: middlewareArrayPlus;
}
preserves the types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yeah, this could be [ThunkMiddleware<S, A>, ...Middleware[]]
for instance. However I have no idea how we would preserve the extension type in configureStore
. Would we need overrides with a one-element middleware
tuple, two-element middleware
tuple, etc., so that we can write Store<S,A> & Ext1 & Ext2 & ...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we would need to write an exponential amount of overloads since not only is the number of middlewares
variable the number of enhancers
is as well.
So we'd end up with overrides for one-element middleware
tuple and one-element enhancer
tuple, one-element middleware
tuple and two-element enhancer
tuple, two-element middleware tuple and one-element enhancer
tuple, two-element middleware
tuple and two-element enhancer
tuple, etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this is a point where we need to favor practicality over maximum type precision. If we cannot find an economical way to get the type just right, we'll probably get by just fine by having the configured store's dispatch
accept a relatively generic type like unknown
for now. We can later see if we can tweak the API to make typing easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I addressed the problem by letting configureStore()
return an EnhancedStore extends Store
type that is hardcoded to allow dispatching thunks in addition to plain actions. This at least unblocks the use of redux-thunk. Let's improve on this after this PR (perhaps with the help of an API change).
@Kovensky Thank you a lot for the very thorough review! I'm still learning capture and maintain as much type information as practical, so your input is extremely appreciated. I'll comment on your points one-by-one. On a general note, this PR is deliberately about a one-to-one translation to TypeScript without any changes to the API design as such (which surely could be made more typing-friendly at some places). Improving the API for type-ability is then the next step after this has been merged. |
This is a pre-requisite to using @babel/preset-typescript later. This change required upgrading rollup-plugin-babel, which in turn required upgrading Rollup.js itself. Fortunately, the upgraded rollup-plugin-babel automatically takes care of Babel helpers deduplication and turning off module transpilation in Babel's env preset, so the Babel configuration could be drastically simplified. For the same reason, we don't need rollup-plugin-replace anymore.
It's apparently not strictly needed for Babel's TypeScript plugin, but at least more explicit.
This allows more concrete types like `Action<'increment'>` to be inferred by TypeScript.
It's implied by "target": "es2018".
I've just published https://github.com/reduxjs/redux-starter-kit/releases/tag/v0.4.0-0 as NPM tag |
For now, we need to use the Git version of typescript-eslint-parser because the last release only supports TypeScript 3.1. A new release is planned for any day now, so we should be able to move to an NPM release soon. eslint/typescript-eslint-parser#596 As ESLint's no-unused-vars doesn't work well with TypeScript, I enabled noUnusedLocals and noUnusedParameters on the TypeScript side instead.
neat, but in the current release on ts-next, the index.d.ts references src/... files, which are not distributed (only dist included) could you please fix that? |
Cool. I'll put up a new |
@denisw : I don't think the |
@markerikson The |
Just noticed that my declaration file approach broke the typing tests. I'll reverted the change and resorted to simply adding |
Published |
Awright, no one's saying "it works", but no one's whining about this either. Let's do it. |
Published as https://github.com/reduxjs/redux-starter-kit/releases/tag/v0.4.0 . Thanks to everyone for working on this! |
It works! Thanks @denisw and @markerikson |
Thanks @denisw and @markerikson , this is great! From the looks of the original PR message, is the import { configureStore, createSlice } from 'redux-starter-kit';
import { combineReducers } from 'redux';
// Is there a better way to import/use this type? Pulling from `/src` doesn't feel right
import { PayloadAction } from 'redux-starter-kit/src/createAction';
const user = createSlice({
slice: 'user',
initialState: { name: '', age: 100 },
reducers: {
setUserName: (state, action: PayloadAction<string>) => {
state.name = action.payload;
},
},
});
const store = configureStore({
reducer: combineReducers({
user: user.reducer,
}),
});
// This results in a compilation issue
// const store2 = configureStore({
// reducer: {
// user: user.reducer,
// },
// });
// Could the resolved type be (action: string) => PayloadAction<string, string>
// instead of (...args: any[]) => PayloadAction<any, string> as calls like this work
// user.actions.setUserName(1, 'foo', true);
user.actions.setUserName('eric');
const state = store.getState();
// No autocompletion here and things like this compile and blow up at runtime:
// console.log(user.selectors.getUser2(state));
// I realize the selectorName is dynamically generated here and can make things tricky
console.log(user.selectors.getUser(state)); |
@vincentjames501 this part is wrong const user = createSlice({
slice: 'user',
initialState: { name: '', age: 100 },
reducers: {
setUserName: (state, action: PayloadAction<string>) => {
state.name = action.payload;
},
},
}); The case reducers do not receive the entire action object, just the payload. So it should be const user = createSlice({
slice: 'user',
initialState: { name: '', age: 100 },
reducers: {
setUserName: (state, payload: string ) => {
state.name = payload;
},
},
}); |
@Dudeonyx : in RSK, they do receive the whole action: |
@markerikson my mistake, thought it was just the payload like in |
Yeah, I debated back and forth on that one, and decided to pass the entire action for consistency with the rest of the Redux world. Users can always destructure |
The problem brought up by @vincentjames501 seems to still persist - action creators are not really getting a valid payload type. Edit: manually typing it works fine, but there is no real working inference of the third generic parameter. This is what it manually looks like: const slice = createSlice<
HomeModuleState,
any,
{
increment: CaseReducer<HomeModuleState, PayloadAction<number>>;
decrement: CaseReducer<HomeModuleState, PayloadAction<number>>;
}
>({
slice: 'home',
reducers: {
increment: (state, action: PayloadAction<number>) => ({ ...state, counter: state.counter + action.payload }),
decrement: (state, action: PayloadAction<number>) => ({ ...state, counter: state.counter - action.payload })
},
initialState: { counter: 0 }
});
slice.actions.increment(5);
slice.actions.increment('foo'); // only has an error when manually typed above |
@denisw will do later, thanks :) |
* move internal exports out of entry point * smuggle in a small test
Based on the Babel 7 PR (#71).
The codebase will be ported here incrementally based on #38, #70 and @Dudeonyx's TypeScript fork.
To do:
createAction.js
createReducer.js
configureStore.js
isPlainObject.js
createSlice.js
index.js
serializableStateInvariantMiddleware.js
sliceSelector.js
Add type tests using typings-tester (also used in the core Redux repo)