在 React 中使用 Typescript 使组件可重用

Make Component Reusable with Typescript in React

构建一个 React 应用程序,我想让我的代码尽可能干。我在这个项目中第一次开始使用 Typescript,我面临着组件的可重用性问题,其中 JSX 对于不同的情况可能是相同的,但类型和接口会发生变化。

这是一个真实的例子:下面的代码是一个使用 TypeScript、Apollo / GraphQL 和 Redux 构建的 React 组件。它 returns 我数据库中所有足球队的 table,从后端 API 获取。

问题是,我想使用同一个组件来显示 table 个游戏、table 个玩家等。

例如,我被 Team 界面卡住了。每队只有一个id和一个name;但每个游戏都有一个 date、一个 homeTeam 和一个 awayTeam, ascore` 等

因此我如何管理我的接口和类型以便我可以重用这个组件?

import React from 'react';
import { connect } from 'react-redux';
import { setView, toggleDeleteElemModal } from '../../redux/actions';

import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import { ApolloError } from 'apollo-client';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faInfo, faEdit, faTrash } from '@fortawesome/free-solid-svg-icons';

interface SectionTableProps {
  setView: typeof setView
  toggleDeleteElemModal : typeof toggleDeleteElemModal
}

interface Team {
  id: string;
  name: string;
}

interface Data {
  allTeams?: Team[];
}

interface SectionTableQueryProps {
    allTeams: Team[];
    error?: ApolloError;
    loading: boolean;
}

const GET_ALL_TEAMS = gql`
  query {
    allTeams {
      id
      name
    }
  }
`;

class SectionTableQuery extends Query<Data, {}> {}

const SectionTable = (props: SectionTableProps) => (
  <table className="table table-hover table-responsive">
    <thead>
      <tr>
        <th scope="col">ID</th>
        <th scope="col">Name</th>
        <th scope="col">Actions</th>
      </tr>
    </thead>
      <SectionTableQuery query={GET_ALL_TEAMS}>
      {({ data: { allTeams = [] } = {}, error, loading }) => {
        if (loading) {
          return <tbody><tr><td>LOADING</td></tr></tbody>
        };
        if (error !== undefined) {
          return <tbody><tr><td>ERROR</td></tr></tbody>
        };
        return (
          <tbody>
            {allTeams.map((team: Team) => (
                <tr key={team.id}>
                  <th scope="row">{team.id}</th>
                  <td>{team.name}</td>
                  <td className="d-flex justify-content-between">
                  <div onClick={() => props.setView("info")}>
                    <FontAwesomeIcon icon={faInfo} />
                  </div>
                  <div onClick={() => props.setView("edit")}>
                    <FontAwesomeIcon icon={faEdit} />
                  </div>
                  <div onClick={() => props.toggleDeleteElemModal()}>
                    <FontAwesomeIcon icon={faTrash} />
                  </div>
                  </td>
                </tr>

            ))}
          </tbody>
        );
      }}
    </SectionTableQuery>
  </table>
)

const mapDispatchToProps = { setView, toggleDeleteElemModal }

export default connect(null, mapDispatchToProps)(SectionTable);

您可以为此行为使用 JSX 组件的通用类型。

class SectionTableQuery extends Query<P, {}> {} // where P is the generic prop

SectionTable 声明必须从它的父级接受这个通用的 P 接口,并在渲染时 SectionTableQuery:

const SectionTable<P> = (props: SectionTableProps) => (
  <SectionTableQuery<P> query={props.query}>  // Assuming you want the query to change, that would be passed as a prop too
  ...
  </SectionTableQuery>
);

在渲染SectionTable时,家长可以选择传入he/she想要的道具

<SectionTable<ITeam> ... />
<SectionTable<IPlayer> ... />

这是对您必须执行的操作的一般近似,看起来可能有不止一种泛型类型要传入,具体取决于查询(IData 对于从查询返回的内容IItem 用于项目本身`)

PS:通用组件可从 Typescript 2.9

获得