reactjs - Cum pot restricționa tipul copiilor React în TypeScript, folosind suportul nou adăugat în TypeScript 2.3?


Încerc să profit de suportul recent adăugat pentru tipizarea copiilor în compilatorul TypeScript și în @types/react, dar întâmpin dificultăți. Folosesc versiunea 2.3.4 de TypeScript.

Spune că am cod ca acesta:

interface TabbedViewProps {children?: Tab[]}
export class TabbedView extends React.Component<TabbedViewProps, undefined> {

  render(): JSX.Element {
    return <div>TabbedView</div>;
  }
}

interface TabProps {name: string}
export class Tab extends React.Component<TabProps, undefined> {
  render(): JSX.Element {
    return <div>Tab</div>
  }
}

Când încerc să folosesc aceste componente în felul următor:

return <TabbedView>
  <Tab name="Creatures">
    <div>Creatures!</div>
  </Tab>
  <Tab name="Combat">
    <div>Combat!</div>
  </Tab>
</TabbedView>;

Primeșc o eroare după cum urmează:

ERROR in ./src/typescript/PlayerView.tsx
(27,12): error TS2322: Type '{ children: Element[]; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<TabbedView> & Readonly<{ children?: ReactNode; }> ...'.
  Type '{ children: Element[]; }' is not assignable to type 'Readonly<TabbedViewProps>'.
    Types of property 'children' are incompatible.
      Type 'Element[]' is not assignable to type 'Tab[] | undefined'.
        Type 'Element[]' is not assignable to type 'Tab[]'.
          Type 'Element' is not assignable to type 'Tab'.
            Property 'render' is missing in type 'Element'.

Se pare că deduce tipul copiilor doar ca Element[] în loc de Tab[], chiar dacă acesta este singurul tip de copii pe care îl folosesc.

EDIT: Ar fi în regulă să restricționezi și interfața props-urilor copiilor în loc să restricționezi direct tipul componentelor copil, deoarece tot ce trebuie să fac este să extrag anumite props specifice din componentele copil.

Răspuns #1

Edit 2: Se pare că această abordare previne avertizarea, dar conform comentariilor, TabProps nu sunt verificate corespunzător.

Ar trebui să încerci să setezi copii ai interfeței TabbedViewProps astfel

interface TabbedViewProps { children?: React.ReactElement<TabProps>[] }

Ideea aici nu este să îi spui TabbedView că are un array de Tab, ci mai degrabă să îi spui TabbedView că are un array de element care primește props specifice. În cazul tău TabProps.

Editare (mulțumiri lui Matei):

interface TabbedViewProps {
    children?: React.ReactElement<TabProps>[] | React.ReactElement<TabProps>
}

Răspunsul #2

Așa cum s-a menționat deja, declararea TabbedView.children ca:

children: React.ReactElement<TabProps> | React.ReactElement<TabProps>[];

Va scăpa de eroare, dar nu va verifica corect tipurile copiilor. Adică, vei putea în continuare să transmiți copii alții decât TabProps către TabbedView fără a primi vreo eroare, deci și aceasta ar fi validă:

return (
  <TabbedView>
    <Tab name="Creatures">
      <div>Creatures!</div>
    </Tab>

    <Tab name="Combat">
        <div>Combat!</div>
    </Tab>

    <NotTabButValidToo />
  </TabbedView>
);

Ceea ce ai putea face în schimb este să declari o prop, să zicem tabs: TabProps[], pentru a transmite mai departe proprietățile de care ai nevoie pentru a crea acele Tab-uri, mai degrabă decât JSX-ul lor, și să le redai în interiorul TabbedView:

interface TabbedViewProps {
  children?: never;
  tabs?: TabProps[];
}

...

const TabbedView: React.FC<TabbedViewProps> = ({ tabs }) => {
  return (
    ...

    { tabs.map(tab => <Tab key={ ... } { ...tab } />) }

    ...
  );
};