diff --git a/.eslintrc.js b/.eslintrc.js index 1512249b46..b51505412b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { extends: 'react-app', - parser: 'typescript-eslint-parser', + parser: '@typescript-eslint/parser', rules: { 'jsx-a11y/href-no-hash': 'off', diff --git a/package-lock.json b/package-lock.json index d1777c4095..1e79716ccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -975,6 +975,47 @@ "redux": "^3.6.0 || ^4.0.0" } }, + "@typescript-eslint/parser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.6.0.tgz", + "integrity": "sha512-VB9xmSbfafI+/kI4gUK3PfrkGmrJQfh0N4EScT1gZXSZyUxpsBirPL99EWZg9MmPG0pzq/gMtgkk7/rAHj4aQw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "1.6.0", + "eslint-scope": "^4.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.6.0.tgz", + "integrity": "sha512-A4CanUwfaG4oXobD5y7EXbsOHjCwn8tj1RDd820etpPAjH+Icjc2K9e/DQM1Hac5zH2BSy+u6bjvvF2wwREvYA==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", @@ -7601,51 +7642,11 @@ "dev": true }, "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, - "typescript-eslint-parser": { - "version": "github:eslint/typescript-eslint-parser#a8fee9089306b9fc890dd9275cbc7f3988fcccda", - "from": "github:eslint/typescript-eslint-parser", - "dev": true, - "requires": { - "eslint-scope": "^4.0.0", - "eslint-visitor-keys": "^1.0.0", - "typescript-estree": "18.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } - } - }, - "typescript-estree": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/typescript-estree/-/typescript-estree-18.0.0.tgz", - "integrity": "sha512-HxTWrzFyYOPWA91Ij7xL9mNUVpGTKLH2KiaBn28CMbYgX2zgWdJqU9hO7Are+pAPAqY91NxAYoaAyDDZ3rLj2A==", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } - } - }, "typings-tester": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/typings-tester/-/typings-tester-0.3.2.tgz", diff --git a/package.json b/package.json index d02f5b86bd..6661d49447 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@types/jest": "^23.3.12", "@types/node": "^10.12.18", "@types/redux-immutable-state-invariant": "^2.1.0", + "@typescript-eslint/parser": "^1.6.0", "babel-core": "7.0.0-bridge.0", "babel-eslint": "^10.0.1", "eslint": "^4.17.0", @@ -30,8 +31,7 @@ "rollup-plugin-babel": "^4.2.0", "rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-node-resolve": "^4.0.0", - "typescript": "^3.2.2", - "typescript-eslint-parser": "eslint/typescript-eslint-parser", + "typescript": "^3.4.3", "typings-tester": "^0.3.2" }, "scripts": { diff --git a/src/createReducer.ts b/src/createReducer.ts index 1117cdfa0f..4934315425 100644 --- a/src/createReducer.ts +++ b/src/createReducer.ts @@ -1,6 +1,11 @@ import createNextState, { Draft } from 'immer' import { AnyAction, Action, Reducer } from 'redux' +/** + * Defines a mapping from action types to corresponding action object shapes. + */ +export type Actions = Record + /** * An *case reducer* is a reducer function for a speficic action type. Case * reducers can be composed to full reducers using `createReducer()`. @@ -23,8 +28,8 @@ export type CaseReducer = ( /** * A mapping from action types to case reducers for `createReducer()`. */ -export interface CaseReducersMapObject { - [actionType: string]: CaseReducer +export type CaseReducers = { + [T in keyof AS]: AS[T] extends Action ? CaseReducer : void } /** @@ -43,17 +48,17 @@ export interface CaseReducersMapObject { * @param actionsMap A mapping from action types to action-type-specific * case redeucers. */ -export function createReducer( - initialState: S, - actionsMap: CaseReducersMapObject -): Reducer { +export function createReducer< + S, + CR extends CaseReducers = CaseReducers +>(initialState: S, actionsMap: CR): Reducer { return function(state = initialState, action): S { // @ts-ignore createNextState() produces an Immutable> rather // than an Immutable, and TypeScript cannot find out how to reconcile // these two types. return createNextState(state, (draft: Draft) => { const caseReducer = actionsMap[action.type] - return caseReducer ? caseReducer(draft, action as A) : undefined + return caseReducer ? caseReducer(draft, action) : undefined }) } } diff --git a/src/createSlice.test.ts b/src/createSlice.test.ts index ba11e14b0d..ca011dacd6 100644 --- a/src/createSlice.test.ts +++ b/src/createSlice.test.ts @@ -1,12 +1,13 @@ import { createSlice } from './createSlice' -import { createAction } from './createAction' +import { createAction, PayloadAction } from './createAction' describe('createSlice', () => { describe('when slice is empty', () => { const { actions, reducer, selectors } = createSlice({ reducers: { increment: state => state + 1, - multiply: (state, action) => state * action.payload + multiply: (state, action: PayloadAction) => + state * action.payload }, initialState: 0 }) diff --git a/src/createSlice.ts b/src/createSlice.ts index 53b885a6ed..791e474fce 100644 --- a/src/createSlice.ts +++ b/src/createSlice.ts @@ -1,16 +1,17 @@ -import { Action, AnyAction, Reducer } from 'redux' +import { Reducer } from 'redux' import { createAction, PayloadAction } from './createAction' -import { createReducer, CaseReducersMapObject } from './createReducer' +import { createReducer, CaseReducers } from './createReducer' import { createSliceSelector, createSelectorName } from './sliceSelector' /** * An action creator atttached to a slice. */ -export type SliceActionCreator

= (payload: P) => PayloadAction

+export type SliceActionCreator

= P extends void + ? () => PayloadAction + : (payload: P) => PayloadAction

export interface Slice< S = any, - A extends Action = AnyAction, AP extends { [key: string]: any } = { [key: string]: any } > { /** @@ -21,7 +22,7 @@ export interface Slice< /** * The slice's reducer. */ - reducer: Reducer + reducer: Reducer /** * Action creators for the types of actions that are handled by the slice @@ -43,9 +44,7 @@ export interface Slice< */ export interface CreateSliceOptions< S = any, - A extends Action = AnyAction, - CR extends CaseReducersMapObject = CaseReducersMapObject, - CR2 extends CaseReducersMapObject = CaseReducersMapObject + CR extends CaseReducers = CaseReducers > { /** * The slice's name. Used to namespace the generated action types and to @@ -70,19 +69,15 @@ export interface CreateSliceOptions< * functions. These reducers should have existing action types used * as the keys, and action creators will _not_ be generated. */ - extraReducers?: CR2 + extraReducers?: CaseReducers } -type ExtractPayloads< - S, - A extends PayloadAction, - CR extends CaseReducersMapObject -> = { - [type in keyof CR]: CR[type] extends (state: S) => any +type CaseReducerActionPayloads> = { + [T in keyof CR]: CR[T] extends (state: any) => any ? void - : (CR[type] extends (state: S, action: PayloadAction) => any + : (CR[T] extends (state: any, action: PayloadAction) => any ? P - : never) + : void) } function getType(slice: string, actionKey: string): string { @@ -97,13 +92,9 @@ function getType(slice: string, actionKey: string): string { * * The `reducer` argument is passed to `createReducer()`. */ -export function createSlice< - S = any, - A extends PayloadAction = PayloadAction, - CR extends CaseReducersMapObject = CaseReducersMapObject ->( - options: CreateSliceOptions -): Slice> { +export function createSlice>( + options: CreateSliceOptions +): Slice> { const { slice = '', initialState } = options const reducers = options.reducers || {} const extraReducers = options.extraReducers || {} diff --git a/type-tests/files/createReducer.typetest.ts b/type-tests/files/createReducer.typetest.ts index 7fc7f22c71..90ecbd2603 100644 --- a/type-tests/files/createReducer.typetest.ts +++ b/type-tests/files/createReducer.typetest.ts @@ -23,7 +23,7 @@ import { AnyAction, createReducer, Reducer } from 'redux-starter-kit' } /** - * Test: createReducer() type parameters can be specified expliclity. + * Test: createReducer() state type can be specified expliclity. */ { type CounterAction = @@ -36,19 +36,13 @@ import { AnyAction, createReducer, Reducer } from 'redux-starter-kit' const decrementHandler = (state: number, action: CounterAction) => state - action.payload - createReducer(0, { + createReducer(0, { increment: incrementHandler, decrement: decrementHandler }) // typings:expect-error - createReducer(0, { - increment: incrementHandler, - decrement: decrementHandler - }) - - // typings:expect-error - createReducer(0, { + createReducer(0, { increment: incrementHandler, decrement: decrementHandler })