reactjs - Valoarea implicită a proprietății într-un component React folosind TypeScript


Nu reușesc să îmi dau seama cum să setezi valorile implicite ale proprietăților pentru componentele mele folosind Typescript.

Acesta este codul sursă:

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

Și când încerc să folosesc componenta astfel:

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

Primesc o eroare care spune că proprietatea foo lipsește. Vreau să folosesc valoarea implicită. Am încercat, de asemenea, să folosesc static defaultProps=... în interiorul componentului, dar nu a avut niciun efect, așa cum bănuiesc.

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

Cum pot folosi valorile implicite ale proprietăților? Multe componente JS pe care le folosește compania mea se bazează pe ele și a nu le folosi nu este o opțiune.

Răspuns #1

Props implicite cu componenta de clasă

Utilizarea static defaultProps este corectă. De asemenea, ar trebui să folosiți interfețe, nu clase, pentru props și state.

Actualizare 2018/12/1: TypeScript a îmbunătățit verificarea tipurilor legată de defaultProps de-a lungul timpului. Citiți mai departe pentru cele mai recente și mai bune utilizări, până la utilizările și problemele mai vechi.

Pentru TypeScript 3.0 și versiuni ulterioare

TypeScript a adăugat în mod specific suport pentru defaultProps pentru a face ca verificarea tipurilor să funcționeze așa cum te-ai aștepta. Exemplu:

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

Care poate fi redat și compilat fără a trece un atribut foo:

<PageComponent bar={ "hello" } />

Observați că:

  • foo nu este marcat ca opțional (adică foo?: string), chiar dacă nu este obligatoriu ca atribut JSX. Marcarea ca opțional ar însemna că ar putea fi undefined, dar de fapt nu va fi niciodată undefined deoarece defaultProps oferă o valoare implicită. Gândește-te la asta asemănător cu modul în care poți marca un parametru al unei funcții ca opțional, sau cu o valoare implicită, dar nu amândouă, totuși ambele înseamnă că apelul nu trebuie să specifice o valoare. TypeScript 3.0+ tratează defaultProps într-un mod similar, ceea ce este foarte tare pentru utilizatorii React!
  • defaultProps nu are o adnotare de tip explicită. Tipul său este dedus și utilizat de compilator pentru a determina care atribute JSX sunt obligatorii. Ai putea utiliza defaultProps: Pick<PageProps, "foo"> pentru a te asigura că defaultProps corespunde unui sub-set de PageProps. Mai multe despre această atenționare sunt explicate aici.
  • Aceasta necesită versiunea @types/react 16.4.11 pentru a funcționa corect.

Pentru TypeScript 2.1 până la 3.0

Înainte ca TypeScript 3.0 să implementeze suportul compilerului pentru defaultProps, puteai totuși să îl folosești, și funcționa 100% cu React la runtime, dar deoarece TypeScript lua în considerare doar props atunci când verifica atributele JSX, trebuia să marchezi props care au valori implicite ca fiind opționale cu ?. Exemplu:

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

Observați că:

  • Este o idee bună să adnotezi defaultProps cu Partial<> astfel încât să fie verificate tipurile în raport cu proprietățile tale, dar nu trebuie să furnizezi fiecare proprietate necesară cu o valoare implicită, ceea ce nu are sens, deoarece proprietățile necesare nu ar trebui să aibă niciodată nevoie de o valoare implicită.
  • Când se folosește strictNullChecks, valoarea lui this.props.foo va fi posibil nedefinită și va necesita o aserțiune non-null (adică this.props.foo!) sau o verificare de tip (adică if (this.props.foo) ...) pentru a elimina undefined. Acest lucru este enervant deoarece valoarea implicită a prop-ului înseamnă că de fapt nu va fi niciodată nedefinită, dar TS nu a înțeles acest flux. Aceasta este unul dintre principalele motive pentru care TS 3.0 a adăugat suport explicit pentru defaultProps.

Înainte de TypeScript 2.1

Acest lucru funcționează la fel, dar nu ai tipuri Partial, așa că pur și simplu omit Partial<> și fie furnizezi valori implicite pentru toate proprietățile necesare (chiar dacă acele valori implicite nu vor fi niciodată folosite), fie omiți complet adnotarea explicită a tipului.

Props implicite cu componente funcționale

Poți folosi defaultProps și pe componentele funcție, dar trebuie să-ți tipizezi funcția la interfața FunctionComponent (StatelessComponent în @types/react înainte de versiunea 16.7.2) astfel încât TypeScript să știe despre defaultProps pe funcție:

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

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

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

Rețineți că nu trebuie să folosiți Partial<PageProps> nicăieri pentru că FunctionComponent.defaultProps este deja specificat ca un partial în TS 2.1+.

O altă alternativă plăcută (asta folosesc eu) este să destrucuturezi parametrii tăi props și să atribui valori implicite direct:

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

Atunci nu ai nevoie deloc de defaultProps! Fii conștient că dacă o faci și oferi defaultProps la un component de tip funcție, acestea vor avea prioritate față de valorile parametrilor impliciți, deoarece React va trece întotdeauna explicit valorile din defaultProps (așadar, parametrii nu sunt niciodată undefined, deci parametrul implicit nu este niciodată folosit). Așadar, vei folosi unul sau altul, nu ambele.

Răspuns #2

Cu Typescript 2.1+, folosește Partial < T > în loc să faci proprietățile interfeței tale opționale.

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

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

Răspunsul #3

Componentă funcțională

De fapt, pentru componenta funcțională cea mai bună practică este următoarea, am creat un exemplu de componentă Spinner:

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

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

const Spinner = ({
  color = colors.primary,
  size = 'small',
  animating = true,
  hidesWhenStopped = true,
}: SpinnerProps): JSX.Element => (
  <ActivityIndicator
    color={color}
    size={size}
    animating={animating}
    hidesWhenStopped={hidesWhenStopped}
  />
);

export default Spinner;

Dacă componenta ta are children, ar fi mai bine să folosești React.FC, astfel:

export interface TypographyProps {
  color?: string;
}

const Typography: React.FC<TypographyProps> = ({
  children,
  color,
}) => (
  <span style={{ color }}>
    {children}
  </span>
);

export default Typography;

Răspunsul #4

Poți folosi operatorul spread pentru a reatribui props într-un component funcțional standard. Ceea ce îmi place la această abordare este că poți combina props obligatorii cu cele opționale care au o valoare implicită.

interface MyProps {
   text: string;
   optionalText?: string;
}

const defaultProps = {
   optionalText = "foo";
}

const MyComponent = (props: MyProps) => {
   props = { ...defaultProps, ...props }
}

Răspunsul #5

Cu Typescript 3.0 există o nouă soluție pentru această 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 />

Rețineți că pentru ca acest lucru să funcționeze aveți nevoie de o versiune mai nouă de @types/react decât 16.4.6. Funcționează cu 16.4.11.