TypeScript i React - dzieci piszą?

138

Mam bardzo prosty element funkcjonalny, jak następuje:

import * as React from 'react';

export interface AuxProps  { 
    children: React.ReactNode
 }


const aux = (props: AuxProps) => props.children;

export default aux;

I kolejny komponent:

import * as React from "react";

export interface LayoutProps  { 
   children: React.ReactNode
}

const layout = (props: LayoutProps) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {props.children}
        </main>
    <Aux/>
);

export default layout;

Ciągle otrzymuję następujący błąd:

[ts] Typ elementu JSX „ReactNode” nie jest funkcją konstruktora dla elementów JSX. Typu „undefined” nie można przypisać do typu „ElementClass”. [2605]

Jak mam to poprawnie wpisać?

Asool
źródło

Odpowiedzi:

133

Właśnie children: React.ReactNode

Ridd
źródło
1
Oto poprawna odpowiedź! JSX.Elementnie jest wystarczająco dobry, ponieważ prawidłowe dzieci React mogą być ciągiem znaków, wartością logiczną, wartością zerową ... ReactChildjest również niekompletne z tych samych powodów
Pierre Ferry
2
@PierreFerry Problem polega na tym, że można przypisać prawie wszystkoReactNode . Nie pomaga to pod względem bezpieczeństwa typów, podobnie jak pisanie childrenjako any- zobacz moją odpowiedź po wyjaśnienie.
ford04
Dzięki za odpowiedź @ ford04. W moim przypadku chcę, aby dzieci były luźne. Mój komponent akceptuje wszystkie prawidłowe elementy potomne React. Więc uważam, że nadal jest to odpowiedź, której szukałem :)
Pierre Ferry
47

Aby użyć go <Aux>w JSX, musi to być funkcja, która zwraca ReactElement<any> | null. To jest definicja składnika funkcji .

Jednak obecnie jest zdefiniowana jako funkcja zwracająca React.ReactNode, która jest typem znacznie szerszym. Jak mówią typy React:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

Upewnij się, że niechciane typy są zneutralizowane, opakowując zwracaną wartość w React Fragment ( <></>):

const aux: React.FC<AuxProps> = props =>
  <>{props.children}</>;
Karol Majewski
źródło
Nie rozumiem, dlaczego trzeba zawijać childrenfragment reakcji. Chcesz wyjaśnić? W React jest to całkowicie ważne return children;.
sanfilippopablo
1
Nie, jeśli childrensą tablicą. W takim przypadku musisz zawinąć je w pojedynczy węzeł lub użyć fragmentów React.
Karol Majewski
Tak, w moim kodzie mam return React.Children.count(children) > 1 ? <>{children}</> : children:, ale maszynopis narzeka na to. Zastąpiłem go po prostu <>{children}</>.
sanfilippopablo
2
Narzeka, ponieważ childrenjest to lista elementów, która tak się składa, że ​​zawiera tylko 1 element. Myślę, że zadziałałoby, gdybyś to zrobił return React.Children.count(children) > 1 ? <>{children}</> : children[0]@sanfilippopablo
tamj0rd2
Wydaje się, że to nie działa w nowszych wersjach Reacta. Konsola jest zalogowana i mogę potwierdzić, że mam rekwizyt dziecięcy, ale nawet przy <React.Fragment>otoczeniu {props.children}nic nie zwracam.
Mark
34

Oto, co zadziałało dla mnie:

interface Props {
  children: JSX.Element[] | JSX.Element
}

Edytuj Polecam używać children: React.ReactNodezamiast tego teraz.

sunknudsen
źródło
8
To nie jest wystarczająco szeroka definicja. Dziecko może być sznurkiem.
Mike S
Przynajmniej ten przykład zadziałał dla mnie, ponieważ mój komponent miał oczekiwać 1 lub więcej elementów JSX.Element. Dzięki!
Italo Borges
1
To nie zadziała z wyrażeniami JavaScript jako dziećmi <MyComponent>{ condition ? <span>test</span> : null}</MyComponent>. Używanie children: ReactNodebędzie działać.
Nickofthyme
10

możesz zadeklarować swój komponent w ten sposób:

const MyComponent: React.FunctionComponent = (props) => {
    return props.children;
}
jsina
źródło
9

Możesz użyć ReactChildreni ReactChild:

import React, { ReactChildren, ReactChild } from 'react';

interface AuxProps {
  children: ReactChild | ReactChildren;
}

const Aux = ({ children }: AuxProps) => (<div>{children}</div>);

export default Aux;
Wilk
źródło
6

Z witryny TypeScript: https://github.com/Microsoft/TypeScript/issues/6471

Zalecaną praktyką jest zapisanie typu rekwizytów jako {dzieci ?: any}

To zadziałało dla mnie. Węzeł potomny może mieć wiele różnych cech, więc jawne wpisywanie może pomijać przypadki.

Dłuższa dyskusja na temat następującego problemu jest tutaj: https://github.com/Microsoft/TypeScript/issues/13618 , ale każde podejście nadal działa.

Mike S.
źródło
6

Typ zwracany składnika funkcji jest ograniczony do JSXElement | nullw TypeScript. Jest to aktualne ograniczenie typu, czysty React pozwala na więcej typów zwracanych.

Minimalny fragment demonstracyjny

Możesz użyć asercji typu lub fragmentów jako obejścia:

const Aux = (props: AuxProps) => <>props.children</>; 
const Aux2 = (props: AuxProps) => props.children as ReactElement; 

ReactNode

children: React.ReactNode może być nieoptymalny, jeśli celem jest posiadanie silnych typów Aux .

Prawie wszystko można przypisać do bieżącego ReactNodetypu, co jest równoważne {} | undefined | null. Bezpieczniejszym typem dla Twojego przypadku może być:

interface AuxProps {
  children: ReactElement | ReactElement[]
}

Przykład:

Biorąc pod uwagę Auxpotrzeby elementy React as children, przypadkowo dodaliśmy stringdo niego a. Wtedy powyższe rozwiązanie byłoby błędem w przeciwieństwie doReactNode - spójrz na połączone place zabaw.

Typy childrensą również przydatne w przypadku właściwości innych niż JSX, takich jak wywołanie zwrotne właściwości renderowania .

ford04
źródło
5

Możesz także użyć React.PropsWithChildren<P>.

type ComponentWithChildProps = React.PropsWithChildren<{example?: string}>;
Sibren
źródło
3

Ogólny sposób znaleźć każdy rodzaj jest dobry przykład. Piękno maszynopisu polega na tym, że masz dostęp do wszystkich typów, o ile masz poprawną@types/ pliki.

Aby odpowiedzieć sobie na to pytanie, pomyślałem, że komponent reaguje, który ma właściwości children. Pierwsza rzecz, która przyszła mi do głowy? Co powiesz na<div /> ?

Wszystko, co musisz zrobić, to otworzyć vscode i utworzyć nowy .tsxplik w projekcie React z @types/react.

import React from 'react';

export default () => (
  <div children={'test'} />
);

Najechanie kursorem na childrenrekwizyt pokazuje typ. A co wiesz - jego typ jest ReactNode(nie ma potrzeby ReactNode[]).

wprowadź opis obrazu tutaj

Następnie, jeśli klikniesz na definicję typu, przejdziesz bezpośrednio do definicji childrenpochodzącej z DOMAttributesinterfejsu.

// node_modules/@types/react/index.d.ts
interface DOMAttributes<T> {
  children?: ReactNode;
  ...
}

Uwaga: ten proces powinien być używany do znalezienia nieznanego typu! Wszystkie czekają, aż je znajdziesz :)

Nickofthyme
źródło
2

Jako typ zawierający dzieci używam:

type ChildrenContainer = Pick<JSX.IntrinsicElements["div"], "children">

Ten typ kontenera podrzędnego jest wystarczająco ogólny, aby obsługiwać wszystkie różne przypadki, a także dostosowany do interfejsu API ReactJS.

Na przykład będzie to coś takiego:

const layout = ({ children }: ChildrenContainer) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {children}
        </main>
    <Aux/>
)
Denis
źródło
1

Te odpowiedzi wydają się być nieaktualne - React ma teraz wbudowany typ PropsWithChildren<{}>. Jest definiowany podobnie do niektórych poprawnych odpowiedzi na tej stronie:

type PropsWithChildren<P> = P & { children?: ReactNode };

Tim Iles
źródło
1
A co Pw przypadku tego typu?
sunpietro
Pto typ rekwizytów twojego komponentu
Tim Iles,
-2

Komponenty React powinny mieć pojedynczy węzeł opakowujący lub zwracać tablicę węzłów.

Twój <Aux>...</Aux>komponent ma dwa węzły divimain .

Spróbuj owinąć swoje dzieci divw Auxkomponent.

import * as React from 'react';

export interface AuxProps  { 
  children: React.ReactNode
}

const aux = (props: AuxProps) => (<div>{props.children}</div>);

export default aux;
Dinesh Pandiyan
źródło
1
Nie prawda. W React 16+ już tak nie jest, a błąd powiedziałby, że gdyby tak było
Andrew Li