Obecnie przeprowadzam migrację aplikacji React do TypeScript. Jak dotąd działa to całkiem nieźle, ale mam problem ze zwracanymi typami moich render
funkcji, odpowiednio, z komponentami funkcji.
Do tej pory zawsze używałem JSX.Element
jako typu zwracanego, teraz to już nie działa, jeśli komponent zdecyduje się nie renderować niczego, tj. Zwrócić null
, ponieważ null
nie ma prawidłowej wartości dla JSX.Element
. To był początek mojej podróży, ponieważ teraz przeszukałem sieć i stwierdziłem, że powinieneś ReactNode
zamiast tego użyć , co obejmuje null
również, a także kilka innych rzeczy, które mogą się zdarzyć. To wydawało się być lepszym rozwiązaniem.
Jednak teraz podczas tworzenia składnika funkcji TypeScript narzeka na ReactNode
typ. Ponownie, po kilku poszukiwaniach stwierdziłem, że dla komponentów funkcji powinieneś użyć ReactElement
zamiast tego. Jednak jeśli to zrobię, problem ze zgodnością zniknie, ale teraz TypeScript ponownie narzeka, że null
nie jest prawidłową wartością.
Krótko mówiąc, mam trzy pytania:
- Jaka jest różnica między
JSX.Element
,ReactNode
iReactElement
? - Dlaczego
render
zwracają się metody składników klasReactNode
, a składniki funkcji zwracająReactElement
? - Jak rozwiązać ten problem w odniesieniu do
null
?
źródło
class Example extends Component<ExampleProps> {
dla klas iconst Example: FunctionComponent<ExampleProps> = (props) => {
dla komponentów funkcji (gdzieExampleProps
jest interfejs dla oczekiwanych właściwości). A następnie te typy mają wystarczającą ilość informacji, aby można było wywnioskować typ zwracany.Odpowiedzi:
ReactElement to obiekt z typem i właściwościami.
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> { type: T; props: P; key: Key | null; }
ReactNode to ReactElement, ReactFragment, ciąg znaków, liczba lub tablica ReactNodes, null, undefined lub wartość logiczna:
type ReactText = string | number; type ReactChild = ReactElement | ReactText; interface ReactNodeArray extends Array<ReactNode> {} type ReactFragment = {} | ReactNodeArray; type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
JSX.Element jest elementem ReactElement, z typem ogólnym dla właściwości i typem dowolnym. Istnieje, ponieważ różne biblioteki mogą implementować JSX na swój własny sposób, dlatego JSX jest globalną przestrzenią nazw, która jest następnie ustawiana przez bibliotekę, React ustawia ją w następujący sposób:
declare global { namespace JSX { interface Element extends React.ReactElement<any, any> { } } }
Przez przykład:
<p> // <- ReactElement = JSX.Element <Custom> // <- ReactElement = JSX.Element {true && "test"} // <- ReactNode </Custom> </p>
Rzeczywiście, zwracają różne rzeczy.
Component
s powrót:Funkcje są „składnikami bezstanowymi”:
interface StatelessComponent<P = {}> { (props: P & { children?: ReactNode }, context?: any): ReactElement | null; // ... doesn't matter }
W rzeczywistości wynika to z przyczyn historycznych .
Wpisz to
ReactElement | null
tak, jak robi to reakcja. Lub pozwól, aby Typescript wywnioskował typ.źródło typów
źródło
ReactComponent
powinno byćReact.Component
lubComponent
.ReactElement i JSX.Element są wynikiem wywołania
React.createElement
bezpośrednio lub poprzez transpilację JSX. Jest to obiekt ztype
,props
ikey
.JSX.Element
jestReactElement
, któregoprops
itype
mają typany
, więc są mniej więcej takie same.const jsx = <div>hello</div> const ele = React.createElement("div", null, "hello");
ReactNode jest używany jako typ zwracany
render()
w komponentach klas. Jest to również domyślny typchildren
atrybutu zPropsWithChildren
.const Comp: FunctionComponent = props => <div>{props.children}</div> // children?: React.ReactNode
W deklaracjach typu React wygląda to na bardziej skomplikowane , ale jest równoważne z:
type ReactNode = {} | null | undefined; // super type `{}` has absorbed *all* other types, which are sub types of `{}` // so it is a very "broad" type (I don't want to say useless...)
Możesz przypisać prawie wszystko do
ReactNode
. Zwykle wolałbym silniejsze typy, ale mogą istnieć pewne ważne przypadki, aby go użyć.tl; dr: Jest to aktualna niekompatybilność typu TS niezwiązana z rdzeniem React .
Komponent klasy TS: wraca
ReactNode
zrender()
, bardziej pobłażliwy niż React / JSKomponent funkcji TS: zwraca
JSX.Element | null
, bardziej restrykcyjny niż React / JSW zasadzie
render()
w klasie React / JS komponenty obsługują te same typy zwracanych wartości, co komponent funkcji. W odniesieniu do TS, różne typy są niespójnymi typami, które są nadal utrzymywane z powodów historycznych i potrzeby kompatybilności wstecznej.Idealnie byłoby, gdyby prawidłowy typ zwrotu wyglądał bardziej tak:
type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number | boolean | null // Note: undefined is invalid
Niektóre opcje:
// Use type inference; inferred return type is `JSX.Element | null` const MyComp1 = ({ condition }: { condition: boolean }) => condition ? <div>Hello</div> : null // Use explicit function return types; Add `null`, if needed const MyComp2 = (): JSX.Element => <div>Hello</div>; const MyComp3 = (): React.ReactElement => <div>Hello</div>; // Option 3 is equivalent to 2 + we don't need to use a global (JSX namespace) // Use built-in `FunctionComponent` or `FC` type const MyComp4: React.FC<MyProps> = () => <div>Hello</div>;
Uwaga: unikanie
React.FC
nie uchroni Cię przedJSX.Element | null
ograniczeniem typu zwrotu.Aplikacja Create React App została niedawno usunięta
W skrajnych przypadkach możesz dodać potwierdzenie typu lub fragmenty jako obejście:React.FC
ze swojego szablonu, ponieważ ma pewne dziwactwa, takie jak niejawna{children?: ReactNode}
definicja typu. DlategoReact.FC
preferowane może być oszczędne używanie .const MyCompFragment: FunctionComponent = () => <>"Hello"</> const MyCompCast: FunctionComponent = () => "Hello" as any // alternative to `as any`: `as unknown as JSX.Element | null`
źródło