Kiedy używać JSX.Element vs ReactNode vs ReactElement?

Obecnie migruję aplikację Reactową do maszynopisu. Jak na razie działa to całkiem dobrze, ale mam problem z typami zwrotnymi moich render funkcji odpowiednio moich komponentów funkcyjnych.

Do tej pory zawsze używałem JSX.Element jako typu return, teraz to już nie działa, jeśli komponent zdecyduje się Nie renderować cokolwiek, tzn. zwrócić null, ponieważ null nie jest poprawną wartością dla JSX.Element. To był początek mojej podróży, ponieważ teraz Szukałem web i okazało się, że należy użyć ReactNode zamiast, który obejmuje również null, a także kilka innych rzeczy, które mogą się zdarzyć. To wydawało się być lepszym zakładem.

Jednak teraz przy tworzeniu komponentu funkcyjnego TypeScript narzeka na typ ReactNode. Ponownie, po kilku poszukiwaniach znalazłem, że Dla komponentów funkcyjnych należy użyć ReactElement. Jeśli jednak to zrobię, problem ze zgodnością zniknie, ale teraz TypeScript znowu narzeka na to, że null nie jest poprawną wartością.

Więc, krótko mówiąc, mam trzy pytania:]}

  1. Jaka jest różnica między JSX.Element, ReactNode i ReactElement?
  2. dlaczego metody render składników klasy zwracają ReactNode, ale składniki funkcji zwracają ReactElement?
  3. Jak to rozwiązać w odniesieniu do null?
Author: jonrsharpe, 2019-09-26

2 answers

Jaka jest różnica między JSX.Element, ReactNode i ReactElement?

ReactElement to obiekt o typie i właściwościach.
 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 boolean:
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 Reaktelementem, A Typ generyczny dla właściwości i typ jest dowolny. Istnieje, ponieważ różne biblioteki mogą zaimplementować JSX na swój sposób, dlatego JSX jest globalna przestrzeń 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>

Dlaczego metody renderowania komponentów klasy zwracają ReactNode, ale komponenty funkcji zwracają Reacttelement?

Rzeczywiście, zwracają różne rzeczy. Components return:
 render(): ReactNode;

I funkcje są "składnikami bezstanowymi":

 interface StatelessComponent<P = {}> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement | null;
    // ... doesn't matter
}
Jest to spowodowane względami historycznymi.

Jak to rozwiązać w odniesieniu do null?

Wpisz go jako ReactElement | null tak jak robi to react. Albo niech Typescript wywnioskuje Typ.

źródło dla typów

 203
Author: Jonas Wilms,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-09 06:38:54

1.) Jaka jest różnica między JSX.Element, ReactNode i ReactElement?

ReactElement i JSX.Element są wynikiem wywołania React.createElement bezpośrednio lub poprzez transpilację JSX. Jest to obiekt o type, props i key. JSX.Element jest ReactElement, którego props i type mają typ any, 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 zwracający render() W komponentach klasy. Jest to również domyślne type for children atrybut with PropsWithChildren.

const Comp: FunctionComponent = props => <div>{props.children}</div> 
// children?: React.ReactNode

Wygląda to bardziej skomplikowanie w deklaracjach typu React , ale jest odpowiednikiem do:

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. Zazwyczaj wolałabym mocniejsze typy, ale może być kilka ważnych przypadków, aby go użyć.


2.) Dlaczego metody renderowania komponentów klasy zwracają ReactNode, ale komponenty funkcji zwracają ReactElement?

Tl; dr: Jest to aktualna niezgodność typu TS niezwiązana z reakcją rdzenia.

  • Komponent klasy TS: zwraca ReactNode z render(), bardziej permisywny niż React / JS

  • Komponent funkcji TS: zwraca JSX.Element | null, bardziej restrykcyjny niż React / JS

W zasadzie, render() W komponentach klasy React/JS obsługuje te same typy zwrotne jako komponent funkcyjny. W odniesieniu do TS, różne typy są rodzajem niespójności nadal utrzymywane ze względów historycznych i konieczności wstecznej zgodności.

Najlepiej valid return type prawdopodobnie wyglądałby bardziej tak:

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number 
  | boolean | null // Note: undefined is invalid

3.) Jak to rozwiązać w odniesieniu do null?

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 zapisuje Ciebie z JSX.Element | null return type restriction.

Tworzenie Reactowej aplikacji Ostatnio porzucone React.FC Z jego szablonu, ponieważ ma pewne dziwactwa jak ukryty {children?: ReactNode} definicja typu. Więc używanie React.FC oszczędnie może być lepsze.

W przypadkach brzegowych można dodać asercję typu lub fragmenty jako obejście:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
 31
Author: ford04,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55