Valor da propriedade padrão no componente React usando TypeScript

153

Não consigo descobrir como definir valores de propriedade padrão para meus componentes usando o Typecript.

Este é o código fonte:

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 tento usar o componente assim:

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

Recebo um erro dizendo que a propriedade fooestá ausente. Eu quero usar o valor padrão. Eu também tentei usar static defaultProps = ...dentro do componente, mas não teve efeito como eu suspeitava.

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

Como posso usar valores de propriedade padrão? Muitos componentes JS que minha empresa usa dependem deles e não usá-los não é uma escolha.

Tom
fonte
static defaultPropsestá correto. Você pode postar esse código?
Aaron Beall

Respostas:

327

Adereços padrão com componente de classe

O uso static defaultPropsestá correto. Você também deve usar interfaces, não classes, para props e state.

Atualização 2018/12/1 : O TypeScript aprimorou a verificação de tipo relacionada ao defaultPropslongo do tempo. Continue lendo para obter o melhor e mais recente uso, para usos e problemas mais antigos.

Para TypeScript 3.0 e superior

O TypeScript adicionou suportedefaultProps especificamente para fazer com que a verificação de tipo funcionasse como você esperaria. Exemplo:

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

Que pode ser renderizado e compilado sem passar um fooatributo:

<PageComponent bar={ "hello" } />

Observe que:

  • foonão está marcado como opcional (ou seja foo?: string), mesmo que não seja necessário como um atributo JSX. Marcar como opcional significaria que poderia ser undefined, mas na verdade nunca será undefinedporque defaultPropsfornece um valor padrão. Pense nisso de maneira semelhante a como você pode marcar um parâmetro de função como opcional ou com um valor padrão, mas não ambos, mas ambos significam que a chamada não precisa especificar um valor . O TypeScript 3.0+ trata de defaultPropsmaneira semelhante, o que é realmente interessante para os usuários do React!
  • O defaultPropsnão tem nenhum tipo de anotação explícita. Seu tipo é inferido e usado pelo compilador para determinar quais atributos JSX são necessários. Você pode usar defaultProps: Pick<PageProps, "foo">para garantir a defaultPropscorrespondência de um subconjunto de PageProps. Mais sobre esta ressalva é explicada aqui .
  • Isso requer que a @types/reactversão 16.4.11funcione corretamente.

Para TypeScript 2.1 até 3.0

Antes que o TypeScript 3.0 implementasse o suporte ao compilador, defaultPropsvocê ainda podia usá-lo, e funcionava 100% com o React em tempo de execução, mas como o TypeScript considerava apenas adereços na verificação de atributos JSX, seria necessário marcar adereços com padrões como opcionais ?. Exemplo:

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

Observe que:

  • É uma boa idéia para anotar defaultPropscom Partial<>para que ele digita-cheques contra seus adereços, mas você não tem que fornecer a cada propriedade necessária com um valor padrão, que não faz sentido uma vez que as propriedades necessárias não deve nunca precisa de um padrão.
  • Ao usar strictNullCheckso valor de this.props.fooserá possibly undefinede será necessária uma asserção não nula (isto é this.props.foo!) ou uma proteção de tipo (isto é,if (this.props.foo) ... ) para remover undefined. Isso é irritante, uma vez que o valor padrão do suporte significa que ele nunca será indefinido, mas o TS não entendeu esse fluxo. Essa é uma das principais razões pelas quais o TS 3.0 adicionou suporte explícito defaultProps.

Antes do TypeScript 2.1

Isso funciona da mesma forma, mas você não tem Partial tipos, portanto, omita Partial<>e forneça os valores padrão para todos os adereços necessários (mesmo que esses padrões nunca sejam usados) ou omita completamente a anotação de tipo explícita.

Adereços padrão com componentes funcionais

Você também pode usar os defaultPropscomponentes da função, mas é necessário digitar sua função na interface FunctionComponent( StatelessComponentna @types/reactversão anterior 16.7.2) para que o TypeScript conheça defaultPropsa função:

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

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

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

Observe que você não precisa usá-lo em Partial<PageProps>nenhum lugar porque FunctionComponent.defaultPropsjá está especificado como parcial no TS 2.1+.

Outra boa alternativa (é isso que eu uso) é desestruturar seus propsparâmetros e atribuir valores padrão diretamente:

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

Então você não precisa de defaultPropsnada! Esteja ciente de que se você não fornecer defaultPropsem um componente função que ele terá precedência sobre valores de parâmetros padrão, porque Reagir sempre explicitamente passar os defaultPropsvalores (para os parâmetros nunca são indefinido, assim, o parâmetro padrão nunca é utilizado.) Então você usaria um ou outro, não ambos.

Aaron Beall
fonte
2
O erro parece que você está usando <PageComponent>algum lugar sem passar o foosuporte. Você pode torná-lo opcional usando foo?: stringem sua interface.
Aaron Beall
1
@ Aaron Mas o tsc lançará um erro de compilação, pois o defaultProps não implementa a interface PageProps. Você precisa tornar todas as propriedades da interface opcionais (incorretas) ou especificar o valor padrão também para todos os campos obrigatórios (clichê desnecessário) ou evitar especificar o tipo em defaultProps.
Pamelus
1
@adrianmoisa Você quer dizer adereços padrão? Sim ele funciona, mas a sintaxe é diferente ... Vou acrescentar um exemplo para a minha resposta quando eu estou de volta no meu computador ...
Aaron Beall
1
@AdrianMoisa Atualizado com o exemplo de componente de função s
Aaron Beall
1
@ Jared Sim, ele precisa ser public static defaultPropsou static defaultProps( publicé padrão) para que tudo (compilador e tempo de execução do React) funcione corretamente. Pode funcionar em tempo de execução private static defaultPropsapenas porque, privatee publicnão existe, mas o compilador não funcionaria corretamente.
Aaron Beall
18

Com o TypeScript 2.1+, use Parcial <T> em vez de tornar suas propriedades de interface opcionais.

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

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

Com o TypeScript 3.0, há uma nova solução para esse 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 />

Observe que, para que isso funcione, você precisa de uma versão mais recente do @types/reactque 16.4.6. Trabalha com 16.4.11.

CorayThan
fonte
Ótima resposta! Como lidar com: export interface Props { name?: string;}onde name é um acessório opcional ? Eu continuo recebendoTS2532 Object is possibly 'undefined'
Fydo 11/09/18
@Fydo Eu não precisava ter um valor padrão para um suporte opcional, pois undefinedé um tipo de valor padrão automático para esses acessórios. Você deseja passar undefinedcomo valor explícito às vezes, mas tem outro valor padrão? Você já tentou export interface Props {name: string | undefined;}? Ainda não tentei isso, apenas uma ideia.
CorayThan
Adicionar ?é o mesmo que adicionar |undefined. Quero opcionalmente passar o suporte e deixar defaultPropslidar com esse caso. Parece que isso ainda não é possível no TS3. Vou usar a temida name!sintaxe, pois sei que nunca é undefinedquando defaultPropsestão definidas. Obrigado de qualquer forma!
Fydo 12/09/18
1
Promovido porque esta é a resposta certa agora! Também atualizei minha resposta aceita (que está começando a se tornar um livro de história) com esse novo recurso, e um pouco mais de explicação. :)
Aaron Beall
5

Para aqueles que possuem adereços opcionais que precisam de valores padrão. Crédito aqui :)

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

De um comentário de @pamelus na resposta aceita:

Você precisa tornar todas as propriedades da interface opcionais (incorretas) ou especificar o valor padrão também para todos os campos obrigatórios (clichê desnecessário) ou evitar especificar o tipo em defaultProps.

Na verdade, você pode usar a herança de interface do Typescript . O código resultante é apenas um pouco mais detalhado.

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

Para o componente funcional, prefiro manter o propsargumento, então aqui está minha solução:

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

Componente funcional

Na verdade, para o componente funcional, a melhor prática é como abaixo, eu crio um componente Spinner de exemplo:

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
fonte