javascript - Bruke punktnotasjon med funksjonell komponent i TypeScript


Den offisielle ReactJs-dokumentasjonen anbefaler å lage komponenter ved å følge punktnotasjon, som React-bootstrap-biblioteket:

<Card>
  <Card.Body>
    <Card.Title>Card Title</Card.Title>
    <Card.Text>
      Some quick example text to build on the card title and make up the bulk of
      the card's content.
    </Card.Text>
  </Card.Body>
</Card>

Takket være dette spørsmålet vet jeg at jeg kan lage denne strukturen ved å bruke funksjonelle komponenter akkurat slik i JavaScript:

const Card = ({ children }) => <>{children}</>
const Body = () => <>Body</>

Card.Body = Body

export default Card

Ved å bruke TypeScript bestemte jeg meg for å legge til de tilsvarende typene til det:

const Card: React.FunctionComponent = ({ children }): JSX.Element => <>{children}</>
const Body: React.FunctionComponent = (): JSX.Element => <>Body</>

Card.Body = Body  // <- Error: Property 'Body' does not exist on type 'FunctionComponent<{}>'

export default Card

Problemet nå er at TypeScript ikke tillater tildelingen Card.Body=Body og gir meg feilen:

Egenskapen 'Body' eksisterer ikke på typen 'FunctionComponent<{}>'

Så hvordan kan jeg skrive dette riktig for å bruke denne kode-strukturen?

Svar #1

const Card: React.FunctionComponent & { Body: React.FunctionComponent } = ({ children }): JSX.Element => <>{children}</>
const Body: React.FunctionComponent = (): JSX.Element => <>Body</>

Card.Body = Body;

Eller mer lesbart:

type BodyComponent = React.FunctionComponent;
type CardComponent = React.FunctionComponent & { Body: BodyComponent };

const Card: CardComponent = ({ children }): JSX.Element => <>{children}</>;
const Body: BodyComponent = (): JSX.Element => <>Body</>;

Card.Body = Body;

Svar #2

Jeg fant en smart måte å bruke Object.assign på for å få punktnotasjon til å fungere med ts. Det var brukstilfeller som lignet på

type TableCompositionType = {
    Head: TableHeadComponentType;
    Body: TableBodyComponentType;
    Row: TableRowComponentType;
    Column: TableColumnComponentType;
};
type TableType = TableComponentType & TableCompositionType;


export const Table: TableType = TableComponent;
Table.Head = TableHeadComponent;
Table.Body = TableBodyComponent;
Table.Row = TableRowComponent;
Table.Column = TableColumnComponent;

der ts ville kaste feil. Min grunnleggende fungerende løsning var:

export const Table: TableType = Object.assign(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});

Den eneste ulempen er at mens resultatet vil bli typekontrollert, vil ikke de enkelte underkomponentene inne i objektparameteren bli det, noe som kan være nyttig for feilsøking.

En god praksis ville være å definere (og typekontrollere) parameteren på forhånd.

const tableComposition: TableCompositionType = {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
};

export const Table: TableType = Object.assign(TableComponent, tableComposition);

Men siden Object.assign er generisk, er dette også gyldig:

export const Table = Object.assign<TableComponentType, TableCompositionType>(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});

Selvfølgelig, hvis du ikke trenger å (eller ønsker å) spesifikt angi typen på forhånd, kan du også gjøre det, og den vil bare bli utledet. Ingen nasty hacks er nødvendig.

export const Table = Object.assign(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});

Relaterte artikler