From 889030c946c514de1ce238b6d89e8fc0c6473897 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Tue, 16 Feb 2016 21:48:55 +0600 Subject: [PATCH 01/12] Add TypeScript definitions --- index.d.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 52 insertions(+) create mode 100644 index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000000..305680376c --- /dev/null +++ b/index.d.ts @@ -0,0 +1,51 @@ +export interface ActionCreator { + (...args: any[]): any; +} + +export type Reducer = (state: S, action: any) => S; + +export type Dispatch = (action: any) => any; + +export interface MiddlewareAPI { + dispatch: Dispatch; + getState: () => S; +} + +export interface Middleware { + (api: MiddlewareAPI): (next: Dispatch) => Dispatch; +} + +export class Store { + dispatch: Dispatch; + getState: () => S; + subscribe: (listener: () => void) => () => void; + replaceReducer: (reducer: Reducer) => void; +} + +export type StoreCreator = (reducer: Reducer, initialState?: S) => Store; + +export type StoreEnhancer = (next: StoreCreator) => StoreCreator; + +export function createStore(reducer: Reducer, initialState?: S, + enhancer?: StoreEnhancer): Store; + +export function bindActionCreators(actionCreators: T, dispatch: Dispatch): T; + +export function combineReducers(reducers: {[key: string]: Reducer}): Reducer; +export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; + +// from DefinitelyTyped/compose-function +// Hardcoded signatures for 2-4 parameters +export function compose(f1: (b: B) => C, + f2: (a: A) => B): (a: A) => C; +export function compose(f1: (b: C) => D, + f2: (a: B) => C, + f3: (a: A) => B): (a: A) => D; +export function compose(f1: (b: D) => E, + f2: (a: C) => D, + f3: (a: B) => C, + f4: (a: A) => B): (a: A) => E; + +// Minimal typing for more than 4 parameters +export function compose(f1: (a: any) => Result, + ...functions: Function[]): (a: any) => Result; diff --git a/package.json b/package.json index 0961676f4d..af1b844914 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Predictable state container for JavaScript apps", "main": "lib/index.js", "jsnext:main": "es/index.js", + "typings": "./index.d.ts", "files": [ "dist", "lib", From c01501fd461457db8dc30b12c6e313daa1957243 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Wed, 17 Feb 2016 11:43:20 +0600 Subject: [PATCH 02/12] Update typings for `Dispatch` and `StoreCreator` --- index.d.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index 305680376c..027c2c1751 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,8 @@ -export interface ActionCreator { - (...args: any[]): any; +export interface Action { + type: string; } -export type Reducer = (state: S, action: any) => S; +export type Reducer = (state: S, action: Action) => S; export type Dispatch = (action: any) => any; @@ -15,25 +15,34 @@ export interface Middleware { (api: MiddlewareAPI): (next: Dispatch) => Dispatch; } -export class Store { +export interface Store { dispatch: Dispatch; getState: () => S; subscribe: (listener: () => void) => () => void; replaceReducer: (reducer: Reducer) => void; } -export type StoreCreator = (reducer: Reducer, initialState?: S) => Store; +export interface StoreCreator { + (reducer: Reducer): Store; + (reducer: Reducer, enhancer: StoreEnhancer): Store; + (reducer: Reducer, initialState: S): Store; + (reducer: Reducer, initialState: S, enhancer: StoreEnhancer): Store; +} export type StoreEnhancer = (next: StoreCreator) => StoreCreator; -export function createStore(reducer: Reducer, initialState?: S, - enhancer?: StoreEnhancer): Store; - -export function bindActionCreators(actionCreators: T, dispatch: Dispatch): T; +export const createStore: StoreCreator; export function combineReducers(reducers: {[key: string]: Reducer}): Reducer; export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; + +export interface ActionCreator { + (...args: any[]): any; +} + +export function bindActionCreators(actionCreators: T, dispatch: Dispatch): T; + // from DefinitelyTyped/compose-function // Hardcoded signatures for 2-4 parameters export function compose(f1: (b: B) => C, From 4b09a3bb15aabd3cb6de1846e4863e634765f974 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Wed, 17 Feb 2016 12:55:38 +0600 Subject: [PATCH 03/12] Fix StoreCreator type parameter --- index.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index 027c2c1751..f5bcd5bf63 100644 --- a/index.d.ts +++ b/index.d.ts @@ -22,14 +22,14 @@ export interface Store { replaceReducer: (reducer: Reducer) => void; } -export interface StoreCreator { - (reducer: Reducer): Store; - (reducer: Reducer, enhancer: StoreEnhancer): Store; - (reducer: Reducer, initialState: S): Store; - (reducer: Reducer, initialState: S, enhancer: StoreEnhancer): Store; +export interface StoreCreator { + (reducer: Reducer): Store; + (reducer: Reducer, enhancer: StoreEnhancer): Store; + (reducer: Reducer, initialState: S): Store; + (reducer: Reducer, initialState: S, enhancer: StoreEnhancer): Store; } -export type StoreEnhancer = (next: StoreCreator) => StoreCreator; +export type StoreEnhancer = (next: StoreCreator) => StoreCreator; export const createStore: StoreCreator; From 6b4f7f64954dcde96ad47221cf01784e0b70c9ef Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Wed, 17 Feb 2016 12:56:13 +0600 Subject: [PATCH 04/12] Add tests for TypeScript definitions --- package.json | 3 +++ test/typescript.spec.js | 12 +++++++++ test/typescript/createStore.ts | 22 ++++++++++++++++ test/typescript/customLoggerMiddleware.ts | 31 +++++++++++++++++++++++ test/typescript/subscribe.ts | 22 ++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 test/typescript.spec.js create mode 100644 test/typescript/createStore.ts create mode 100644 test/typescript/customLoggerMiddleware.ts create mode 100644 test/typescript/subscribe.ts diff --git a/package.json b/package.json index af1b844914..735c5e0d4e 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.3.13", "babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-register": "^6.3.13", + "chai": "^3.5.0", "cross-env": "^1.0.7", "es3ify": "^0.2.0", "eslint": "^1.10.3", @@ -100,6 +101,8 @@ "isparta": "^4.0.0", "mocha": "^2.2.5", "rimraf": "^2.3.4", + "typescript": "^1.7.5", + "typescript-definition-tester": "0.0.3", "webpack": "^1.9.6" }, "npmName": "redux", diff --git a/test/typescript.spec.js b/test/typescript.spec.js new file mode 100644 index 0000000000..743ecc3039 --- /dev/null +++ b/test/typescript.spec.js @@ -0,0 +1,12 @@ +import * as tt from 'typescript-definition-tester' + + +describe('TypeScript definitions', () => { + it('should compile against index.d.ts', (done) => { + tt.compileDirectory( + __dirname + '/typescript', + fileName => fileName.match(/\.ts$/), + () => done() + ) + }) +}) diff --git a/test/typescript/createStore.ts b/test/typescript/createStore.ts new file mode 100644 index 0000000000..dc83dd98c3 --- /dev/null +++ b/test/typescript/createStore.ts @@ -0,0 +1,22 @@ +import {createStore, Action} from '../../index.d.ts' + +interface AddTodoAction extends Action { + text: string; +} + +function todos(state: string[] = [], action: Action): string[] { + switch (action.type) { + case 'ADD_TODO': + const addTodoAction = action; + return state.concat([addTodoAction.text]); + default: + return state + } +} + +let store = createStore(todos, ['Use Redux']); + +store.dispatch({ + type: 'ADD_TODO', + text: 'Read the docs' +}) diff --git a/test/typescript/customLoggerMiddleware.ts b/test/typescript/customLoggerMiddleware.ts new file mode 100644 index 0000000000..d36762a49c --- /dev/null +++ b/test/typescript/customLoggerMiddleware.ts @@ -0,0 +1,31 @@ +import {Dispatch, applyMiddleware, createStore} from "../../index.d.ts"; + +function logger({ getState }) { + return (next: Dispatch) => (action: any) => { + console.log('will dispatch', action) + + // Call the next dispatch method in the middleware chain. + let returnValue = next(action) + + console.log('state after dispatch', getState()) + + // This will likely be the action itself, unless + // a middleware further in chain changed it. + return returnValue + } +} + +function todos(state:any, action:any) { + return state; +} + +let store = createStore( + todos, + [ 'Use Redux' ], + applyMiddleware(logger) +); + +store.dispatch({ + type: 'ADD_TODO', + text: 'Understand the middleware' +}) diff --git a/test/typescript/subscribe.ts b/test/typescript/subscribe.ts new file mode 100644 index 0000000000..169cac478f --- /dev/null +++ b/test/typescript/subscribe.ts @@ -0,0 +1,22 @@ +import {createStore} from "../../index.d.ts"; + +const store = createStore((state: any) => state, { + some: {deep: {property: 42}} +}); + +function select(state: any) { + return state.some.deep.property +} + +let currentValue: number; +function handleChange() { + let previousValue = currentValue; + currentValue = select(store.getState()); + + if (previousValue !== currentValue) { + console.log('Some deep nested property changed from', previousValue, 'to', currentValue) + } +} + +let unsubscribe = store.subscribe(handleChange); +handleChange(); From b19ebc16954b9796cd3c575a11601d5598d81fa3 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Thu, 18 Feb 2016 11:58:53 +0600 Subject: [PATCH 05/12] Update TypeScript definitions More strict types: replace `any` with type parameters Add middleware tests --- index.d.ts | 57 ++++++++++++++++------- test/typescript/customLoggerMiddleware.ts | 31 ------------ test/typescript/loggerMiddleware.ts | 35 ++++++++++++++ test/typescript/thunkMiddleware.ts | 31 ++++++++++++ 4 files changed, 105 insertions(+), 49 deletions(-) delete mode 100644 test/typescript/customLoggerMiddleware.ts create mode 100644 test/typescript/loggerMiddleware.ts create mode 100644 test/typescript/thunkMiddleware.ts diff --git a/index.d.ts b/index.d.ts index f5bcd5bf63..d0f6c0551f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,30 +2,33 @@ export interface Action { type: string; } -export type Reducer = (state: S, action: Action) => S; -export type Dispatch = (action: any) => any; +/* reducers */ -export interface MiddlewareAPI { - dispatch: Dispatch; - getState: () => S; -} +export type Reducer = (state: S, action: A) => S; -export interface Middleware { - (api: MiddlewareAPI): (next: Dispatch) => Dispatch; +export function combineReducers(reducers: {[key: string]: Reducer}): Reducer; + + +/* store */ + +export interface Dispatch { + (action: A): A; + (action: A): B; } export interface Store { dispatch: Dispatch; - getState: () => S; - subscribe: (listener: () => void) => () => void; - replaceReducer: (reducer: Reducer) => void; + getState(): S; + subscribe(listener: () => void): () => void; + replaceReducer(reducer: Reducer): void; } export interface StoreCreator { (reducer: Reducer): Store; - (reducer: Reducer, enhancer: StoreEnhancer): Store; (reducer: Reducer, initialState: S): Store; + + (reducer: Reducer, enhancer: StoreEnhancer): Store; (reducer: Reducer, initialState: S, enhancer: StoreEnhancer): Store; } @@ -33,15 +36,33 @@ export type StoreEnhancer = (next: StoreCreator) => StoreCreator; export const createStore: StoreCreator; -export function combineReducers(reducers: {[key: string]: Reducer}): Reducer; -export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; +/* middleware */ + +export interface MiddlewareAPI { + dispatch: Dispatch; + getState(): S; +} + +export interface Middleware { + (api: MiddlewareAPI): (next: Dispatch) => (action: A) => B; +} + +export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; + + +/* action creators */ export interface ActionCreator { - (...args: any[]): any; + (...args: any[]): O; } -export function bindActionCreators(actionCreators: T, dispatch: Dispatch): T; +export function bindActionCreators< + T extends ActionCreator|{[key: string]: ActionCreator} +>(actionCreators: T, dispatch: Dispatch): T; + + +/* compose */ // from DefinitelyTyped/compose-function // Hardcoded signatures for 2-4 parameters @@ -56,5 +77,5 @@ export function compose(f1: (b: D) => E, f4: (a: A) => B): (a: A) => E; // Minimal typing for more than 4 parameters -export function compose(f1: (a: any) => Result, - ...functions: Function[]): (a: any) => Result; +export function compose(f1: (a: any) => R, + ...functions: Function[]): (a: I) => R; diff --git a/test/typescript/customLoggerMiddleware.ts b/test/typescript/customLoggerMiddleware.ts deleted file mode 100644 index d36762a49c..0000000000 --- a/test/typescript/customLoggerMiddleware.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {Dispatch, applyMiddleware, createStore} from "../../index.d.ts"; - -function logger({ getState }) { - return (next: Dispatch) => (action: any) => { - console.log('will dispatch', action) - - // Call the next dispatch method in the middleware chain. - let returnValue = next(action) - - console.log('state after dispatch', getState()) - - // This will likely be the action itself, unless - // a middleware further in chain changed it. - return returnValue - } -} - -function todos(state:any, action:any) { - return state; -} - -let store = createStore( - todos, - [ 'Use Redux' ], - applyMiddleware(logger) -); - -store.dispatch({ - type: 'ADD_TODO', - text: 'Understand the middleware' -}) diff --git a/test/typescript/loggerMiddleware.ts b/test/typescript/loggerMiddleware.ts new file mode 100644 index 0000000000..f18ffe2d0b --- /dev/null +++ b/test/typescript/loggerMiddleware.ts @@ -0,0 +1,35 @@ +import { + applyMiddleware, createStore, + Middleware, Dispatch +} from "../../index.d.ts"; + +export const loggerMiddleware: Middleware = ({getState}) => { + return (next: Dispatch) => + (action: any) => { + console.log('will dispatch', action) + + // Call the next dispatch method in the middleware chain. + let returnValue = next(action) + + console.log('state after dispatch', getState()) + + // This will likely be the action itself, unless + // a middleware further in chain changed it. + return returnValue + } +} + +function todos(state: any, action: any) { + return state; +} + +let store = createStore( + todos, + ['Use Redux'], + applyMiddleware(loggerMiddleware) +); + +store.dispatch({ + type: 'ADD_TODO', + text: 'Understand the middleware' +}) diff --git a/test/typescript/thunkMiddleware.ts b/test/typescript/thunkMiddleware.ts new file mode 100644 index 0000000000..a037d7ff4e --- /dev/null +++ b/test/typescript/thunkMiddleware.ts @@ -0,0 +1,31 @@ +import { + Middleware, MiddlewareAPI, + applyMiddleware, createStore, Dispatch +} from "../../index"; + + +type Thunk = (dispatch: Dispatch, getState?: () => S) => O; + + +const thunkMiddleware: Middleware = + ({dispatch, getState}: MiddlewareAPI) => + (next: Dispatch) => + (action: A | Thunk) => + typeof action === 'function' ? + (>action)(dispatch, getState) : + next(action) + + +function todos(state: any, action: any) { + return state; +} + +let store = createStore( + todos, + ['Use Redux'], + applyMiddleware(thunkMiddleware) +); + +store.dispatch, void>((dispatch: Dispatch) => { + dispatch({type: 'ADD_TODO'}) +}) From a6b4d8026626ce391a1af13ef09f519d9e24cefd Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Thu, 18 Feb 2016 18:26:34 +0600 Subject: [PATCH 06/12] Update TypeScript definitions Add type parameter for `Action` Stronger typings for `combineReducers`, add `ReducerMapObject` type Add Unsubscribe type Stronger typings for `bindActionCreators`, add `ActionCreatorsMapObject` type Add and refine tests for complete coverage --- index.d.ts | 42 ++++++++++++----- test/typescript/actionCreators.ts | 29 ++++++++++++ test/typescript/actions.ts | 53 ++++++++++++++++++++++ test/typescript/compose.ts | 22 +++++++++ test/typescript/createStore.ts | 22 --------- test/typescript/dispatch.ts | 12 +++++ test/typescript/loggerMiddleware.ts | 35 --------------- test/typescript/middleware.ts | 60 +++++++++++++++++++++++++ test/typescript/reducers.ts | 70 +++++++++++++++++++++++++++++ test/typescript/store.ts | 62 +++++++++++++++++++++++++ test/typescript/subscribe.ts | 22 --------- test/typescript/thunkMiddleware.ts | 31 ------------- 12 files changed, 339 insertions(+), 121 deletions(-) create mode 100644 test/typescript/actionCreators.ts create mode 100644 test/typescript/actions.ts create mode 100644 test/typescript/compose.ts delete mode 100644 test/typescript/createStore.ts create mode 100644 test/typescript/dispatch.ts delete mode 100644 test/typescript/loggerMiddleware.ts create mode 100644 test/typescript/middleware.ts create mode 100644 test/typescript/reducers.ts create mode 100644 test/typescript/store.ts delete mode 100644 test/typescript/subscribe.ts delete mode 100644 test/typescript/thunkMiddleware.ts diff --git a/index.d.ts b/index.d.ts index d0f6c0551f..e5927baa6b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,13 +1,20 @@ -export interface Action { - type: string; +export interface Action { + type: T; } /* reducers */ -export type Reducer = (state: S, action: A) => S; +export type Reducer = >(state: S, action: A) => S; -export function combineReducers(reducers: {[key: string]: Reducer}): Reducer; +export interface ReducersMapObject { + [key: string]: Reducer; +} + +export function combineReducers(reducers: ReducersMapObject): Reducer; +export function combineReducers( + reducers: M +): Reducer; /* store */ @@ -17,10 +24,14 @@ export interface Dispatch { (action: A): B; } +export interface Unsubscribe { + (): void; +} + export interface Store { dispatch: Dispatch; getState(): S; - subscribe(listener: () => void): () => void; + subscribe(listener: () => void): Unsubscribe; replaceReducer(reducer: Reducer): void; } @@ -53,18 +64,27 @@ export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; /* action creators */ -export interface ActionCreator { - (...args: any[]): O; +export interface ActionCreator { + (...args: any[]): A; } -export function bindActionCreators< - T extends ActionCreator|{[key: string]: ActionCreator} ->(actionCreators: T, dispatch: Dispatch): T; +export interface ActionCreatorsMapObject { + [key: string]: ActionCreator; +} + + +export function bindActionCreators>( + actionCreator: A, dispatch: Dispatch +): A; + +export function bindActionCreators( + actionCreators: M, dispatch: Dispatch +): M; /* compose */ -// from DefinitelyTyped/compose-function +// copied from DefinitelyTyped/compose-function // Hardcoded signatures for 2-4 parameters export function compose(f1: (b: B) => C, f2: (a: A) => B): (a: A) => C; diff --git a/test/typescript/actionCreators.ts b/test/typescript/actionCreators.ts new file mode 100644 index 0000000000..2d96afc6a7 --- /dev/null +++ b/test/typescript/actionCreators.ts @@ -0,0 +1,29 @@ +import { + ActionCreator, Action, Dispatch, + bindActionCreators +} from "../../index.d.ts"; + + +interface AddTodoAction extends Action { + text: string; +} + +const addTodo: ActionCreator = (text: string) => ({ + type: 'ADD_TODO', + text +}) + +const addTodoAction: AddTodoAction = addTodo('test'); + + +declare const dispatch: Dispatch; + +const boundAddTodo = bindActionCreators(addTodo, dispatch); + +const dispatchedAddTodoAction: AddTodoAction = boundAddTodo('test'); + + +const boundActionCreators = bindActionCreators({addTodo}, dispatch); + +const otherDispatchedAddTodoAction: AddTodoAction = + boundActionCreators.addTodo('test'); diff --git a/test/typescript/actions.ts b/test/typescript/actions.ts new file mode 100644 index 0000000000..7dab14abd8 --- /dev/null +++ b/test/typescript/actions.ts @@ -0,0 +1,53 @@ +import {Action as ReduxAction} from "../../index.d.ts"; + + +namespace FSA { + interface Action

extends ReduxAction { + payload: P; + } + + const action: Action = { + type: 'ACTION_TYPE', + payload: 'test', + } + + const payload: string = action.payload; +} + + +namespace FreeShapeAction { + interface Action extends ReduxAction { + [key: string]: any; + } + + const action: Action = { + type: 'ACTION_TYPE', + text: 'test', + } + + const text: string = action['text']; +} + + +namespace StringLiteralTypeAction { + type ActionType = 'A' | 'B' | 'C'; + + const action: ReduxAction = { + type: 'A' + } + + const type: ActionType = action.type; +} + + +namespace EnumTypeAction { + enum ActionType { + A, B, C + } + + const action: ReduxAction = { + type: ActionType.A + } + + const type: ActionType = action.type; +} diff --git a/test/typescript/compose.ts b/test/typescript/compose.ts new file mode 100644 index 0000000000..2392fe151b --- /dev/null +++ b/test/typescript/compose.ts @@ -0,0 +1,22 @@ +import {compose} from "../../index.d.ts"; + +// copied from DefinitelyTyped/compose-function + +const numberToNumber = (a: number): number => a + 2; +const numberToString = (a: number): string => "foo"; +const stringToNumber = (a: string): number => 5; + +const t1: number = compose(numberToNumber, numberToNumber)(5); +const t2: string = compose(numberToString, numberToNumber)(5); +const t3: string = compose(numberToString, stringToNumber)("f"); +const t4: (a: string) => number = compose( + (f: (a: string) => number) => ((p: string) => 5), + (f: (a: number) => string) => ((p: string) => 4) +)(numberToString); + + +const t5: number = compose(stringToNumber, numberToString, numberToNumber)(5); +const t6: string = compose(numberToString, stringToNumber, numberToString, numberToNumber)(5); + +const t7: string = compose( + numberToString, numberToNumber, stringToNumber, numberToString, stringToNumber)("fo"); diff --git a/test/typescript/createStore.ts b/test/typescript/createStore.ts deleted file mode 100644 index dc83dd98c3..0000000000 --- a/test/typescript/createStore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {createStore, Action} from '../../index.d.ts' - -interface AddTodoAction extends Action { - text: string; -} - -function todos(state: string[] = [], action: Action): string[] { - switch (action.type) { - case 'ADD_TODO': - const addTodoAction = action; - return state.concat([addTodoAction.text]); - default: - return state - } -} - -let store = createStore(todos, ['Use Redux']); - -store.dispatch({ - type: 'ADD_TODO', - text: 'Read the docs' -}) diff --git a/test/typescript/dispatch.ts b/test/typescript/dispatch.ts new file mode 100644 index 0000000000..2dac9bb21f --- /dev/null +++ b/test/typescript/dispatch.ts @@ -0,0 +1,12 @@ +import {Dispatch, Action} from "../../index.d.ts"; + + +declare const dispatch: Dispatch; + + +const dispatchResult: Action = dispatch({type: 'TYPE'}); + + +type Thunk = () => O; + +const dispatchThunkResult: number = dispatch, number>(() => 42); diff --git a/test/typescript/loggerMiddleware.ts b/test/typescript/loggerMiddleware.ts deleted file mode 100644 index f18ffe2d0b..0000000000 --- a/test/typescript/loggerMiddleware.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - applyMiddleware, createStore, - Middleware, Dispatch -} from "../../index.d.ts"; - -export const loggerMiddleware: Middleware = ({getState}) => { - return (next: Dispatch) => - (action: any) => { - console.log('will dispatch', action) - - // Call the next dispatch method in the middleware chain. - let returnValue = next(action) - - console.log('state after dispatch', getState()) - - // This will likely be the action itself, unless - // a middleware further in chain changed it. - return returnValue - } -} - -function todos(state: any, action: any) { - return state; -} - -let store = createStore( - todos, - ['Use Redux'], - applyMiddleware(loggerMiddleware) -); - -store.dispatch({ - type: 'ADD_TODO', - text: 'Understand the middleware' -}) diff --git a/test/typescript/middleware.ts b/test/typescript/middleware.ts new file mode 100644 index 0000000000..0077e3f20e --- /dev/null +++ b/test/typescript/middleware.ts @@ -0,0 +1,60 @@ +import { + Middleware, MiddlewareAPI, + applyMiddleware, createStore, Dispatch, Reducer, Action +} from "../../index.d.ts"; + + +type Thunk = (dispatch: Dispatch, getState?: () => S) => O; + + +const thunkMiddleware: Middleware = + ({dispatch, getState}: MiddlewareAPI) => + (next: Dispatch) => + (action: A | Thunk): B => + typeof action === 'function' ? + (>action)(dispatch, getState) : + next(action) + + +const loggerMiddleware: Middleware = ({getState}: MiddlewareAPI) => + (next: Dispatch) => + (action: A): B => { + console.log('will dispatch', action) + + // Call the next dispatch method in the middleware chain. + const returnValue: B = next(action) + + console.log('state after dispatch', getState()) + + // This will likely be the action itself, unless + // a middleware further in chain changed it. + return returnValue + } + + + +type State = { + todos: string[]; +} + +const reducer: Reducer = (state: State, action: Action): State => { + return state; +} + +const storeWithThunkMiddleware = createStore( + reducer, + applyMiddleware(thunkMiddleware) +); + +storeWithThunkMiddleware.dispatch, void>( + (dispatch: Dispatch, getState: () => State) => { + const todos: string[] = getState().todos; + dispatch({type: 'ADD_TODO'}) + } +) + + +const storeWithMultipleMiddleware = createStore( + reducer, + applyMiddleware(loggerMiddleware, thunkMiddleware) +) diff --git a/test/typescript/reducers.ts b/test/typescript/reducers.ts new file mode 100644 index 0000000000..1fd5a0595d --- /dev/null +++ b/test/typescript/reducers.ts @@ -0,0 +1,70 @@ +import { + Reducer, Action, combineReducers, + ReducersMapObject +} from "../../index.d.ts"; + + +type TodosState = string[]; + +interface AddTodoAction extends Action { + text: string; +} + + +const todosReducer: Reducer = (state: TodosState, + action: Action): TodosState => { + switch (action.type) { + case 'ADD_TODO': + return [...state, (action).text] + default: + return state + } +} + +const todosState: TodosState = todosReducer([], { + type: 'ADD_TODO', + text: 'test', +}); + + +type CounterState = number; + + +const counterReducer: Reducer = (state: CounterState, action: Action): CounterState => { + switch (action.type) { + case 'INCREMENT': + return state + 1 + default: + return state + } +} + + +type RootState = { + todos: TodosState; + counter: CounterState; +} + + +const rootReducer: Reducer = combineReducers({ + todos: todosReducer, + counter: counterReducer, +}) + +const rootState: RootState = rootReducer(undefined, { + type: 'ADD_TODO', + text: 'test', +}) + + +interface RootReducers extends ReducersMapObject { + todos: Reducer; + counter: Reducer; +} + + +const rootReducerStrict: Reducer = + combineReducers({ + todos: todosReducer, + counter: counterReducer, + }) diff --git a/test/typescript/store.ts b/test/typescript/store.ts new file mode 100644 index 0000000000..75b979d34c --- /dev/null +++ b/test/typescript/store.ts @@ -0,0 +1,62 @@ +import { + Store, createStore, Reducer, Action, StoreEnhancer, + StoreCreator, Unsubscribe +} from "../../index.d.ts"; + + +type State = { + todos: string[]; +} + +const reducer: Reducer = (state: State, action: Action): State => { + return state; +} + + +/* createStore */ + +const store: Store = createStore(reducer); + +const storeWithInitialState: Store = createStore(reducer, { + todos: [] +}); + +const enhancer: StoreEnhancer = (next: StoreCreator) => next; + +const storeWithEnhancer: Store = createStore(reducer, enhancer); + +const storeWithInitialStateAndEnhancer: Store = createStore(reducer, { + todos: [] +}, enhancer); + + +/* dispatch */ + +store.dispatch({ + type: 'ADD_TODO', + text: 'test' +}) + + +/* getState */ + +const state: State = store.getState(); + + +/* subscribe / unsubscribe */ + +const unsubscribe: Unsubscribe = store.subscribe(() => { + console.log('Current state:', store.getState()) +}) + +unsubscribe(); + + +/* replaceReducer */ + +const newReducer: Reducer = (state: State, + action: Action): State => { + return state; +} + +store.replaceReducer(newReducer); diff --git a/test/typescript/subscribe.ts b/test/typescript/subscribe.ts deleted file mode 100644 index 169cac478f..0000000000 --- a/test/typescript/subscribe.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {createStore} from "../../index.d.ts"; - -const store = createStore((state: any) => state, { - some: {deep: {property: 42}} -}); - -function select(state: any) { - return state.some.deep.property -} - -let currentValue: number; -function handleChange() { - let previousValue = currentValue; - currentValue = select(store.getState()); - - if (previousValue !== currentValue) { - console.log('Some deep nested property changed from', previousValue, 'to', currentValue) - } -} - -let unsubscribe = store.subscribe(handleChange); -handleChange(); diff --git a/test/typescript/thunkMiddleware.ts b/test/typescript/thunkMiddleware.ts deleted file mode 100644 index a037d7ff4e..0000000000 --- a/test/typescript/thunkMiddleware.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - Middleware, MiddlewareAPI, - applyMiddleware, createStore, Dispatch -} from "../../index"; - - -type Thunk = (dispatch: Dispatch, getState?: () => S) => O; - - -const thunkMiddleware: Middleware = - ({dispatch, getState}: MiddlewareAPI) => - (next: Dispatch) => - (action: A | Thunk) => - typeof action === 'function' ? - (>action)(dispatch, getState) : - next(action) - - -function todos(state: any, action: any) { - return state; -} - -let store = createStore( - todos, - ['Use Redux'], - applyMiddleware(thunkMiddleware) -); - -store.dispatch, void>((dispatch: Dispatch) => { - dispatch({type: 'ADD_TODO'}) -}) From 56ab5227d343e953d18412d0dd8fcb84c739461d Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Thu, 18 Feb 2016 19:06:00 +0600 Subject: [PATCH 07/12] Update TypeScript dependency to 1.8.0 to support string literal types --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 735c5e0d4e..27c775e9b2 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "isparta": "^4.0.0", "mocha": "^2.2.5", "rimraf": "^2.3.4", - "typescript": "^1.7.5", + "typescript": "^1.8.0", "typescript-definition-tester": "0.0.3", "webpack": "^1.9.6" }, From 1fa6636d9f986ee80d8af39658c1c89016083f9e Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Thu, 18 Feb 2016 22:46:53 +0600 Subject: [PATCH 08/12] Disable timeout for TypeScript definitions tests --- test/typescript.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/typescript.spec.js b/test/typescript.spec.js index 743ecc3039..710fab2de0 100644 --- a/test/typescript.spec.js +++ b/test/typescript.spec.js @@ -1,7 +1,9 @@ import * as tt from 'typescript-definition-tester' -describe('TypeScript definitions', () => { +describe('TypeScript definitions', function() { + this.timeout(0) + it('should compile against index.d.ts', (done) => { tt.compileDirectory( __dirname + '/typescript', From 6b61fca446bf1a330f92ad549b6f22e69c57e3ae Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Thu, 18 Feb 2016 23:07:56 +0600 Subject: [PATCH 09/12] Fix linter error --- test/typescript.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/typescript.spec.js b/test/typescript.spec.js index 710fab2de0..5d0bc28f0c 100644 --- a/test/typescript.spec.js +++ b/test/typescript.spec.js @@ -1,7 +1,7 @@ import * as tt from 'typescript-definition-tester' -describe('TypeScript definitions', function() { +describe('TypeScript definitions', function () { this.timeout(0) it('should compile against index.d.ts', (done) => { From 16325a227b5148bb6321230e7be273c0e4cfb364 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Fri, 19 Feb 2016 17:24:38 +0600 Subject: [PATCH 10/12] Update TypeScript definitions Add two more overloads for `bindActionCreators` --- index.d.ts | 10 +++++++++ test/typescript/actionCreators.ts | 34 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index e5927baa6b..977ae84363 100644 --- a/index.d.ts +++ b/index.d.ts @@ -77,10 +77,20 @@ export function bindActionCreators>( actionCreator: A, dispatch: Dispatch ): A; +export function bindActionCreators< + A extends ActionCreator, + B extends ActionCreator +>(actionCreator: A, dispatch: Dispatch): B; + export function bindActionCreators( actionCreators: M, dispatch: Dispatch ): M; +export function bindActionCreators< + M extends ActionCreatorsMapObject, + N extends ActionCreatorsMapObject +>(actionCreators: M, dispatch: Dispatch): N; + /* compose */ diff --git a/test/typescript/actionCreators.ts b/test/typescript/actionCreators.ts index 2d96afc6a7..4458f19176 100644 --- a/test/typescript/actionCreators.ts +++ b/test/typescript/actionCreators.ts @@ -1,6 +1,6 @@ import { ActionCreator, Action, Dispatch, - bindActionCreators + bindActionCreators, ActionCreatorsMapObject } from "../../index.d.ts"; @@ -15,6 +15,13 @@ const addTodo: ActionCreator = (text: string) => ({ const addTodoAction: AddTodoAction = addTodo('test'); +type AddTodoThunk = (dispatch: Dispatch) => AddTodoAction; + +const addTodoViaThunk: ActionCreator = (text: string) => + (dispatch: Dispatch) => ({ + type: 'ADD_TODO', + text + }) declare const dispatch: Dispatch; @@ -23,7 +30,32 @@ const boundAddTodo = bindActionCreators(addTodo, dispatch); const dispatchedAddTodoAction: AddTodoAction = boundAddTodo('test'); +const boundAddTodoViaThunk = bindActionCreators< + ActionCreator, + ActionCreator +>(addTodoViaThunk, dispatch) + +const dispatchedAddTodoViaThunkAction: AddTodoAction = + boundAddTodoViaThunk('test'); + + const boundActionCreators = bindActionCreators({addTodo}, dispatch); const otherDispatchedAddTodoAction: AddTodoAction = boundActionCreators.addTodo('test'); + + +interface M extends ActionCreatorsMapObject { + addTodoViaThunk: ActionCreator +} + +interface N extends ActionCreatorsMapObject { + addTodoViaThunk: ActionCreator +} + +const boundActionCreators2 = bindActionCreators({ + addTodoViaThunk +}, dispatch) + +const otherDispatchedAddTodoAction2: AddTodoAction = + boundActionCreators2.addTodoViaThunk('test'); From 6317727a32b7e45bbfc0ca2f48d4bab3137f5892 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Sun, 21 Feb 2016 12:15:25 +0600 Subject: [PATCH 11/12] Update `typescript-definition-tester` to `0.04` --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 27c775e9b2..53915898a8 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.3.13", "babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-register": "^6.3.13", - "chai": "^3.5.0", "cross-env": "^1.0.7", "es3ify": "^0.2.0", "eslint": "^1.10.3", @@ -102,7 +101,7 @@ "mocha": "^2.2.5", "rimraf": "^2.3.4", "typescript": "^1.8.0", - "typescript-definition-tester": "0.0.3", + "typescript-definition-tester": "0.0.4", "webpack": "^1.9.6" }, "npmName": "redux", From a5d44fab7a4cd82cf9144041939aafa719d68040 Mon Sep 17 00:00:00 2001 From: Daniel Lytkin Date: Wed, 24 Feb 2016 17:27:50 +0600 Subject: [PATCH 12/12] Update TypeScript definitions Remove redundant type parameters from `Dispatch` type Remove redundant `combineReducers` overload Simplify `StoreCreator` type by using optional arguments Remove redundant type parameters from `Middleware` type Update `compose` definitions to cover case with zero arguments and case with multiple arguments for innermost function Add JSDoc --- index.d.ts | 365 ++++++++++++++++++++++++++---- test/typescript/actionCreators.ts | 2 +- test/typescript/actions.ts | 16 +- test/typescript/compose.ts | 2 +- test/typescript/dispatch.ts | 4 +- test/typescript/middleware.ts | 30 +-- test/typescript/reducers.ts | 21 +- test/typescript/store.ts | 5 +- 8 files changed, 360 insertions(+), 85 deletions(-) diff --git a/index.d.ts b/index.d.ts index 977ae84363..ad6973eb71 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,50 +1,250 @@ -export interface Action { - type: T; +/** + * An *action* is a plain object that represents an intention to change the + * state. Actions are the only way to get data into the store. Any data, + * whether from UI events, network callbacks, or other sources such as + * WebSockets needs to eventually be dispatched as actions. + * + * Actions must have a `type` field that indicates the type of action being + * performed. Types can be defined as constants and imported from another + * module. It’s better to use strings for `type` than Symbols because strings + * are serializable. + * + * Other than `type`, the structure of an action object is really up to you. + * If you’re interested, check out Flux Standard Action for recommendations on + * how actions should be constructed. + */ +export interface Action { + type: any; } /* reducers */ -export type Reducer = >(state: S, action: A) => S; - +/** + * A *reducer* (also called a *reducing function*) is a function that accepts + * an accumulation and a value and returns a new accumulation. They are used + * to reduce a collection of values down to a single value + * + * Reducers are not unique to Redux—they are a fundamental concept in + * functional programming. Even most non-functional languages, like + * JavaScript, have a built-in API for reducing. In JavaScript, it's + * `Array.prototype.reduce()`. + * + * In Redux, the accumulated value is the state object, and the values being + * accumulated are actions. Reducers calculate a new state given the previous + * state and an action. They must be *pure functions*—functions that return + * the exact same output for given inputs. They should also be free of + * side-effects. This is what enables exciting features like hot reloading and + * time travel. + * + * Reducers are the most important concept in Redux. + * + * *Do not put API calls into reducers.* + * + * @template S State object type. + */ +export type Reducer = (state: S, action: A) => S; + +/** + * Object whose values correspond to different reducer functions. + */ export interface ReducersMapObject { [key: string]: Reducer; } +/** + * Turns an object whose values are different reducer functions, into a single + * reducer function. It will call every child reducer, and gather their results + * into a single state object, whose keys correspond to the keys of the passed + * reducer functions. + * + * @template S Combined state object type. + * + * @param reducers An object whose values correspond to different reducer + * functions that need to be combined into one. One handy way to obtain it + * is to use ES6 `import * as reducers` syntax. The reducers may never + * return undefined for any action. Instead, they should return their + * initial state if the state passed to them was undefined, and the current + * state for any unrecognized action. + * + * @returns A reducer function that invokes every reducer inside the passed + * object, and builds a state object with the same shape. + */ export function combineReducers(reducers: ReducersMapObject): Reducer; -export function combineReducers( - reducers: M -): Reducer; /* store */ -export interface Dispatch { - (action: A): A; - (action: A): B; -} - +/** + * A *dispatching function* (or simply *dispatch function*) is a function that + * accepts an action or an async action; it then may or may not dispatch one + * or more actions to the store. + * + * We must distinguish between dispatching functions in general and the base + * `dispatch` function provided by the store instance without any middleware. + * + * The base dispatch function *always* synchronously sends an action to the + * store’s reducer, along with the previous state returned by the store, to + * calculate a new state. It expects actions to be plain objects ready to be + * consumed by the reducer. + * + * Middleware wraps the base dispatch function. It allows the dispatch + * function to handle async actions in addition to actions. Middleware may + * transform, delay, ignore, or otherwise interpret actions or async actions + * before passing them to the next middleware. + */ +export type Dispatch = (action: any) => any; + +/** + * Function to remove listener added by `Store.subscribe()`. + */ export interface Unsubscribe { (): void; } +/** + * A store is an object that holds the application’s state tree. + * There should only be a single store in a Redux app, as the composition + * happens on the reducer level. + * + * @template S State object type. + */ export interface Store { + /** + * Dispatches an action. It is the only way to trigger a state change. + * + * The `reducer` function, used to create the store, will be called with the + * current state tree and the given `action`. Its return value will be + * considered the **next** state of the tree, and the change listeners will + * be notified. + * + * The base implementation only supports plain object actions. If you want + * to dispatch a Promise, an Observable, a thunk, or something else, you + * need to wrap your store creating function into the corresponding + * middleware. For example, see the documentation for the `redux-thunk` + * package. Even the middleware will eventually dispatch plain object + * actions using this method. + * + * @param action A plain object representing “what changed”. It is a good + * idea to keep actions serializable so you can record and replay user + * sessions, or use the time travelling `redux-devtools`. An action must + * have a `type` property which may not be `undefined`. It is a good idea + * to use string constants for action types. + * + * @returns For convenience, the same action object you dispatched. + * + * Note that, if you use a custom middleware, it may wrap `dispatch()` to + * return something else (for example, a Promise you can await). + */ dispatch: Dispatch; + + /** + * Reads the state tree managed by the store. + * + * @returns The current state tree of your application. + */ getState(): S; + + /** + * Adds a change listener. It will be called any time an action is + * dispatched, and some part of the state tree may potentially have changed. + * You may then call `getState()` to read the current state tree inside the + * callback. + * + * You may call `dispatch()` from a change listener, with the following + * caveats: + * + * 1. The subscriptions are snapshotted just before every `dispatch()` call. + * If you subscribe or unsubscribe while the listeners are being invoked, + * this will not have any effect on the `dispatch()` that is currently in + * progress. However, the next `dispatch()` call, whether nested or not, + * will use a more recent snapshot of the subscription list. + * + * 2. The listener should not expect to see all states changes, as the state + * might have been updated multiple times during a nested `dispatch()` before + * the listener is called. It is, however, guaranteed that all subscribers + * registered before the `dispatch()` started will be called with the latest + * state by the time it exits. + * + * @param listener A callback to be invoked on every dispatch. + * @returns A function to remove this change listener. + */ subscribe(listener: () => void): Unsubscribe; - replaceReducer(reducer: Reducer): void; + + /** + * Replaces the reducer currently used by the store to calculate the state. + * + * You might need this if your app implements code splitting and you want to + * load some of the reducers dynamically. You might also need this if you + * implement a hot reloading mechanism for Redux. + * + * @param nextReducer The reducer for the store to use instead. + */ + replaceReducer(nextReducer: Reducer): void; } +/** + * A store creator is a function that creates a Redux store. Like with + * dispatching function, we must distinguish the base store creator, + * `createStore(reducer, initialState)` exported from the Redux package, from + * store creators that are returned from the store enhancers. + * + * @template S State object type. + */ export interface StoreCreator { - (reducer: Reducer): Store; - (reducer: Reducer, initialState: S): Store; - - (reducer: Reducer, enhancer: StoreEnhancer): Store; - (reducer: Reducer, initialState: S, enhancer: StoreEnhancer): Store; + (reducer: Reducer, enhancer?: StoreEnhancer): Store; + (reducer: Reducer, initialState: S, + enhancer?: StoreEnhancer): Store; } +/** + * A store enhancer is a higher-order function that composes a store creator + * to return a new, enhanced store creator. This is similar to middleware in + * that it allows you to alter the store interface in a composable way. + * + * Store enhancers are much the same concept as higher-order components in + * React, which are also occasionally called “component enhancers”. + * + * Because a store is not an instance, but rather a plain-object collection of + * functions, copies can be easily created and modified without mutating the + * original store. There is an example in `compose` documentation + * demonstrating that. + * + * Most likely you’ll never write a store enhancer, but you may use the one + * provided by the developer tools. It is what makes time travel possible + * without the app being aware it is happening. Amusingly, the Redux + * middleware implementation is itself a store enhancer. + */ export type StoreEnhancer = (next: StoreCreator) => StoreCreator; +/** + * Creates a Redux store that holds the state tree. + * The only way to change the data in the store is to call `dispatch()` on it. + * + * There should only be a single store in your app. To specify how different + * parts of the state tree respond to actions, you may combine several + * reducers + * into a single reducer function by using `combineReducers`. + * + * @template S State object type. + * + * @param reducer A function that returns the next state tree, given the + * current state tree and the action to handle. + * + * @param [initialState] The initial state. You may optionally specify it to + * hydrate the state from the server in universal apps, or to restore a + * previously serialized user session. If you use `combineReducers` to + * produce the root reducer function, this must be an object with the same + * shape as `combineReducers` keys. + * + * @param [enhancer] The store enhancer. You may optionally specify it to + * enhance the store with third-party capabilities such as middleware, time + * travel, persistence, etc. The only store enhancer that ships with Redux + * is `applyMiddleware()`. + * + * @returns A Redux store that lets you read the state, dispatch actions and + * subscribe to changes. + */ export const createStore: StoreCreator; @@ -55,57 +255,136 @@ export interface MiddlewareAPI { getState(): S; } +/** + * A middleware is a higher-order function that composes a dispatch function + * to return a new dispatch function. It often turns async actions into + * actions. + * + * Middleware is composable using function composition. It is useful for + * logging actions, performing side effects like routing, or turning an + * asynchronous API call into a series of synchronous actions. + */ export interface Middleware { - (api: MiddlewareAPI): (next: Dispatch) => (action: A) => B; + (api: MiddlewareAPI): (next: Dispatch) => (action: any) => any; } +/** + * Creates a store enhancer that applies middleware to the dispatch method + * of the Redux store. This is handy for a variety of tasks, such as + * expressing asynchronous actions in a concise manner, or logging every + * action payload. + * + * See `redux-thunk` package as an example of the Redux middleware. + * + * Because middleware is potentially asynchronous, this should be the first + * store enhancer in the composition chain. + * + * Note that each middleware will be given the `dispatch` and `getState` + * functions as named arguments. + * + * @param middlewares The middleware chain to be applied. + * @returns A store enhancer applying the middleware. + */ export function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer; /* action creators */ +/** + * An *action creator* is, quite simply, a function that creates an action. Do + * not confuse the two terms—again, an action is a payload of information, and + * an action creator is a factory that creates an action. + * + * Calling an action creator only produces an action, but does not dispatch + * it. You need to call the store’s `dispatch` function to actually cause the + * mutation. Sometimes we say *bound action creators* to mean functions that + * call an action creator and immediately dispatch its result to a specific + * store instance. + * + * If an action creator needs to read the current state, perform an API call, + * or cause a side effect, like a routing transition, it should return an + * async action instead of an action. + * + * @template A Returned action type. + */ export interface ActionCreator { (...args: any[]): A; } +/** + * Object whose values are action creator functions. + */ export interface ActionCreatorsMapObject { [key: string]: ActionCreator; } - -export function bindActionCreators>( - actionCreator: A, dispatch: Dispatch -): A; +/** + * Turns an object whose values are action creators, into an object with the + * same keys, but with every function wrapped into a `dispatch` call so they + * may be invoked directly. This is just a convenience method, as you can call + * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. + * + * For convenience, you can also pass a single function as the first argument, + * and get a function in return. + * + * @param actionCreator An object whose values are action creator functions. + * One handy way to obtain it is to use ES6 `import * as` syntax. You may + * also pass a single function. + * + * @param dispatch The `dispatch` function available on your Redux store. + * + * @returns The object mimicking the original object, but with every action + * creator wrapped into the `dispatch` call. If you passed a function as + * `actionCreator`, the return value will also be a single function. + */ +export function bindActionCreators>(actionCreator: A, dispatch: Dispatch): A; export function bindActionCreators< A extends ActionCreator, B extends ActionCreator ->(actionCreator: A, dispatch: Dispatch): B; + >(actionCreator: A, dispatch: Dispatch): B; -export function bindActionCreators( - actionCreators: M, dispatch: Dispatch -): M; +export function bindActionCreators(actionCreators: M, dispatch: Dispatch): M; export function bindActionCreators< M extends ActionCreatorsMapObject, N extends ActionCreatorsMapObject ->(actionCreators: M, dispatch: Dispatch): N; + >(actionCreators: M, dispatch: Dispatch): N; /* compose */ -// copied from DefinitelyTyped/compose-function -// Hardcoded signatures for 2-4 parameters -export function compose(f1: (b: B) => C, - f2: (a: A) => B): (a: A) => C; -export function compose(f1: (b: C) => D, - f2: (a: B) => C, - f3: (a: A) => B): (a: A) => D; -export function compose(f1: (b: D) => E, - f2: (a: C) => D, - f3: (a: B) => C, - f4: (a: A) => B): (a: A) => E; - -// Minimal typing for more than 4 parameters -export function compose(f1: (a: any) => R, - ...functions: Function[]): (a: I) => R; +/** + * Composes single-argument functions from right to left. The rightmost + * function can take multiple arguments as it provides the signature for the + * resulting composite function. + * + * @param funcs The functions to compose. + * @returns R function obtained by composing the argument functions from right + * to left. For example, `compose(f, g, h)` is identical to doing + * `(...args) => f(g(h(...args)))`. + */ +export function compose(): (a: R, ...args: any[]) => R; + +export function compose( + f1: (b: A) => R, + f2: (...args: any[]) => A +): (...args: any[]) => R; + +export function compose( + f1: (b: B) => R, + f2: (a: A) => B, + f3: (...args: any[]) => A +): (...args: any[]) => R; + +export function compose( + f1: (b: C) => R, + f2: (a: B) => C, + f3: (a: A) => B, + f4: (...args: any[]) => A +): (...args: any[]) => R; + +export function compose( + f1: (a: any) => R, + ...funcs: Function[] +): (...args: any[]) => R; diff --git a/test/typescript/actionCreators.ts b/test/typescript/actionCreators.ts index 4458f19176..77daa9d739 100644 --- a/test/typescript/actionCreators.ts +++ b/test/typescript/actionCreators.ts @@ -4,7 +4,7 @@ import { } from "../../index.d.ts"; -interface AddTodoAction extends Action { +interface AddTodoAction extends Action { text: string; } diff --git a/test/typescript/actions.ts b/test/typescript/actions.ts index 7dab14abd8..1a0bb29d03 100644 --- a/test/typescript/actions.ts +++ b/test/typescript/actions.ts @@ -2,7 +2,7 @@ import {Action as ReduxAction} from "../../index.d.ts"; namespace FSA { - interface Action

extends ReduxAction { + interface Action

extends ReduxAction { payload: P; } @@ -16,7 +16,7 @@ namespace FSA { namespace FreeShapeAction { - interface Action extends ReduxAction { + interface Action extends ReduxAction { [key: string]: any; } @@ -32,7 +32,11 @@ namespace FreeShapeAction { namespace StringLiteralTypeAction { type ActionType = 'A' | 'B' | 'C'; - const action: ReduxAction = { + interface Action extends ReduxAction { + type: ActionType; + } + + const action: Action = { type: 'A' } @@ -45,7 +49,11 @@ namespace EnumTypeAction { A, B, C } - const action: ReduxAction = { + interface Action extends ReduxAction { + type: ActionType; + } + + const action: Action = { type: ActionType.A } diff --git a/test/typescript/compose.ts b/test/typescript/compose.ts index 2392fe151b..5ee9328eb9 100644 --- a/test/typescript/compose.ts +++ b/test/typescript/compose.ts @@ -18,5 +18,5 @@ const t4: (a: string) => number = compose( const t5: number = compose(stringToNumber, numberToString, numberToNumber)(5); const t6: string = compose(numberToString, stringToNumber, numberToString, numberToNumber)(5); -const t7: string = compose( +const t7: string = compose( numberToString, numberToNumber, stringToNumber, numberToString, stringToNumber)("fo"); diff --git a/test/typescript/dispatch.ts b/test/typescript/dispatch.ts index 2dac9bb21f..b589592f1b 100644 --- a/test/typescript/dispatch.ts +++ b/test/typescript/dispatch.ts @@ -4,9 +4,9 @@ import {Dispatch, Action} from "../../index.d.ts"; declare const dispatch: Dispatch; -const dispatchResult: Action = dispatch({type: 'TYPE'}); +const dispatchResult: Action = dispatch({type: 'TYPE'}); type Thunk = () => O; -const dispatchThunkResult: number = dispatch, number>(() => 42); +const dispatchThunkResult: number = dispatch(() => 42); diff --git a/test/typescript/middleware.ts b/test/typescript/middleware.ts index 0077e3f20e..d5738dc844 100644 --- a/test/typescript/middleware.ts +++ b/test/typescript/middleware.ts @@ -6,30 +6,30 @@ import { type Thunk = (dispatch: Dispatch, getState?: () => S) => O; - const thunkMiddleware: Middleware = ({dispatch, getState}: MiddlewareAPI) => (next: Dispatch) => (action: A | Thunk): B => typeof action === 'function' ? (>action)(dispatch, getState) : - next(action) + next(action) -const loggerMiddleware: Middleware = ({getState}: MiddlewareAPI) => - (next: Dispatch) => - (action: A): B => { - console.log('will dispatch', action) +const loggerMiddleware: Middleware = + ({getState}: MiddlewareAPI) => + (next: Dispatch) => + (action: any): any => { + console.log('will dispatch', action) - // Call the next dispatch method in the middleware chain. - const returnValue: B = next(action) + // Call the next dispatch method in the middleware chain. + const returnValue = next(action) - console.log('state after dispatch', getState()) + console.log('state after dispatch', getState()) - // This will likely be the action itself, unless - // a middleware further in chain changed it. - return returnValue - } + // This will likely be the action itself, unless + // a middleware further in chain changed it. + return returnValue + } @@ -37,7 +37,7 @@ type State = { todos: string[]; } -const reducer: Reducer = (state: State, action: Action): State => { +const reducer: Reducer = (state: State, action: Action): State => { return state; } @@ -46,7 +46,7 @@ const storeWithThunkMiddleware = createStore( applyMiddleware(thunkMiddleware) ); -storeWithThunkMiddleware.dispatch, void>( +storeWithThunkMiddleware.dispatch( (dispatch: Dispatch, getState: () => State) => { const todos: string[] = getState().todos; dispatch({type: 'ADD_TODO'}) diff --git a/test/typescript/reducers.ts b/test/typescript/reducers.ts index 1fd5a0595d..215b872349 100644 --- a/test/typescript/reducers.ts +++ b/test/typescript/reducers.ts @@ -6,13 +6,13 @@ import { type TodosState = string[]; -interface AddTodoAction extends Action { +interface AddTodoAction extends Action { text: string; } const todosReducer: Reducer = (state: TodosState, - action: Action): TodosState => { + action: Action): TodosState => { switch (action.type) { case 'ADD_TODO': return [...state, (action).text] @@ -30,7 +30,9 @@ const todosState: TodosState = todosReducer([], { type CounterState = number; -const counterReducer: Reducer = (state: CounterState, action: Action): CounterState => { +const counterReducer: Reducer = ( + state: CounterState, action: Action +): CounterState => { switch (action.type) { case 'INCREMENT': return state + 1 @@ -55,16 +57,3 @@ const rootState: RootState = rootReducer(undefined, { type: 'ADD_TODO', text: 'test', }) - - -interface RootReducers extends ReducersMapObject { - todos: Reducer; - counter: Reducer; -} - - -const rootReducerStrict: Reducer = - combineReducers({ - todos: todosReducer, - counter: counterReducer, - }) diff --git a/test/typescript/store.ts b/test/typescript/store.ts index 75b979d34c..bfb758671b 100644 --- a/test/typescript/store.ts +++ b/test/typescript/store.ts @@ -8,7 +8,7 @@ type State = { todos: string[]; } -const reducer: Reducer = (state: State, action: Action): State => { +const reducer: Reducer = (state: State, action: Action): State => { return state; } @@ -54,8 +54,7 @@ unsubscribe(); /* replaceReducer */ -const newReducer: Reducer = (state: State, - action: Action): State => { +const newReducer: Reducer = (state: State, action: Action): State => { return state; }