Как использовать тип React.F C<props>, когда дочерние элементы могут быть узлом React или функцией - PullRequest
2 голосов
/ 17 февраля 2020

У меня есть этот пример компонента

import React, { FC, ReactNode, useMemo } from "react";
import PropTypes from "prop-types";

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props) {
  const val = useMemo(() => {
    return 1;
  }, []);

  return (
    <div>
      {typeof props.children === "function"
        ? props.children(val)
        : props.children}
    </div>
  );
};

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

export default Comp;

Мое намерение заключается в том, что children опора компонента может быть

  • a node, который описывается как

    Все, что может быть визуализировано: числа, строки, элементы или массив (или фрагмент), содержащий эти типы.

  • function, (или «пропеллер рендеринга»), который просто получает значение из компонента и возвращает другое node

, суть в том, что children может быть либо один (node, который в значительной степени все) или другой (который просто function)

Проблема

Я сталкиваюсь со следующим Однако возникают проблемы с проверкой типа.

  • Если я оставлю код, как показано здесь, я получу следующее сообщение об ошибке в строке ? props.children(val)

    Это выражение не вызывается. Не все составляющие типа 'Функция | ((x: number) => ReactNode) | (строка & {}) | (число & {}) | (ложь & {}) | (правда & {}) | ({} & строка) | ({} и номер) | ({} & false) | ({} и правда) | (((x: number) => ReactNode) & string)

Я не понимаю эту ошибку.

  • , если я изменю Props введите
type Props = {
  children: (x: number) => ReactNode;
};

и полагайтесь на собственный type PropsWithChildren<P> = P & { children?: ReactNode }; React для обработки случая, когда children не является функцией, тогда я получаю ошибку

(свойство) children ?: PropTypes.Validator <(x: number) => React.ReactNode> Тип «Validator» нельзя назначить типу «Validator <(x: number) => ReactNode>». Тип 'ReactNodeLike' нельзя назначить типу (x: number) => ReactNode '. Тип 'string' нельзя назначить типу (x: number) => ReactNode'.ts (2322) Comp.tsx (5, 3): ожидаемый тип происходит из свойства 'children', которое объявлено здесь для типа

на линии children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired

Единственное решение - оставить тип Props как

type Props = {
  children: (x: number) => ReactNode;
};

, а также изменить Comp.propTypes на children: PropTypes.func.isRequired, то есть , а не , что я хочу, поскольку я хочу быть явным.

Вопрос

Как сохранить явный код, представленный в начале этого вопроса, а также нет ошибок проверки типа на мне?

CodeSandbox ссылка

Ответы [ 3 ]

1 голос
/ 19 февраля 2020

Почему эта ошибка?

Есть две причины, почему вы получаете эту странную ошибку:

  1. Тип React.FC уже включает тип по умолчанию children, тип ReactNode.
  2. ReactNode - очень слабый тип , которого вам, вероятно, следует избегать в пользу более сильных типов.

Прежде всего, вот встроенные типы React:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}

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

1.) Вы используете FC<Props> для ввода Comp. FC внутренне уже включает объявление children, набранное как ReactNode, которое объединяется с вашим определением children из Props:

type Props = { children: ((x: number) => ReactNode) | ReactNode } & { children?: ReactNode }
// this is how the actual/effective props rather look like

2 .) Если вы посмотрите на тип ReactNode, вы увидите, что типы становятся значительно более сложными. Еще хуже: ReactNode включает тип {} через ReactFragment, , который является супертипом всего, кроме null и undefined ! (Я не знаю, почему им, видимо, пришлось go этот маршрут; возможно эта проблема улучшает набор текста).

Как следствие, вы делаете ваш children тип намного шире как необходимо. Это также причина, по которой вы получаете эту ошибку - охранник типа typeof props.children === function" не может больше сузить ваш тип ошибки до function.


Решение

В дополнение к вашему решению вы можете полностью опустить тип FC<Props>. Это просто особая функция в конце концов, чьи реквизиты вкл. children вы хотите, чтобы пользовательский тип все равно. Это приведет к более сильным и простым типам для отображения компилятора и IDE. Если я возьму ваше определение Anything that can be rendered для children, это может быть:

import React, { ReactChild, ReactPortal } from "react";
type Children = ReactChild | Array<Children> | ReactPortal

type Props = {
  children: ((x: number) => Children) | Children;
};

const Comp = (props: Props) => {...}

Вы могли бы даже определить свою собственную версию FC, которая имеет все от React.FC, за исключением тех широких children types:

type FC_NoChildren<P = {}> = { [K in keyof FC]: FC[K] } & // propTypes etc.
{ (props: P, context?: any): ReactElement | null } // call signature

const Comp: FC_NoChildren<Props> = props => {...}

PS: я не упомянул Comp.propTypes - ИМО, вы можете лучше использовать безопасность времени компиляции с TS.

Надеюсь, что это имеет смысл для вас, ура!

1 голос
/ 17 февраля 2020

Я думаю, что глобальный союз может помочь:

type Props = {
  children: ((x: number) => ReactNode);
} | {
  children: ReactNode;
};
0 голосов
/ 18 февраля 2020

Другое решение, которое работает и не требует написания декларации Props по-другому или переписывает что-либо еще по-другому, состоит в том, чтобы строго определять тип параметра props во время компонента определение, как это

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props: Props) { // we strictly define the props type here
  ...
}

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

Я не уверен на 100%, почему это имеет значение, моя интуиция заключается в том, что мы «принудительно» приводим наше собственное определение Props к проверке типов, поэтому мы ограничиваем возможные объем.

ОБНОВЛЕНИЕ

С тех пор, как я задал исходный вопрос, я в итоге согласился со следующим решением моей проблемы: Я определил свой собственный тип компонента функции :

//global.d.ts

declare module 'react' {
  // Do not arbitrarily pass children down to props.
  // Do not type check actual propTypes because they cannot always map 1:1 with TS types,
  // forcing you to go with PropTypes.any very often, in order for the TS compiler
  // to shut up

  type CFC<P = {}> = CustomFunctionComponent<P>;

  interface CustomFunctionComponent<P = {}> {
    (props: P, context?: any): ReactElement | null;
    propTypes?: { [key: string]: any };
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
  }
}

Это решение

  • Позволяет мне строго определить, что является компонентом функции
  • Не заставляет произвольную children опору в моем определении
  • нет перекрестной ссылки любого фактического Component.propTypes с TS type Props = {...}. Много раз они не отображали точно 1: 1, и я был вынужден использовать PropTypes.any, а это не то, что я хотел.

Причина, по которой я держу Component.propTypes вместе с типами TS, заключается в том, что хотя TS очень хорош во время разработки, PropTypes на самом деле будет предупреждать в случае значения неправильного типа во время выполнения , что является полезным поведением, когда, например, поле в ответе API должно быть числом и теперь является строкой. Подобные вещи могут произойти, и это не то, с чем TS может помочь.

Дальнейшее чтение

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237 https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237#issuecomment -486374424

...