简体   繁体   中英

React Component and Typescript - Generic Props

I am currently facing a problem using React and Typescript. I have a generic component that I use to build specific tables like this:

export const SpecificTable = () => {
  return
  <GenericTable 
    object1={{ ... }}
    object2={{ ... }}
    object3={{ ... }}
    object4={{ ... }}
  />;
};

The generic component and its props look like this:

export type GenericProps<GenericType1, GenericType2, GenericType3, GenericType4> {
  object1: { ... };
  object2: { ... };
  object3: { ... };
  object4: { ... };
}
  
export const GenericTable = <
  GenericType1 extends ...,
  GenericType2 extends ...,
  GenericType3 extends ...,
  GenericType4 extends ...
>(
  props: GenericProps<GenericType1, GenericType2, GenericType3, GenericType4>
) => {
  // build the table
}

The problem here is that whenever I need to use this generic component to render a table, the generic types are going to change for each different table. The GenericTable types should look something like this, I suppose:

export const GenericTable = <
  GenericType1 extends SpecificType1,
  GenericType2 extends SpecificType2,
  GenericType3 extends SpecificType3,
  GenericType4 extends SpecificType4
>(
  props: GenericProps<GenericType1, GenericType2, GenericType3, GenericType4>
) => {
  // build the table
}

However, since I can not hard code the specific types there, what should I do? How can I pass in these specific types as props to the generic component?

From your description, it may not be necessary for GenericTable to be generic, but it may need to be.

Here's what I do:

  1. Define just the minimum information that GenericTable needs to do its work for each of those props.

  2. Define a type alias ( type =... or interface {... } ) for those minimum types (optional, but convenient).

  3. Determine whether GenericTable needs to be generic. If it just needs to accept the props you've listed, it doesn't, because you can just use the minimum-necessary types from above. But sometimes, a component needs to call a callback you pass to it and pass back something it receives, and in that case if you want the callback strongly typed, you may want to make GenericTable generic.

    Note that the decision may not be the same for all of the props. It's entirely possible one or two of them need to be generic but the others don't.

So for instance:

type MinType1 = /*...*/;
type MinType2 = /*...*/;
type MinType3 = /*...*/;
type MinType4 = /*...*/;

Then, if GenericTable doens't have to be generic:

type GenericTableProps = {
    object1: MinType1,
    object2: MinType2,
    object3: MinType3,
    object4: MinType4,
};
const GenericTable = ({object1, object2, object3, object4}: GenericTableProps) => {
    // ...
};

Or if it does:

type GenericTableProps<
    Type1 extends MinType1,
    Type2 extends MinType2,
    Type3 extends MinType3,
    Type4 extends MinType4,
> = {
    object1: Type1,
    object2: Type2,
    object3: Type3,
    object4: Type4,
};
const GenericTable = <
    T1 extends MinType1,
    T2 extends MinType2,
    T3 extends MinType3,
    T4 extends MinType4,
> = ({object1, object2, object3, object4}: GenericTableProps<T1, T2, T3, T4>) => {
    // ...
};

The key thing is #1 above: defining the minimum information GenericTable needs to do its job, then using that as the constraint for the relevant props.

Here's an example with just one type parameter which is used by two out of the three props on the component:

import React from "react";

type GenericTableRowType = {
    id: string | number;
    text: string;
};
type GenericTableProps<RowType extends GenericTableRowType> = {
    label: string;
    rows: RowType[];
    onRowClick: (row: RowType) => void;
};

const GenericTable = <RowType extends GenericTableRowType>({label, rows, onRowClick}: GenericTableProps<RowType>) => {
    return <table>
        <thead>
            <tr>
                <th>{label}</th>
            </tr>
        </thead>
        <tbody>
            {rows.map((row) => <tr key={row.id} onClick={() => onRowClick(row)}>
                <td>{row.text}</td>
            </tr>)}
        </tbody>
    </table>
};

// Example use:
type MyRowType = {
    id: number;
    text: string;
    flag: boolean;
}
const Example = () => {
    const rows: MyRowType[] = [
        {id: 1, text: "one",   flag: true},
        {id: 2, text: "two",   flag: false},
        {id: 3, text: "three", flag: false},
    ];
    const onRowClick = (row: MyRowType) => {
        console.log(`Row ${row.id}, flag = ${row.flag}`);
    }
    return <div>
        <GenericTable label="Example" rows={rows} onRowClick={onRowClick} />
    </div>;
};

Playground link , and here's a live example with the types commented out:

 /* type GenericTableRowType = { id: string | number; text: string; }; type GenericTableProps<RowType extends GenericTableRowType> = { label: string; rows: RowType[]; onRowClick: (row: RowType) => void; }; */ const GenericTable = /*<RowType extends GenericTableRowType>*/({label, rows, onRowClick}/*: GenericTableProps<RowType>*/) => { return <table> <thead> <tr> <th>{label}</th> </tr> </thead> <tbody> {rows.map((row) => <tr key={row.id} onClick={() => onRowClick(row)}> <td>{row.text}</td> </tr>)} </tbody> </table> }; // Example use: /* type MyRowType = { id: number; text: string; flag: boolean; } */ const Example = () => { const rows/*: MyRowType[]*/ = [ {id: 1, text: "one", flag: true}, {id: 2, text: "two", flag: false}, {id: 3, text: "three", flag: false}, ]; const onRowClick = (row/*: MyRowType*/) => { console.log(`Row ${row.id}, flag = ${row.flag}`); } return <div> <GenericTable label="Example" rows={rows} onRowClick={onRowClick} /> </div>; }; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<Example />);
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Obviously, that's a silly component (a table with one column), but it serves to demonstrate the concepts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM