Standardeigenschaftswert in der React-Komponente mit TypeScript

150

Ich kann mit Typescript nicht herausfinden, wie Standardeigenschaftswerte für meine Komponenten festgelegt werden.

Dies ist der Quellcode:

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

Und wenn ich versuche, die Komponente so zu verwenden:

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

Ich erhalte die Fehlermeldung, dass die Eigenschaft foofehlt. Ich möchte den Standardwert verwenden. Ich habe auch versucht, static defaultProps = ...innerhalb der Komponente zu verwenden, aber es hatte keine Wirkung, wie ich vermutete.

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

Wie kann ich Standardeigenschaftswerte verwenden? Viele JS-Komponenten, die mein Unternehmen verwendet, verlassen sich auf sie, und es ist keine Wahl, sie nicht zu verwenden.

Tom
quelle
static defaultPropsist richtig. Können Sie diesen Code posten?
Aaron Beall

Antworten:

324

Standard Requisiten mit Klassenkomponente

Verwendung static defaultPropsist richtig. Sie sollten auch Schnittstellen und keine Klassen für die Requisiten und den Status verwenden.

Update 2018/12/1 : TypeScript hat die Typprüfung im defaultPropsLaufe der Zeit verbessert . Lesen Sie weiter für die neueste und beste Verwendung bis hin zu älteren Verwendungen und Problemen.

Für TypeScript 3.0 und höher

TypeScript hat speziell Unterstützung hinzugefügtdefaultProps , damit die Typprüfung wie erwartet funktioniert. Beispiel:

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

Was gerendert und kompiliert werden kann, ohne ein fooAttribut zu übergeben:

<PageComponent bar={ "hello" } />

Beachten Sie, dass:

  • fooist nicht als optional markiert (dh foo?: string), obwohl es nicht als JSX-Attribut erforderlich ist. Das Markieren als optional würde bedeuten, dass dies möglich ist undefined, wird es jedoch niemals sein, undefinedda defaultPropsein Standardwert bereitgestellt wird. Stellen Sie sich das ähnlich vor, wie Sie einen Funktionsparameter optional oder mit einem Standardwert markieren können, aber nicht beide, aber beide bedeuten, dass der Aufruf keinen Wert angeben muss . TypeScript 3.0+ behandelt defaultPropsauf ähnliche Weise, was für React-Benutzer wirklich cool ist!
  • Das defaultPropshat keine explizite Typanmerkung. Sein Typ wird abgeleitet und vom Compiler verwendet, um zu bestimmen, welche JSX-Attribute erforderlich sind. Sie können verwenden, defaultProps: Pick<PageProps, "foo">um sicherzustellen defaultProps, dass eine Teilmenge von übereinstimmt PageProps. Mehr zu dieser Einschränkung wird hier erklärt .
  • Dies erfordert, dass die @types/reactVersion 16.4.11ordnungsgemäß funktioniert.

Für TypeScript 2.1 bis 3.0

Bevor TypeScript 3.0 die Compiler-Unterstützung für defaultPropsSie implementierte , konnten Sie diese noch verwenden, und sie funktionierte zur Laufzeit zu 100% mit React. Da TypeScript jedoch bei der Suche nach JSX-Attributen nur Requisiten berücksichtigte, mussten Sie Requisiten mit Standardeinstellungen als optional markieren ?. Beispiel:

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

Beachten Sie, dass:

  • Es ist eine gute Idee, mit Anmerkungen versehen defaultPropsmit , Partial<>so dass es typ Kontrollen gegen Ihre Requisiten, aber Sie haben nicht alle erforderliche Eigenschaft mit einem Standardwert zu liefern, die keinen Sinn macht , da geforderte Eigenschaften sollen nie eine Standard benötigen.
  • Bei Verwendung strictNullChecksdes Werts von this.props.foowird possibly undefinedund erfordert eine Nicht-Null-Zusicherung (dh this.props.foo!) oder Typ-Guard (dh if (this.props.foo) ...) zu entfernen undefined. Dies ist ärgerlich, da der Standard-Prop-Wert bedeutet, dass er niemals undefiniert sein wird, aber TS hat diesen Ablauf nicht verstanden. Dies ist einer der Hauptgründe, warum TS 3.0 explizite Unterstützung für hinzugefügt hat defaultProps.

Vor TypeScript 2.1

Dies funktioniert genauso, aber Sie haben keine PartialTypen. Lassen Sie also einfach die Partial<>Standardwerte für alle erforderlichen Requisiten weg und geben Sie sie entweder an (auch wenn diese Standardeinstellungen niemals verwendet werden) oder lassen Sie die explizite Typanmerkung vollständig weg.

Standard Requisiten mit Funktionskomponenten

Sie können sie auch defaultPropsfür Funktionskomponenten verwenden, müssen jedoch Ihre Funktion in die FunctionComponent( StatelessComponentin der @types/reactvorherigen Version 16.7.2) Schnittstelle eingeben, damit TypeScript über defaultPropsdie Funktion Bescheid weiß :

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

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

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

Beachten Sie, dass Sie Partial<PageProps>nirgendwo verwenden müssen, da dies FunctionComponent.defaultPropsin TS 2.1+ bereits als Teil angegeben ist.

Eine andere nette Alternative (das ist, was ich benutze) ist, deine propsParameter zu zerstören und Standardwerte direkt zuzuweisen:

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

Dann brauchen Sie das überhaupt nicht defaultProps! Beachten Sie , dass defaultPropseine Funktionskomponente Vorrang vor Standardparameterwerten hat, da React die defaultPropsWerte immer explizit übergibt (sodass die Parameter niemals undefiniert sind und der Standardparameter daher niemals verwendet wird) der eine oder andere, nicht beide.

Aaron Beall
quelle
2
Der Fehler klingt so, als würden Sie <PageComponent>irgendwo etwas verwenden, ohne die fooRequisite zu übergeben. Sie können es foo?: stringin Ihrer Benutzeroberfläche optional machen .
Aaron Beall
1
@Aaron Aber tsc löst dann einen Kompilierungsfehler aus, da defaultProps die PageProps-Schnittstelle nicht implementiert. Sie müssen entweder alle Schnittstelleneigenschaften optional machen (fehlerhaft) oder den Standardwert auch für alle erforderlichen Felder angeben (unnötiges Boilerplate) oder die Angabe des Typs in defaultProps vermeiden.
Pamelus
1
@adrianmoisa Du meinst Standard Requisiten? Ja, es funktioniert, aber die Syntax ist anders ... Ich werde meiner Antwort ein Beispiel hinzufügen, wenn ich wieder an meinem Computer bin ...
Aaron Beall
1
@AdrianMoisa Aktualisiert mit s Funktionskomponentenbeispiel
Aaron Beall
1
@Jared Ja, es muss sein public static defaultPropsoder static defaultProps( publicist Standard), damit alles (Compiler- und React-Laufzeit) ordnungsgemäß funktioniert. Es funktioniert möglicherweise private static defaultPropsnur zur Laufzeit mit privateund publicexistiert zur Laufzeit nicht, aber der Compiler würde nicht richtig funktionieren.
Aaron Beall
18

Verwenden Sie in Typescript 2.1+ Partial <T>, anstatt Ihre Schnittstelleneigenschaften optional zu machen.

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

public static defaultProps: Partial<Props> = {
    a: true
};
Lerner
quelle
6

Mit Typescript 3.0 gibt es eine neue Lösung für dieses Problem:

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

Beachten Sie, dass Sie eine neuere Version von @types/reactals benötigen, damit dies funktioniert 16.4.6. Es funktioniert mit 16.4.11.

CorayThan
quelle
Gute Antwort! Wie könnte man damit umgehen: export interface Props { name?: string;}Wo ist der Name eine optionale Requisite? Ich TS2532 Object is possibly 'undefined'
bekomme
@Fydo Ich brauchte keinen Standardwert für eine optionale Requisite, da dies undefinedeine Art automatischer Standardwert für diese Requisiten ist. Sie möchten undefinedmanchmal als expliziter Wert übergeben können, haben aber einen anderen Standardwert? Hast du es export interface Props {name: string | undefined;}stattdessen versucht ? Ich habe das nicht versucht, nur eine Idee.
CorayThan
Das Hinzufügen ?ist dasselbe wie das Hinzufügen |undefined. Ich möchte optional die Requisite übergeben und defaultPropsdiesen Fall behandeln lassen. Sieht so aus, als wäre dies in TS3 noch nicht möglich. Ich werde nur die gefürchtete name!Syntax verwenden, da ich weiß, dass es nie ist, undefinedwann defaultPropsfestgelegt wird. Trotzdem danke!
Fydo
1
Upvoted, weil dies jetzt die richtige Antwort ist! Außerdem wurde meine akzeptierte Antwort (die langsam zu einem Geschichtsbuch wird) mit dieser neuen Funktion und etwas mehr Erklärung aktualisiert. :)
Aaron Beall
5

Für diejenigen mit optionalen Requisiten, die Standardwerte benötigen. Gutschrift hier :)

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
quelle
3

Aus einem Kommentar von @pamelus zur akzeptierten Antwort:

Sie müssen entweder alle Schnittstelleneigenschaften optional machen (fehlerhaft) oder den Standardwert auch für alle erforderlichen Felder angeben (unnötiges Boilerplate) oder die Angabe des Typs in defaultProps vermeiden.

Tatsächlich können Sie die Schnittstellenvererbung von Typescript verwenden . Der resultierende Code ist nur ein bisschen ausführlicher.

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
quelle
0

Für die Funktionskomponente möchte ich das propsArgument lieber beibehalten. Hier ist meine Lösung:

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
quelle
0

Funktionskomponente

Für die Funktionskomponente ist die beste Vorgehensweise wie folgt: Ich erstelle eine Beispiel-Spinner-Komponente:

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
quelle