Perfect your code
With built-in code review tools, GitHub makes it easy to raise the quality bar before you ship. Join the 40 million developers who've merged over 200 million pull requests.
Sign up for free See pricing for teams and enterprisesTypeScript rewrite #73
Conversation
This comment has been minimized.
This comment has been minimized.
netlify
bot
commented
Jan 8, 2019
•
Deploy preview for redux-starter-kit-docs ready! Built with commit 497babf https://deploy-preview-73--redux-starter-kit-docs.netlify.com |
This comment has been minimized.
This comment has been minimized.
@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. |
This comment has been minimized.
This comment has been minimized.
@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. |
resir014 left a comment
First pass of reviews. Looks good so far, might come back here if I see more. |
Jessidhia left a comment
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. |
*/ | ||
export function getDefaultMiddleware( | ||
isProduction = IS_PRODUCTION | ||
): Middleware[] { |
This comment has been minimized.
This comment has been minimized.
Jessidhia
Jan 12, 2019
This erases the type of the thunk middleware, so you need to somehow add it back later, otherwise its enhanced dispatch
type gets lost.
This comment has been minimized.
This comment has been minimized.
Dudeonyx
Jan 12, 2019
Contributor
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
This comment has been minimized.
This comment has been minimized.
denisw
Jan 12, 2019
Author
Contributor
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 & ...
?
This comment has been minimized.
This comment has been minimized.
Dudeonyx
Jan 12, 2019
Contributor
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
This comment has been minimized.
This comment has been minimized.
denisw
Jan 12, 2019
Author
Contributor
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.
This comment has been minimized.
This comment has been minimized.
denisw
Jan 13, 2019
Author
Contributor
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).
This comment has been minimized.
This comment has been minimized.
@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.
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
Cool. I'll put up a new |
This comment has been minimized.
This comment has been minimized.
@denisw : I don't think the |
This comment has been minimized.
This comment has been minimized.
@markerikson The |
This comment has been minimized.
This comment has been minimized.
Just noticed that my declaration file approach broke the typing tests. I'll reverted the change and resorted to simply adding |
This comment has been minimized.
This comment has been minimized.
Published |
This comment has been minimized.
This comment has been minimized.
Awright, no one's saying "it works", but no one's whining about this either. Let's do it. |
This comment has been minimized.
This comment has been minimized.
Published as https://github.com/reduxjs/redux-starter-kit/releases/tag/v0.4.0 . Thanks to everyone for working on this! |
This comment has been minimized.
This comment has been minimized.
It works! Thanks @denisw and @markerikson |
This comment has been minimized.
This comment has been minimized.
vincentjames501
commented
Jan 23, 2019
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)); |
This comment has been minimized.
This comment has been minimized.
@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;
},
},
}); |
This comment has been minimized.
This comment has been minimized.
@Dudeonyx : in RSK, they do receive the whole action: |
This comment has been minimized.
This comment has been minimized.
@markerikson my mistake, thought it was just the payload like in |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@denisw will do later, thanks :) |
denisw commentedJan 7, 2019
•
edited
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)