Valeur de propriété par défaut dans le composant React à l'aide de TypeScript

153

Je ne peux pas comprendre comment définir les valeurs de propriété par défaut pour mes composants à l'aide de Typescript.

Voici le code source:

class PageState
{
}

export class PageProps
{
    foo: string = "bar";
}

export class PageComponent extends React.Component<PageProps, PageState>
{
    public render(): JSX.Element
    {
        return (
            <span>Hello, world</span>
        );
    }
}

Et quand j'essaye d'utiliser le composant comme ceci:

ReactDOM.render(<PageComponent />, document.getElementById("page"));

J'obtiens une erreur indiquant que la propriété fooest manquante. Je souhaite utiliser la valeur par défaut. J'ai également essayé d'utiliser static defaultProps = ...à l'intérieur du composant, mais cela n'a eu aucun effet comme je le soupçonnais.

src/typescript/main.tsx(8,17): error TS2324: Property 'foo' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<PageComponent> & PageProps & { children?: ReactEle...'.

Comment puis-je utiliser les valeurs de propriété par défaut? De nombreux composants JS que mon entreprise utilise en dépendent et ne pas les utiliser n'est pas un choix.

À M
la source
static defaultPropsest correct. Pouvez-vous publier ce code?
Aaron Beall

Réponses:

327

Accessoires par défaut avec composant de classe

L'utilisation static defaultPropsest correcte. Vous devriez également utiliser des interfaces, pas des classes, pour les accessoires et l'état.

Mise à jour 2018/12/1 : TypeScript a amélioré la vérification de type liée au defaultPropsfil du temps. Poursuivez votre lecture pour connaître les utilisations les plus récentes et les plus importantes jusqu'aux utilisations et problèmes plus anciens.

Pour TypeScript 3.0 et plus

TypeScript a spécifiquement ajouté la prise en charge pourdefaultProps que la vérification de type fonctionne comme vous le souhaitez. Exemple:

interface PageProps {
  foo: string;
  bar: string;
}

export class PageComponent extends React.Component<PageProps, {}> {
    public static defaultProps = {
        foo: "default"
    };

    public render(): JSX.Element {
        return (
            <span>Hello, { this.props.foo.toUpperCase() }</span>
        );
    }
}

Qui peut être rendu et compilé sans passer d' fooattribut:

<PageComponent bar={ "hello" } />

Notez que:

  • foon'est pas marqué comme facultatif (c'est-à-dire foo?: string) même s'il n'est pas requis comme attribut JSX. Marquer comme facultatif signifierait que cela pourrait l'être undefined, mais en fait, ce ne sera jamais undefinedparce que defaultPropsfournit une valeur par défaut. Pensez-y de la même manière que vous pouvez marquer un paramètre de fonction comme facultatif, ou avec une valeur par défaut, mais pas les deux, mais les deux signifient que l'appel n'a pas besoin de spécifier une valeur . TypeScript 3.0+ traite defaultPropsde la même manière, ce qui est vraiment cool pour les utilisateurs de React!
  • Le defaultPropsn'a pas d'annotation de type explicite. Son type est déduit et utilisé par le compilateur pour déterminer les attributs JSX requis. Vous pouvez utiliser defaultProps: Pick<PageProps, "foo">pour vous assurer que defaultPropscorrespond à un sous-ensemble de PageProps. Plus d'informations sur cette mise en garde sont expliquées ici .
  • Cela nécessite une @types/reactversion 16.4.11pour fonctionner correctement.

Pour TypeScript 2.1 jusqu'à 3.0

Avant que TypeScript 3.0 n'implémente la prise en charge du compilateur, defaultPropsvous pouviez toujours l'utiliser, et cela fonctionnait à 100% avec React au moment de l'exécution, mais comme TypeScript ne considérait que les accessoires lors de la vérification des attributs JSX, vous deviez marquer les accessoires qui ont des valeurs par défaut comme facultatifs avec ?. Exemple:

interface PageProps {
    foo?: string;
    bar: number;
}

export class PageComponent extends React.Component<PageProps, {}> {
    public static defaultProps: Partial<PageProps> = {
        foo: "default"
    };

    public render(): JSX.Element {
        return (
            <span>Hello, world</span>
        );
    }
}

Notez que:

  • C'est une bonne idée d'annoter defaultProps avec Partial<>pour qu'il vérifie le type par rapport à vos accessoires, mais vous n'avez pas à fournir à chaque propriété requise une valeur par défaut, ce qui n'a aucun sens puisque les propriétés requises ne devraient jamais avoir besoin d'une valeur par défaut.
  • Lorsque vous utilisez strictNullChecksla valeur de this.props.foosera possibly undefinedet nécessitera une assertion non nulle (ie this.props.foo!) ou un type-guard (ie if (this.props.foo) ...) pour supprimer undefined. C'est ennuyeux car la valeur de prop par défaut signifie qu'il ne sera jamais indéfini, mais TS n'a pas compris ce flux. C'est l'une des principales raisons pour lesquelles TS 3.0 a ajouté un support explicite pour defaultProps.

Avant TypeScript 2.1

Cela fonctionne de la même manière mais vous n'avez pas de Partialtypes, alors omettez simplement Partial<>et fournissez les valeurs par défaut pour tous les accessoires requis (même si ces valeurs par défaut ne seront jamais utilisées) ou omettez complètement l'annotation de type explicite.

Accessoires par défaut avec composants fonctionnels

Vous pouvez également utiliser defaultPropsdes composants de fonction, mais vous devez taper votre fonction dans l' interface FunctionComponent( StatelessComponentdans la @types/reactversion antérieure 16.7.2) afin que TypeScript sache defaultPropssur la fonction:

interface PageProps {
  foo?: string;
  bar: number;
}

const PageComponent: FunctionComponent<PageProps> = (props) => {
  return (
    <span>Hello, {props.foo}, {props.bar}</span>
  );
};

PageComponent.defaultProps = {
  foo: "default"
};

Notez que vous ne devez utiliser Partial<PageProps>nulle part car il FunctionComponent.defaultPropsest déjà spécifié comme partiel dans TS 2.1+.

Une autre alternative intéressante (c'est ce que j'utilise) est de déstructurer vos propsparamètres et d'attribuer directement les valeurs par défaut:

const PageComponent: FunctionComponent<PageProps> = ({foo = "default", bar}) => {
  return (
    <span>Hello, {foo}, {bar}</span>
  );
};

Alors vous n'en avez pas du tout besoin defaultProps! Sachez que si vous ne fournissez defaultPropssur un composant de fonction , il aura la priorité sur les valeurs des paramètres par défaut, car React toujours passer explicitement les defaultPropsvaleurs (si les paramètres ne sont jamais non défini, donc le paramètre par défaut est jamais utilisé.) Vous utiliseriez l'un ou l'autre, pas les deux.

Aaron Beall
la source
2
L'erreur sonne comme si vous utilisiez <PageComponent>quelque part sans passer l' fooaccessoire. Vous pouvez le rendre facultatif en utilisant foo?: stringdans votre interface.
Aaron Beall
1
@Aaron Mais tsc lancera alors une erreur de compilation, car defaultProps n'implémente pas l'interface PageProps. Vous devez soit rendre toutes les propriétés d'interface facultatives (mauvaises), soit spécifier la valeur par défaut également pour tous les champs obligatoires (passe-partout inutile) ou éviter de spécifier le type sur defaultProps.
pamelus
1
@adrianmoisa Vous voulez dire les accessoires par défaut? Oui ça marche mais la syntaxe est différente ... J'ajouterai un exemple à ma réponse quand je serai de retour devant mon ordinateur ...
Aaron Beall
1
@AdrianMoisa Mis à jour avec l'exemple de composant de fonction s
Aaron Beall
1
@Jared Oui, il doit être public static defaultPropsou static defaultProps( publicpar défaut) pour que tout (compilateur et runtime React) fonctionne correctement. Cela peut fonctionner au moment de l'exécution avec private static defaultPropsuniquement parce que privateet publicn'existe pas au moment de l'exécution, mais le compilateur ne fonctionnerait pas correctement.
Aaron Beall
18

Avec Typescript 2.1+, utilisez Partial <T> au lieu de rendre vos propriétés d'interface facultatives.

export interface Props {
    obj: Model,
    a: boolean
    b: boolean
}

public static defaultProps: Partial<Props> = {
    a: true
};
Apprenant
la source
6

Avec Typescript 3.0, il existe une nouvelle solution à ce problème:

export interface Props {
    name: string;
}

export class Greet extends React.Component<Props> {
    render() {
        const { name } = this.props;
        return <div>Hello ${name.toUpperCase()}!</div>;
    }
    static defaultProps = { name: "world"};
}

// Type-checks! No type assertions needed!
let el = <Greet />

Notez que pour que cela fonctionne, vous avez besoin d'une version plus récente de @types/reactque 16.4.6. Cela fonctionne avec 16.4.11.

CorayThan
la source
Très bonne réponse! Comment pourrait-on gérer: export interface Props { name?: string;}où le nom est un accessoire optionnel ? Je continue de recevoirTS2532 Object is possibly 'undefined'
Fydo
@Fydo Je n'ai pas eu besoin d'avoir une valeur par défaut pour un accessoire optionnel, car undefinedc'est une sorte de valeur par défaut automatique pour ces accessoires. Vous voulez pouvoir passer en undefinedtant que valeur explicite parfois, mais avez une autre valeur par défaut? Avez-vous essayé à la export interface Props {name: string | undefined;}place? Je n'ai pas essayé ça, juste une idée.
CorayJeu
L'ajout ?est identique à l'ajout |undefined. Je veux éventuellement passer dans l'accessoire, et laisser defaultPropsgérer ce cas. On dirait que cela n'est pas encore possible dans TS3. Je vais simplement utiliser la name!syntaxe redoutée car je sais que ce n'est jamais undefinedquand ils defaultPropssont définis. Merci quand même!
Fydo le
1
J'ai voté pour parce que c'est la bonne réponse maintenant! J'ai également mis à jour ma réponse acceptée (qui commence à devenir un livre d'histoire) avec cette nouvelle fonctionnalité, et un peu plus d'explications. :)
Aaron Beall
5

Pour ceux qui ont des accessoires optionnels qui ont besoin de valeurs par défaut. Crédit ici :)

interface Props {
  firstName: string;
  lastName?: string;
}

interface DefaultProps {
  lastName: string;
}

type PropsWithDefaults = Props & DefaultProps;

export class User extends React.Component<Props> {
  public static defaultProps: DefaultProps = {
    lastName: 'None',
  }

  public render () {
    const { firstName, lastName } = this.props as PropsWithDefaults;

    return (
      <div>{firstName} {lastName}</div>
    )
  }
}
Morlo Mbakop
la source
3

D'après un commentaire de @pamelus sur la réponse acceptée:

Vous devez soit rendre toutes les propriétés d'interface facultatives (mauvaises), soit spécifier la valeur par défaut également pour tous les champs obligatoires (passe-partout inutile) ou éviter de spécifier le type sur defaultProps.

En fait, vous pouvez utiliser l' héritage d'interface de Typescript . Le code qui en résulte n'est qu'un peu plus détaillé.

interface OptionalGoogleAdsProps {
    format?: string;
    className?: string;
    style?: any;
    scriptSrc?: string
}

interface GoogleAdsProps extends OptionalGoogleAdsProps {
    client: string;
    slot: string;
}


/**
 * Inspired by https://github.com/wonism/react-google-ads/blob/master/src/google-ads.js
 */
export default class GoogleAds extends React.Component<GoogleAdsProps, void> {
    public static defaultProps: OptionalGoogleAdsProps = {
        format: "auto",
        style: { display: 'block' },
        scriptSrc: "//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
    };
Harald Mühlhoff
la source
0

Pour le composant fonctionnel, je préfère garder l' propsargument, voici donc ma solution:

interface Props {
  foo: string;
  bar?: number; 
}

// IMPORTANT!, defaultProps is of type {bar: number} rather than Partial<Props>!
const defaultProps = {
  bar: 1
}


// externalProps is of type Props
const FooComponent = exposedProps => {
  // props works like type Required<Props> now!
  const props = Object.assign(defaultProps, exposedProps);

  return ...
}

FooComponent.defaultProps = defaultProps;
xiechao06
la source
0

Composant fonctionnel

En fait, pour le composant fonctionnel, la meilleure pratique est comme ci-dessous, je crée un exemple de composant Spinner:

import React from 'react';
import { ActivityIndicator } from 'react-native';
import { colors } from 'helpers/theme';
import type { FC } from 'types';

interface SpinnerProps {
  color?: string;
  size?: 'small' | 'large' | 1 | 0;
  animating?: boolean;
  hidesWhenStopped?: boolean;
}

const Spinner: FC<SpinnerProps> = ({
  color,
  size,
  animating,
  hidesWhenStopped,
}) => (
  <ActivityIndicator
    color={color}
    size={size}
    animating={animating}
    hidesWhenStopped={hidesWhenStopped}
  />
);

Spinner.defaultProps = {
  animating: true,
  color: colors.primary,
  hidesWhenStopped: true,
  size: 'small',
};

export default Spinner;
AmerllicA
la source