web-dev-qa-db-fra.com

Comment utiliser le type React.FC <props> lorsque les enfants peuvent être soit un nœud React) soit une fonction

J'ai cet exemple de composant

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;

Mon intention ici est que le prop children du composant puisse être soit

  • a node, qui est décrit comme

    Tout ce qui peut être rendu: des nombres, des chaînes, des éléments ou un tableau (ou fragment) contenant ces types.

  • un function, (ou un "accessoire de rendu") qui obtient simplement une valeur de l'intérieur du composant et renvoie un autre node

le point ici est d'être explicite, que le children peut être soit l'un (node, qui est à peu près tout) soit l'autre (qui est simplement un function)

Le problème

Je suis cependant confronté aux problèmes suivants avec la vérification de type.

  • si je laisse le code tel que présenté ici, j'obtiens le message d'erreur suivant sur la ligne ? props.children(val)

    Cette expression n'est pas appelable. Tous les constituants de type "Fonction | ((x: nombre) => ReactNode) | (chaîne & {}) | (nombre & {}) | (faux & {}) | (vrai & {}) | ({} & chaîne) | ({} & nombre) | ({} & false) | ({} & vrai) | (((x: nombre) => ReactNode) & chaîne)

Je ne comprends pas cette erreur.

  • si je change le type Props pour être
type Props = {
  children: (x: number) => ReactNode;
};

et comptez sur le propre type PropsWithChildren<P> = P & { children?: ReactNode }; de React pour gérer le cas où children n'est pas une fonction, alors j'obtiens l'erreur

(propriété) children ?: PropTypes.Validator <(x: number) => React.ReactNode> Le type 'Validator' n'est pas attribuable au type 'Validator <(x: number) => ReactNode>'. Le type 'ReactNodeLike' n'est pas assignable au type '(x: number) => ReactNode'. Le type 'string' n'est pas assignable au type '(x: number) => ReactNode'.ts (2322) Comp.tsx (5, 3): Le type attendu provient de la propriété' children 'qui est déclarée ici sur type

sur la ligne children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired

La seule solution est de laisser le type Props comme

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

et changez aussi le Comp.propTypes en children: PropTypes.func.isRequired, ce qui est pas ce que je veux, puisque je veux être explicite .

La question

Comment puis-je garder le code explicite, tel que présenté au début de cette question, et ne pas avoir la vérification de type me renvoyer des erreurs?

lien CodeSandbox

5
Dimitris Karagiannis

Je pense que l'union mondiale pourrait aider:

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

Une autre solution qui fonctionne, et qui ne nécessite pas d'écrire la déclaration Props différemment ou de réécrire autre chose différemment, est de strictement définir le type de props paramètre, lors de la définition du composant, comme ceci

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
};

Je ne suis pas sûr à 100% pourquoi cela fait une différence, mon intuition est que nous "forcons" notre propre Props définition au vérificateur de type, donc nous limitons la portée possible.

METTRE À JOUR

Depuis que j'ai posé la question initiale, j'ai finalement opté pour la solution suivante à mon problème: J'ai défini mon propre type de composant de fonction :

//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;
  }
}

Cette solution

  • Me permet de définir strictement ce qu'est un composant de fonction
  • Ne force aucun accessoire children arbitraire dans ma définition
  • Ne recoupe aucun Component.propTypes avec le TS type Props = {...}. Souvent, ils ne mappaient pas exactement 1: 1, et j'ai été obligé d'utiliser PropTypes.any ce n'est pas ce que je voulais.

La raison pour laquelle je garde le Component.propTypes avec les types TS, c'est que bien que TS soit très agréable pendant le développement, PropTypes avertira en fait en cas de valeur de type incorrect pendant l'exécution, ce qui est un comportement utile lorsque, par exemple, un champ dans une réponse API était censée être un nombre et est maintenant une chaîne. Des choses comme celles-ci peuvent arriver et ce n'est pas quelque chose que TS peut aider.

Lectures complémentaires

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

0
Dimitris Karagiannis