Domyślna wartość właściwości w komponencie React przy użyciu języka TypeScript

151

Nie mogę dowiedzieć się, jak ustawić domyślne wartości właściwości dla moich składników za pomocą Typescript.

Oto kod źródłowy:

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

A kiedy spróbuję użyć komponentu w ten sposób:

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

Pojawia się błąd informujący, że foobrakuje właściwości . Chcę użyć wartości domyślnej. Próbowałem również użyć static defaultProps = ...wewnątrz komponentu, ale nie przyniosło to efektu, jak podejrzewałem.

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

Jak mogę użyć domyślnych wartości właściwości? Wiele komponentów JS, których używa moja firma, opiera się na nich i ich niestosowanie nie jest wyborem.

Tomek
źródło
static defaultPropsjest poprawne. Czy możesz wysłać ten kod?
Aaron Beall

Odpowiedzi:

326

Domyślne rekwizyty z komponentem klasy

Używanie static defaultPropsjest poprawne. Powinieneś także używać interfejsów, a nie klas, dla właściwości i stanu.

Aktualizacja 2018/12/1 : TypeScript poprawił sprawdzanie typów związane z defaultPropsupływem czasu. Czytaj dalej, aby poznać najnowsze i najlepsze zastosowania, aż do starszych zastosowań i problemów.

W przypadku języka TypeScript 3.0 i nowszych

Specjalnie dodano obsługędefaultProps języka TypeScript, aby sprawdzanie typów działało zgodnie z oczekiwaniami. Przykład:

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

Które można renderować i kompilować bez przekazywania fooatrybutu:

<PageComponent bar={ "hello" } />

Zauważ, że:

  • foonie jest oznaczony jako opcjonalny (tj. foo?: string), mimo że nie jest wymagany jako atrybut JSX. Oznaczenie jako opcjonalne oznaczałoby, że tak może być undefined, ale w rzeczywistości nigdy tak nie będzie, undefinedponieważ defaultPropszapewnia wartość domyślną. Pomyśl o tym podobnie do tego , jak możesz oznaczyć parametr funkcji jako opcjonalny lub jako wartość domyślną, ale nie obie, ale oba oznaczają, że wywołanie nie musi określać wartości . TypeScript 3.0+ traktuje defaultPropsw podobny sposób, co jest naprawdę fajne dla użytkowników Reacta!
  • Nie defaultPropsma wyraźnej adnotacji typu. Jego typ jest wywnioskowany i używany przez kompilator do określenia, które atrybuty JSX są wymagane. Możesz użyć, defaultProps: Pick<PageProps, "foo">aby zapewnić defaultPropsdopasowanie podzbioru PageProps. Więcej informacji na temat tego zastrzeżenia wyjaśniono tutaj .
  • Wymaga to @types/reactwersji 16.4.11do prawidłowego działania.

Dla TypeScript 2.1 do 3.0

Przed zaimplementowaniem obsługi kompilatora TypeScript 3.0 defaultPropsnadal można było z niego korzystać i działało w 100% z React w czasie wykonywania, ale ponieważ TypeScript uwzględniał właściwości tylko podczas sprawdzania atrybutów JSX, musiałbyś oznaczyć właściwości, które mają wartości domyślne, jako opcjonalne ?. Przykład:

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

Zauważ, że:

  • To dobry pomysł, aby annotate defaultPropsz Partial<>tak, że typu kontrole przeciw rekwizytów, ale nie trzeba dostarczyć wszelkich wymaganych własności o wartości domyślnej, która nie ma sensu, ponieważ wymagane właściwości nie powinny potrzebować domyślną.
  • Podczas korzystania strictNullChecksz wartości this.props.foobędzie possibly undefinedi wymagać potwierdzenia niezerowego (tj. this.props.foo!) Lub zabezpieczenia typu (tj. if (this.props.foo) ...) Do usunięcia undefined. Jest to denerwujące, ponieważ domyślna wartość właściwości oznacza, że ​​w rzeczywistości nigdy nie będzie ona niezdefiniowana, ale TS nie zrozumiał tego przepływu. To jeden z głównych powodów, dla których TS 3.0 dodał wyraźne wsparcie dla defaultProps.

Przed TypeScript 2.1

Działa to tak samo, ale nie masz Partialtypów, więc po prostu pomiń Partial<>i albo podaj domyślne wartości dla wszystkich wymaganych właściwości (nawet jeśli te domyślne nigdy nie będą używane) lub całkowicie pomiń jawną adnotację typu.

Domyślne rekwizyty z komponentami funkcjonalnymi

Możesz również użyć defaultPropsna składnikach funkcji, ale musisz wpisać swoją funkcję w interfejsie FunctionComponent( StatelessComponentw @types/reactpoprzedniej wersji 16.7.2), aby TypeScript wiedział o defaultPropsfunkcji:

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

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

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

Zauważ, że nie musisz Partial<PageProps>nigdzie używać, ponieważ FunctionComponent.defaultPropsjest już określony jako częściowy w TS 2.1+.

Inną fajną alternatywą (właśnie tego używam) jest zniszczenie propsparametrów i bezpośrednie przypisanie wartości domyślnych:

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

Wtedy wcale nie potrzebujesz defaultProps! Należy pamiętać, że jeśli nie dostarczają defaultPropsna składnik funkcji będzie mieć pierwszeństwo przed wartościami parametrów domyślnych, bo React zawsze wyraźnie przekazać defaultPropswartości (tak parametry nie są niezdefiniowane, więc parametr domyślny jest nie używany). Tak byłoby użyć jeden lub drugi, nie oba.

Aaron Beall
źródło
2
Błąd wygląda na to, że używasz <PageComponent>gdzieś bez przekazywania właściwości foo. Możesz foo?: stringustawić to jako opcjonalne, używając w swoim interfejsie.
Aaron Beall
1
@Aaron Ale tsc zgłosi wtedy błąd kompilacji, ponieważ defaultProps nie implementuje interfejsu PageProps. Musisz albo ustawić wszystkie właściwości interfejsu jako opcjonalne (złe), albo określić wartość domyślną także dla wszystkich wymaganych pól (niepotrzebne standardowe) lub uniknąć określania typu w defaultProps.
pamelus
1
@adrianmoisa Masz na myśli domyślne rekwizyty? Tak to działa, ale składnia jest inna ... dodam przykład na moją odpowiedź, kiedy jestem z powrotem w moim komputerze ...
Aaron Beall
1
@AdrianMoisa Zaktualizowano o przykład komponentu funkcji s
Aaron Beall
1
@Jared Tak, musi być public static defaultPropslub static defaultProps( publicjest to ustawienie domyślne), aby wszystko (kompilator i środowisko uruchomieniowe React) działało poprawnie. Może działać w czasie wykonywania private static defaultPropstylko z bo privatei publicnie istnieje w czasie wykonywania, ale kompilator nie działałby poprawnie.
Aaron Beall
18

W przypadku Typescript 2.1+ użyj Partial <T> zamiast ustawiania właściwości interfejsu jako opcjonalnych.

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

public static defaultProps: Partial<Props> = {
    a: true
};
Uczeń
źródło
6

Dzięki Typescript 3.0 jest nowe rozwiązanie tego problemu:

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

Zauważ, że aby to zadziałało, potrzebujesz nowszej wersji @types/reactniż 16.4.6. Działa z 16.4.11.

CorayThan
źródło
Świetna odpowiedź! Jak można sobie poradzić: export interface Props { name?: string;}gdzie nazwa jest opcjonalnym rekwizytem? Ciągle dostajęTS2532 Object is possibly 'undefined'
Fydo
@Fydo Nie potrzebowałem mieć domyślnej wartości opcjonalnej właściwości, ponieważ undefinedjest to rodzaj automatycznej wartości domyślnej dla tych właściwości. Chcesz undefinedczasami przekazać jako jawną wartość, ale masz inną wartość domyślną? Czy próbowałeś export interface Props {name: string | undefined;}zamiast tego? Nie próbowałem tego, tylko pomysł.
CorayThan,
Dodawanie ?jest tym samym, co dodawanie |undefined. Chcę opcjonalnie przekazać propozycję i niech defaultPropszajmiemy się tym przypadkiem. Wygląda na to, że nie jest to jeszcze możliwe w TS3. Ja po prostu bał użyć name!składni, ponieważ wiem, że nigdy nie ma undefined, gdy defaultPropssą ustawione. W każdym razie dzięki!
Fydo
1
Głosowano za, ponieważ jest to teraz właściwa odpowiedź! Zaktualizowałem również moją zaakceptowaną odpowiedź (która zaczyna być książką historyczną) o tę nową funkcję i trochę więcej wyjaśnień. :)
Aaron Beall
5

Dla tych, którzy mają opcjonalne właściwości, które wymagają wartości domyślnych. Kredyt tutaj :)

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
źródło
3

Z komentarza @pamelus do zaakceptowanej odpowiedzi:

Musisz albo ustawić wszystkie właściwości interfejsu jako opcjonalne (złe), albo określić wartość domyślną również dla wszystkich wymaganych pól (niepotrzebne standardowe) lub uniknąć określania typu w defaultProps.

Właściwie możesz użyć dziedziczenia interfejsu Typescript . Wynikowy kod jest tylko trochę bardziej szczegółowy.

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
źródło
0

W przypadku komponentu funkcjonalnego wolałbym zachować propsargument, więc oto moje rozwiązanie:

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
źródło
0

Komponent funkcjonalny

Właściwie dla komponentu funkcjonalnego najlepsza praktyka jest jak poniżej, tworzę przykładowy komponent 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
źródło