diff --git a/.travis.yml b/.travis.yml index 8d76e3004..e9c9205a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,6 @@ before_install: script: - npm run lint - npm run test:cov + - npm run test:typescript after_success: - npm run coverage diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000..886cc1d75 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,67 @@ +import { ComponentClass, Component, StatelessComponent } from 'react'; +import { Store, Dispatch, ActionCreator, Action } from 'redux'; + +interface ComponentDecorator { + (component: StatelessComponent|ComponentClass): ComponentClass; +} + +/** + * Following 5 functions cover all possible ways connect could be invoked + * + * - State: Redux state interface (the same one used by Store) + * - TOwnProps: Props passed to the wrapping component + * - TStateProps: Result of MapStateToProps + * - TDispatchProps: Result of MapDispatchToProps + */ +export function connect(): ComponentDecorator<{ dispatch: Dispatch } & TOwnProps, TOwnProps>; + +export function connect( + mapStateToProps: FuncOrSelf>, +): ComponentDecorator } & TOwnProps, TOwnProps>; + +export function connect( + mapStateToProps: FuncOrSelf> | null, + mapDispatchToProps: FuncOrSelf | MapDispatchToPropsObject & TDispatchProps> +): ComponentDecorator; + +export function connect( + mapStateToProps: FuncOrSelf> | null, + mapDispatchToProps: FuncOrSelf | MapDispatchToPropsObject & TDispatchProps>|null, + mergeProps: MergeProps | null, + options?: Options +): ComponentDecorator; + +interface MapDispatchToPropsObject { + [name: string]: ActionCreator | ThunkedActionCreator; +} + +type ThunkedActionCreator = (...args: any[]) => (dispatch: Dispatch) => void + +interface MapStateToProps { + (state: State, ownProps: TOwnProps): TStateProps; +} + +interface MapDispatchToPropsFunction { + (dispatch: Dispatch, ownProps: TOwnProps): TDispatchProps; +} + +interface MergeProps { + (stateProps: TStateProps, dispatchProps: TDispatchProps, ownProps: TOwnProps): TMergeProps; +} + +interface Options { + pure?: boolean; + withRef?: boolean; +} + +type FuncOrSelf = T | (() => T); + +/** + * Typescript does not support generic components in tsx yet in an intuïtive way which is the reason we avoid a + * generic parameter in Store here by using any as the type + */ +export interface ProviderProps { + store: Store; +} + +export class Provider extends Component { } diff --git a/package.json b/package.json index d02f57e74..517c12bb9 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "./lib/index.js", "module": "es/index.js", "jsnext:main": "es/index.js", + "typings": "./index.d.ts", "scripts": { "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", @@ -17,6 +18,7 @@ "test": "cross-env BABEL_ENV=commonjs NODE_ENV=test mocha --compilers js:babel-register --recursive --require ./test/setup.js", "test:watch": "npm test -- --watch", "test:cov": "cross-env NODE_ENV=test nyc npm test", + "test:typescript": "typings-tester --dir test/typescript", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov" }, "repository": { @@ -27,7 +29,8 @@ "dist", "lib", "src", - "es" + "es", + "index.d.ts" ], "keywords": [ "react", @@ -47,6 +50,7 @@ }, "homepage": "https://github.com/gaearon/react-redux", "devDependencies": { + "@types/react": "^16.0.18", "babel-cli": "^6.3.17", "babel-core": "^6.3.26", "babel-eslint": "^7.1.1", @@ -98,7 +102,9 @@ "rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-replace": "^1.1.1", - "rollup-plugin-uglify": "^2.0.1" + "rollup-plugin-uglify": "^2.0.1", + "typescript": "^2.0.10", + "typings-tester": "^0.2.0" }, "dependencies": { "hoist-non-react-statics": "^2.2.1", diff --git a/test/typescript/test.tsx b/test/typescript/test.tsx new file mode 100644 index 000000000..df9179db6 --- /dev/null +++ b/test/typescript/test.tsx @@ -0,0 +1,358 @@ +import * as React from 'react' +import {Dispatch, Store} from "redux"; +import {connect, Provider} from "../../index"; + + +function testNoArgs() { + const Connected = connect()(props => { + // typings:expect-error + props.foo; + + return