TypeScript и React - детский тип?


141

У меня есть очень простой функциональный компонент:

import * as React from 'react';

export interface AuxProps  { 
    children: React.ReactNode
 }


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

export default aux;

И еще один компонент:

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;

Я получаю следующую ошибку:

[ts] Тип элемента JSX 'ReactNode' не является функцией-конструктором для элементов JSX. Тип undefined нельзя присвоить типу ElementClass. [2605]

Как мне это правильно набрать?

Ответы:


134

Просто children: React.ReactNode


1
Это правильный ответ! JSX.Elementнедостаточно хорош, так как допустимые дочерние элементы React могут быть строкой, логическим значением, нулем ... ReactChildтоже неполны по тем же причинам
Пьер Ферри

2
@PierreFerry Проблема в том, что можно назначить почти всеReactNode . Это не помогает с точки зрения безопасности типов, подобно тому, childrenкак печатать как any- см. Мой ответ для объяснения.
ford04

Спасибо за ответ @ ford04. В моем случае я хочу, чтобы дети имели свободный тип. Мой компонент принимает любые допустимые дочерние элементы React. Так что я считаю, что это все еще тот ответ, который я искал :)
Пьер Ферри,

48

Чтобы использовать <Aux>в JSX, это должна быть функция, которая возвращает ReactElement<any> | null. Это определение функционального компонента .

Однако в настоящее время он определяется как функция, которая возвращает React.ReactNode, что является гораздо более широким типом. Как говорится в типизации React:

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

Убедитесь, что нежелательные типы нейтрализованы, заключив возвращаемое значение в React Fragment ( <></>):

const aux: React.FC<AuxProps> = props =>
  <>{props.children}</>;

Я не понимаю, зачем нужно оборачивать childrenфрагмент React. Не хочешь объяснить? В React вполне допустимо просто return children;.
sanfilippopablo

1
Нет, если childrenэто массив. В этом случае вам нужно либо обернуть их в один узел, либо использовать фрагменты React.
Karol Majewski

Да, в моем коде есть:, return React.Children.count(children) > 1 ? <>{children}</> : childrenно машинопись на это жалуется. Я заменил его на просто <>{children}</>.
sanfilippopablo

2
Он жалуется, потому что childrenэто список элементов, который содержит только 1 элемент. Думаю, сработало бы, если бы вы сделали return React.Children.count(children) > 1 ? <>{children}</> : children[0]@sanfilippopablo
tamj0rd2

Похоже, что это не работает в более новых версиях React. Я вошел в консоль и могу подтвердить, что у меня есть детская опора, но даже с <React.Fragment>окружением {props.children}я ничего не возвращаю.
Марк

34

Вот что сработало для меня:

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

Изменить Я бы рекомендовал использовать children: React.ReactNodeвместо этого сейчас.


8
Это недостаточно широкое определение. Ребенок мог быть строкой.
Mike S

По крайней мере, этот пример сработал для меня, поскольку мой компонент должен был ожидать 1 или более JSX.Elements. Спасибо!
Итало Борхес

1
Это не будет работать с выражениями JavaScript в качестве потомков <MyComponent>{ condition ? <span>test</span> : null}</MyComponent>. Использование children: ReactNodeбудет работать.
Nickofthyme

11

вы можете объявить свой компонент следующим образом:

const MyComponent: React.FunctionComponent = (props) => {
    return props.children;
}

10

Вы можете использовать ReactChildrenи ReactChild:

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

interface AuxProps {
  children: ReactChild | ReactChildren;
}

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

export default Aux;

6

С сайта TypeScript: https://github.com/Microsoft/TypeScript/issues/6471

Рекомендуемая практика - писать тип реквизита как {children ?: any}

Это сработало для меня. Дочерним узлом может быть много разных вещей, поэтому явная типизация может пропустить случаи.

Здесь более подробно обсуждается следующая проблема: https://github.com/Microsoft/TypeScript/issues/13618 , но любой подход все еще работает.


6

Тип возвращаемого значения функционального компонента ограничен JSXElement | nullв TypeScript. Это текущее ограничение типа, чистый React допускает больше типов возвращаемых значений.

Минимальный демонстрационный фрагмент

В качестве временного решения можно использовать утверждение типа или фрагменты:

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

ReactNode

children: React.ReactNodeможет быть неоптимальным, если цель состоит в том, чтобы иметь сильные типы для Aux.

Текущему ReactNodeтипу можно присвоить почти все , что эквивалентно {} | undefined | null. Более безопасным вариантом для вашего случая может быть:

interface AuxProps {
  children: ReactElement | ReactElement[]
}

Пример:

Учитывая Auxпотребности в элементах React as children, мы случайно добавили stringк нему. Тогда приведенное выше решение будет ошибкой, в отличие от ReactNode- взгляните на связанные игровые площадки.

Типизированные childrenтакже полезны для свойств, отличных от JSX, например для обратного вызова Render Prop .


5

Вы также можете использовать React.PropsWithChildren<P>.

type ComponentWithChildProps = React.PropsWithChildren<{example?: string}>;

3

Общий способ найти любой тип на примере. Прелесть машинописного текста в том, что у вас есть доступ ко всем типам, если у вас есть правильные @types/файлы.

Чтобы ответить на этот вопрос, я просто подумал о том, что компонент React использует эту childrenопору. Первое, что пришло в голову? А как насчет <div />?

Все, что вам нужно сделать, это открыть vscode и создать новый .tsxфайл в проекте реакции с помощью @types/react.

import React from 'react';

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

При наведении курсора на childrenопору отображается тип. А что вы знаете - его тип ReactNode(не нужен ReactNode[]).

введите описание изображения здесь

Затем, если вы щелкните определение типа, вы сразу перейдете к определению childrenисходящего из DOMAttributesинтерфейса.

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

Примечание: этот процесс следует использовать для поиска любого неизвестного типа! Все они ждут, пока вы их найдете :)


2

В качестве типа, содержащего детей, я использую:

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

Этот тип дочернего контейнера достаточно универсален, чтобы поддерживать все различные случаи, а также согласован с API ReactJS.

Итак, для вашего примера это будет примерно так:

const layout = ({ children }: ChildrenContainer) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {children}
        </main>
    <Aux/>
)

1

Эти ответы кажутся устаревшими - React теперь имеет встроенный тип PropsWithChildren<{}>. Он определяется аналогично некоторым правильным ответам на этой странице:

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


1
Что Pв случае с этим типом?
sunpietro

Pтип реквизита вашего компонента
Тим Айлс


-2

Компоненты React должны иметь один узел-оболочку или возвращать массив узлов.

У вашего <Aux>...</Aux>компонента есть два узла divи main.

Попробуйте заключить своих детей divв Auxкомпонент in .

import * as React from 'react';

export interface AuxProps  { 
  children: React.ReactNode
}

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

export default aux;

1
Не правда. В React 16+ это уже не так, плюс ошибка говорила бы об этом, если бы это было так
Эндрю Ли
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.