Use react-redux connect in typescript

2019-04-01 17:26发布

问题:

I try to build a react app in typescript using redux and react-router-dom. I ran into typing issues when I added redux to my app. Thus I created the following minimal example with only one page test-page:

App.jsx

import * as React from 'react';
import { Route, Redirect } from 'react-router-dom'
import Test from './containers/test-page'
import './App.css';

class App extends React.Component {
  render() {
    return (
      <div className="ui container" id="main">
        <Route exact path="/" render={() => <Redirect to="/test" />}/>
        <Route exact path="/test" component={Test} />
      </div>
    );
  }
}

export default App;

The container for the test page looks like this. It produces a typing error in the call to connect.

containers/test-page/index.tsx

import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import TestPage from './test-page'

function mapDispatchToProps(dispatch: Dispatch<any>) {
  return dispatch({ type: 'ALERT_USER' });
}

function mapStateToProps(state: any) {
  return { label: 'my test label' }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TestPage)

The container uses the following react component, which in production should render a page for the router. It produces two errors, see below.

containers/test-page/test-page.tsx

import * as React from 'react';

export namespace Test {
  export interface Props {
    alert: () => void;
    label: string;
  }

  export interface State {
  }
}

export default class TestPage extends React.Component {

  constructor(props?: Test.Props, state?: Test.State, context?: any) {
    super(props, context);
  }

  sendAlert = () => {
      this.props.alert()
  }

  render() {
    return (
      <div>
        <h1>Test</h1>
        <button onClick={this.sendAlert}>{this.props.label}</button>
      </div>
    );
  }
}

Error messages:

proxyConsole.js:54 ./src/containers/test-page/test-page.tsx
(20,18): error TS2339: Property 'alert' does not exist on type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'.

proxyConsole.js:54 ./src/containers/test-page/test-page.tsx
(27,54): error TS2339: Property 'label' does not exist on type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'.

proxyConsole.js:54 ./src/containers/test-page/index.tsx
(16,3): error TS2345: Argument of type 'typeof TestPage' is not assignable to parameter of type 'ComponentType<{ label: string; } & { type: string; }>'.
  Type 'typeof TestPage' is not assignable to type 'StatelessComponent<{ label: string; } & { type: string; }>'.
    Type 'typeof TestPage' provides no match for the signature '(props: { label: string; } & { type: string; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

I tried to follow different guides and looked up example implementations but could not solve these issues. I do not understand the error messages of the typescript compiler:

  • Why do my properties not exist on this.props when I defined them?
  • What exactly is not assignable in connect?

回答1:

A couple of things I notice:

1) As far as I've seen in examples and when working with props in TypeScript, your call to React.Component needs to specify Props as a type argument like so:

export default class TestPage extends React.Component<Test.Props, Test.State>{

    constructor(props: Test.Props) {
        super(props);
    }

}

You can specify that your component does not accept props or state by passing empty interfaces i.e.:

export default class TestPage extends React.Component<{}, {}>{

    // constructor becomes 'useless' at this point and is not needed
    constructor() {
        super();
    }

}

I think this explains why your call signature is not matching and why there are no properties visible on this.props - TS sees an interface of ReadOnly{} since it has no type arguments passed in.

2) Your mapStateToProps function doesn't look quite right. mapStateToProps takes two arguments, state (referencing your Redux store) and ownProps as an optional second argument, which refers to props passed down from the parent. So mapStateToProps should look like this:

function mapStateToProps(state: any, ownProps: { label: string }) {

    return {
        label: ownProps.label
    };
}

This is my guess for why connect is throwing an error - it is simply a place where you make assertions about how Redux should handle combining props coming from the store and props coming from the parent. Let me know if this works out.



回答2:

This doesn't work because connect is a generic function. This means you need to provide additional type parameters.

connect<StateProps, DispatchProps>({
    mapStateToProps,
    mapDispatchToProps,
})(SomeComponent);

You can find typing implementation here. Everything you need to know is there C:



回答3:

Working syntax variant for Type Script application is:

  import * as React from 'react';
  import { connect } from 'react-redux';
  import { withRouter } from 'react-router-dom';

  class MyComponentName extends React.Component<any, any> {
      constructor(props: any) {
          super(props);
      }
  }

  export default withRouter(
    connect(
        mapStateToProps
      )(MyComponentName) as any
  );

or:

    export default withRouter(
      connect<any, any>(
        mapStateToProps
      )(MyComponentName) as any
  );