Сильно набрав соединение react-redux с машинописным текстом

Я получаю ошибку, пытаясь ввести параметры для моего компонента реакции. Я хотел бы, чтобы строгий тип, какие свойства будут на реквизитах и состоянии компонента, но когда я делаю это с помощью Redux, я получаю сообщение об ошибке при передаче mapStateToProps функции соединения.

Это код компонента:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import FileExplorer from '../components/file-explorer/file-explorer';
import { ISideMenu, ISideMenuState } from '../models/interfaces/side-menu';

    class SideMenu extends Component<ISideMenu, ISideMenuState> {
        render() {
            return (
                <div>
                    {this.props.fileExplorerInfo !== null &&
                        <FileExplorer fileExplorerDirectory={this.props.fileExplorerInfo.fileExplorerDirectory}/>
                    }
                </div>
            );
        }
    }

    const mapStateToProps = (state: ISideMenuState) => {
        return {
            fileExplorerInfo: state.fileExplorer
        };
    };

    export default connect<ISideMenu, null, ISideMenuState>(mapStateToProps)(SideMenu);

Таким образом, ошибка возникает в этой строке:

export default connect<ISideMenu, null, ISideMenuState>(mapStateToProps)(SideMenu);

и когда я наводил над словом "mapStateToProps" в этой строке, я вижу ошибку:

Argument of type '(state: ISideMenuState) => { fileExplorerInfo: FileDirectoryTree | null; }'
is not assignable to parameter of type 'MapStateToPropsParam<ISideMenu, ISideMenuState, {}>'.
  Type '(state: ISideMenuState) => { fileExplorerInfo: FileDirectoryTree | null; }' is not
assignable to type 'MapStateToProps<ISideMenu, ISideMenuState, {}>'.
    Types of parameters 'state' and 'state' are incompatible.
      Type '{}' is not
assignable to type 'ISideMenuState'.
        Property 'fileExplorer' is missing in type '{}'.

И это два интерфейса, которые я использую в реагирующем компоненте:

export interface ISideMenu {
    fileExplorerInfo: FileExplorerReducerState | null;
}

export interface ISideMenuState {
    fileExplorer: FileDirectoryTree | null;
}

Любое понимание этой ошибки будет принята с благодарностью!

Ответ 1

При использовании дженериков вы неправильно используете место интерфейса:

Когда вы объявляете свой компонент React:

class Comp extends Component<ICompProps, ICompState>

С ICompProps и ICompState - ваши компонентные реквизиты и внутреннее состояние соответственно.

Когда вы используете соединение:

connect<IMapStateToProps, IMapDispatchToProps, ICompProps, IReduxState>

IMapStateToProps представляет то, что возвращается mapStateToProps(). IMapDispatchToProps представляет то, что возвращается mapDispatchToProps(). ICompProps представляет ваши реквизиты компонента React (такие же, как указано выше). IReduxState представляет ваше App Redux State

Итак, в вашем конкретном примере:

При объявлении компонента React:

class SideMenu extends Component<ISideMenu, {}>

Используйте ISideMenu для реквизита и {} (пустое состояние) для состояния, так как вы не используете какое-либо состояние.

При использовании connect:

connect<ISideMenu, {}, ISideMenu, ISideMenuState>(mapStateToProps)(SideMenu);

Вы можете использовать ISideMenu как реквизит компонента React, так и объект, возвращаемый mapStateToProps. Но на практике было бы лучше создать два отдельных интерфейса.

В моих приложениях я, как правило, не должен беспокоиться о вводе mapStateToProps возврата mapStateToProps поэтому я просто использую:

connect<{}, {}, ISideMenu, ISideMenuState>(mapStateToProps)(SideMenu);

Ответ 2

Надеюсь, вы не возражаете, если я удалю некоторые антипаттерны из приведенного выше кода. Пожалуйста, проверьте комментарии, которые я добавил. Также я добавил с помощью Router, чтобы проиллюстрировать шаблон лучше

import * as React from "react";
import { bindActionCreators } from "redux";
import { withRouter, RouteComponentProps } from "react-router";
import { connect } from "react-redux";
import { compose } from "recompose";

// OUR ROOT REDUX STORE STATE TYPE
import { State } from "../redux"; 

import FileExplorer from "../components/file-explorer/file-explorer";

// interfaces starting with 'I' is an antipattern and really
// rarely needed to be in a separate file

// OwnProps - that what external code knows about out container
type OwnProps = {};

// this comes from redux
type StateProps = {
  fileExplorer: FileDirectoryTree | null;
};

// resulting props - that what container expects to have
type Props = OwnProps & StateProps & RouteComponentProps<any>;

// no need to have a class, SFC will do the same job
const SideMenu: React.SFC<Props> = props => {
  return (
    <div>
      {this.props.fileExplorerInfo !== null && (
        <FileExplorer
          fileExplorerDirectory={
            this.props.fileExplorerInfo.fileExplorerDirectory
          }
        />
      )}
    </div>
  );
};

// compose (from recompose lib) because usually we have more than 1 hoc
// say let add withRouter just for fun



export default compose<Props, OwnProps>(

  withRouter,

  // it important to read the typings:
  // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts
  connect<StateProps, {}, {}, State>(s => ({
    fileExplorerInfo: s.fileExplorer
  })),

)(SideMenu);