Valore di proprietà predefinito nel componente React utilizzando TypeScript


153

Non riesco a capire come impostare i valori di proprietà predefiniti per i miei componenti usando Typescript.

Questo è il codice sorgente:

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

E quando provo ad usare il componente in questo modo:

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

fooViene visualizzato un errore che indica che manca la proprietà . Voglio usare il valore predefinito. Ho anche provato a utilizzare static defaultProps = ...all'interno del componente, ma non ha avuto alcun effetto come sospettavo.

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

Come posso usare i valori di proprietà predefiniti? Molti componenti JS utilizzati dalla mia azienda si basano su di essi e non utilizzarli non è una scelta.


static defaultPropsè corretto. Puoi pubblicare quel codice?
Aaron Beall,

Risposte:


327

Puntelli predefiniti con componente di classe

L'uso static defaultPropsè corretto. Dovresti anche usare interfacce, non classi, per oggetti di scena e stato.

Aggiornamento 2018/12/1 : TypeScript ha migliorato il controllo del tipo relativo al defaultPropstempo. Continua a leggere per l'utilizzo più recente e più efficace, fino agli usi e ai problemi precedenti.

Per TypeScript 3.0 e versioni successive

TypeScript ha aggiuntodefaultProps specificamente il supporto per far funzionare il controllo del tipo come ti aspetteresti. Esempio:

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

Che può essere reso e compilato senza passare un fooattributo:

<PageComponent bar={ "hello" } />

Nota che:

  • foonon è contrassegnato come facoltativo (ovvero foo?: string) anche se non è richiesto come attributo JSX. Contrassegnare come facoltativo significherebbe che potrebbe esserlo undefined, ma in realtà non lo sarà mai undefinedperché defaultPropsfornisce un valore predefinito. Pensalo in modo simile a come puoi contrassegnare un parametro di funzione facoltativo o con un valore predefinito, ma non entrambi, ma entrambi significano che la chiamata non deve specificare un valore . TypeScript 3.0+ tratta defaultPropsin modo simile, il che è davvero interessante per gli utenti di React!
  • Non defaultPropsha annotazioni di tipo esplicite. Il suo tipo viene dedotto e utilizzato dal compilatore per determinare quali attributi JSX sono richiesti. È possibile utilizzare defaultProps: Pick<PageProps, "foo">per garantire defaultPropscorrispondenze a un sottoinsieme di PageProps. Maggiori informazioni su questo avvertimento sono spiegate qui .
  • Ciò richiede che la @types/reactversione 16.4.11funzioni correttamente.

Per TypeScript 2.1 fino alla 3.0

Prima che TypeScript 3.0 implementasse il supporto del compilatore per defaultPropste, potresti comunque utilizzarlo, e funzionava al 100% con React in fase di runtime, ma poiché TypeScript considerava solo gli oggetti di scena quando controllava gli attributi JSX, dovresti contrassegnare gli oggetti con valori predefiniti come facoltativi ?. Esempio:

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

Nota che:

  • E 'una buona idea di annotare defaultPropscon Partial<>in modo che tipo di controlli contro i vostri oggetti di scena, ma non c'è bisogno di fornire ogni proprietà richiesta con un valore di default, che non ha senso dal momento che le proprietà richieste non dovrebbero mai bisogno di un default.
  • Quando si utilizza strictNullChecksil valore di this.props.foosarà possibly undefinede richiederà un'asserzione non nulla (ie this.props.foo!) o type-guard (ie if (this.props.foo) ...) da rimuovere undefined. Questo è fastidioso poiché il valore di prop predefinito significa che in realtà non sarà mai indefinito, ma TS non ha capito questo flusso. Questo è uno dei motivi principali per cui TS 3.0 ha aggiunto il supporto esplicito defaultProps.

Prima di TypeScript 2.1

Funziona allo stesso modo ma non hai Partialtipi, quindi ometti Partial<>e fornisci i valori predefiniti per tutti gli oggetti di scena richiesti (anche se quei valori predefiniti non verranno mai utilizzati) o ometti completamente l'annotazione di tipo esplicito.

Puntelli predefiniti con componenti funzionali

Puoi utilizzare anche i defaultPropscomponenti delle funzioni, ma devi digitare la tua funzione nell'interfaccia FunctionComponent( StatelessComponentnella @types/reactversione precedente 16.7.2) in modo che TypeScript sia a conoscenza defaultPropsdella funzione:

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

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

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

Si noti che non è necessario utilizzare da Partial<PageProps>nessuna parte perché FunctionComponent.defaultPropsè già specificato come parziale in TS 2.1+.

Un'altra bella alternativa (questo è quello che uso) è quella di destrutturare i propsparametri e assegnare direttamente i valori predefiniti:

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

Quindi non ti serve defaultPropsaffatto! Essere consapevoli del fatto che se non fornisci defaultPropssu un componente funzione che avrà la precedenza sui valori di default dei parametri, perché Reagire sarà sempre passare esplicitamente i defaultPropsvalori (in modo che i parametri non sono mai definito, quindi il parametro di default non viene mai usato.) Allora devi usare l'uno o l'altro, non entrambi.


2
L'errore sembra che tu stia usando <PageComponent>da qualche parte senza passare l' fooelica. Puoi renderlo opzionale usando foo?: stringnella tua interfaccia.
Aaron Beall,

1
@Aaron Ma tsc genererà un errore di compilazione, poiché defaultProps non implementa l'interfaccia PageProps. Devi rendere facoltative (non valide) tutte le proprietà dell'interfaccia o specificare il valore predefinito anche per tutti i campi obbligatori (boilerplate non necessario) o evitare di specificare il tipo su defaultProps.
pamelus,

1
@adrianmoisa Intendi oggetti di scena predefiniti? Sì, funziona ma la sintassi è diversa ... Aggiungerò un esempio alla mia risposta quando torno al mio computer ...
Aaron Beall

1
@AdrianMoisa Aggiornato con l'esempio del componente di funzione s
Aaron Beall

1
@Jared Sì, deve essere public static defaultPropso static defaultProps( publicè l'impostazione predefinita) affinché tutto (compilatore e runtime React) funzioni correttamente. Potrebbe funzionare in fase di esecuzione private static defaultPropssolo perché privatee publicnon esiste in fase di esecuzione, ma il compilatore non funzionerebbe correttamente.
Aaron Beall,

18

Con Typescript 2.1+, utilizzare Parziale <T> invece di rendere facoltative le proprietà dell'interfaccia.

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

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

6

Con Typescript 3.0 esiste una nuova soluzione a questo problema:

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

Si noti che per far funzionare tutto questo è necessaria una versione più recente di @types/reactrispetto 16.4.6. Funziona con 16.4.11.


Bella risposta! Come si potrebbe gestire: export interface Props { name?: string;}dove name è un oggetto opzionale ? Continuo a ricevereTS2532 Object is possibly 'undefined'
Fydo l'

@Fydo Non ho avuto bisogno di avere un valore predefinito per un oggetto opzionale, poiché undefinedè una sorta di valore predefinito automatico per quegli oggetti. Vuoi essere in grado di passare undefinedcome valore esplicito a volte, ma hai un altro valore predefinito? Hai provato export interface Props {name: string | undefined;}invece? Non ci ho provato, solo un'idea.
Coray,

L'aggiunta ?è la stessa dell'aggiunta |undefined. Voglio opzionalmente passare il puntello e lasciare defaultPropsgestire quel caso. Sembra che questo non sia ancora possibile in TS3. Userò solo la temuta name!sintassi poiché so che non è mai undefinedquando defaultPropssono impostati. Grazie comunque!
Fydo,

1
È stato votato perché questa è la risposta giusta ora! Ho anche aggiornato la mia risposta accettata (che sta iniziando a diventare un libro di storia) con questa nuova funzione e un po 'più di spiegazione. :)
Aaron Beall,

5

Per chi ha oggetti di scena opzionali che richiedono valori predefiniti. Credito qui :)

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

3

Da un commento di @pamelus sulla risposta accettata:

Devi rendere facoltative (non valide) tutte le proprietà dell'interfaccia o specificare il valore predefinito anche per tutti i campi obbligatori (boilerplate non necessario) o evitare di specificare il tipo su defaultProps.

In realtà puoi usare l' ereditarietà dell'interfaccia di Typescript . Il codice risultante è solo un po 'più dettagliato.

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

0

Per il componente funzionale, preferirei mantenere l' propsargomento, quindi ecco la mia soluzione:

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;

0

Componente funzionale

In realtà, per il componente funzionale la best practice è come di seguito, creo un componente Spinner di esempio:

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.