Valor de propiedad predeterminado en el componente Reaccionar utilizando TypeScript

153

No puedo encontrar la manera de establecer valores de propiedad predeterminados para mis componentes usando Typecript.

Este es el código fuente:

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

Y cuando trato de usar el componente así:

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

Recibo un error diciendo fooque falta la propiedad . Quiero usar el valor predeterminado. También traté de usarlo static defaultProps = ...dentro del componente, pero no tuvo efecto como sospechaba.

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

¿Cómo puedo usar los valores de propiedad predeterminados? Muchos componentes de JS que usa mi compañía dependen de ellos y no usarlos no es una opción.

Tom
fuente
static defaultPropses correcto. ¿Puedes publicar ese código?
Aaron Beall

Respuestas:

327

Accesorios predeterminados con componente de clase

El uso static defaultPropses correcto. También debe usar interfaces, no clases, para los accesorios y el estado.

Actualización 2018/12/1 : TypeScript ha mejorado la verificación de tipos relacionada con defaultPropsel tiempo. Siga leyendo para conocer el uso más reciente y mejor hasta usos y problemas más antiguos.

Para TypeScript 3.0 y superior

TypeScript agregódefaultProps específicamente soporte para hacer que la verificación de tipos funcione como es de esperar. Ejemplo:

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 se puede representar y compilar sin pasar un fooatributo:

<PageComponent bar={ "hello" } />

Tenga en cuenta que:

  • foono está marcado como opcional (es decir foo?: string), aunque no se requiere como un atributo JSX. Marcar como opcional significaría que podría ser undefined, pero en realidad nunca lo será undefinedporque defaultPropsproporciona un valor predeterminado. Piénselo de forma similar a cómo puede marcar un parámetro de función opcional, o con un valor predeterminado, pero no ambos, pero ambos significan que la llamada no necesita especificar un valor . TypeScript 3.0+ trata defaultPropsde una manera similar, ¡lo cual es realmente genial para los usuarios de React!
  • El defaultPropsno tiene anotaciones de tipo explícito. Su tipo es inferido y utilizado por el compilador para determinar qué atributos JSX son necesarios. Puede usar defaultProps: Pick<PageProps, "foo">para asegurarse de que defaultPropscoincida con un subconjunto de PageProps. Más sobre esta advertencia se explica aquí .
  • Esto requiere que la @types/reactversión 16.4.11funcione correctamente.

Para TypeScript 2.1 hasta 3.0

Antes de que TypeScript 3.0 implementara el soporte del compilador para defaultPropsusted, aún podía usarlo, y funcionaba al 100% con React en tiempo de ejecución, pero dado que TypeScript solo consideraba accesorios al verificar los atributos JSX, tendría que marcar los accesorios que tienen valores predeterminados como opcionales ?. Ejemplo:

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

Tenga en cuenta que:

  • Es una buena idea para anotar defaultPropscon Partial<>lo que tipo de controles contra sus apoyos, pero no tiene que suministrar todos los bienes necesarios con un valor por defecto, que no tiene sentido ya que las propiedades requeridas no deben necesitar un defecto.
  • Cuando se utiliza strictNullChecksel valor de this.props.foowill be possibly undefinedy require una aserción no nula (es decir this.props.foo!) o un protector de tipo (es decir if (this.props.foo) ...) para eliminar undefined. Esto es molesto ya que el valor de apoyo predeterminado significa que en realidad nunca será indefinido, pero TS no entendió este flujo. Esa es una de las principales razones por las que TS 3.0 agregó soporte explícito defaultProps.

Antes de TypeScript 2.1

Esto funciona de la misma manera pero no tiene Partialtipos, así que simplemente omita Partial<>y proporcione los valores predeterminados para todos los accesorios necesarios (a pesar de que esos valores predeterminados nunca se utilizarán) u omita la anotación de tipo explícito por completo.

Accesorios predeterminados con componentes funcionales

También puede usar defaultPropscomponentes de funciones, pero debe escribir su función en la interfaz FunctionComponent( StatelessComponenten la @types/reactversión anterior 16.7.2) para que TypeScript conozca defaultPropsla función:

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

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

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

Tenga en cuenta que no tiene que usarlo en Partial<PageProps>ningún lugar porque FunctionComponent.defaultPropsya está especificado como parcial en TS 2.1+.

Otra buena alternativa (esto es lo que uso) es desestructurar sus propsparámetros y asignar valores predeterminados directamente:

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

¡Entonces no necesitas defaultPropsnada! Tenga en cuenta que si usted no proporciona defaultPropsen un componente de la función que tendrá prioridad sobre los valores de los parámetros por defecto, porque Reaccionar siempre pasar explícitamente los defaultPropsvalores (por lo que los parámetros no están definidos, por lo tanto el parámetro por defecto no se usa nunca.) Así que tendría que utilizar uno u otro, no ambos.

Aaron Beall
fuente
2
El error suena como si estuvieras usando en <PageComponent>algún lugar sin pasar el fooaccesorio. Puede hacerlo opcional usando foo?: stringen su interfaz.
Aaron Beall
1
@Aaron Pero tsc arrojará un error de compilación, ya que defaultProps no implementa la interfaz PageProps. Debe hacer que todas las propiedades de la interfaz sean opcionales (incorrectas) o especificar el valor predeterminado también para todos los campos obligatorios (repetitivo innecesario) o evitar especificar el tipo en defaultProps.
pamelus
1
@adrianmoisa ¿Quieres decir accesorios por defecto? Sí, funciona, pero la sintaxis es diferente ... Agregaré un ejemplo a mi respuesta cuando vuelva a mi computadora ...
Aaron Beall
1
@AdrianMoisa Actualizado con el componente componente de función s
Aaron Beall
1
@ Jared Sí, debe ser public static defaultPropso static defaultProps( publices el valor predeterminado) para que todo (compilador y tiempo de ejecución de React) funcione correctamente. Puede funcionar en tiempo de ejecución con private static defaultPropssolo porque privatey publicno existe en tiempo de ejecución, pero el compilador no funcionaría correctamente.
Aaron Beall el
18

Con Typecript 2.1+, use Parcial <T> en lugar de hacer que las propiedades de su interfaz sean opcionales.

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

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

Con Typecript 3.0 hay una nueva solución para este 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 />

Tenga en cuenta que para que esto funcione se necesita una versión más reciente de @types/reactque 16.4.6. Funciona con 16.4.11.

CorayThan
fuente
¡Gran respuesta! ¿Cómo podría uno manejar: export interface Props { name?: string;}donde nombre es un accesorio opcional ? Sigo recibiendoTS2532 Object is possibly 'undefined'
Fydo
@Fydo No he necesitado tener un valor predeterminado para un accesorio opcional, ya que undefined es una especie de valor predeterminado automático para esos accesorios. ¿Desea poder pasar undefinedcomo valor explícito a veces, pero tiene otro valor predeterminado? ¿Has probado en su export interface Props {name: string | undefined;}lugar? No lo he intentado, solo una idea.
CorayThan
Agregar ?es lo mismo que agregar |undefined. Opcionalmente, quiero pasar el accesorio y dejar defaultPropsque se encargue de ese caso. Parece que esto aún no es posible en TS3. Solo usaré lo temidoname! sintaxis ya que sé que nunca es undefinedcuando defaultPropsse establecen. ¡Gracias de todos modos!
Fydo
1
¡Votado porque esta es la respuesta correcta ahora! También actualicé mi respuesta aceptada (que está comenzando a convertirse en un libro de historia) con esta nueva característica, y un poco más de explicación. :)
Aaron Beall
5

Para aquellos que tienen accesorios opcionales que necesitan valores predeterminados. Crédito aquí :)

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

De un comentario de @pamelus sobre la respuesta aceptada:

Debe hacer que todas las propiedades de la interfaz sean opcionales (incorrectas) o especificar el valor predeterminado también para todos los campos obligatorios (repetitivo innecesario) o evitar especificar el tipo en defaultProps.

En realidad, puede usar la herencia de interfaz de Typecript . El código resultante es solo un poco más detallado.

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

Para el componente funcional, preferiría mantener el propsargumento, así que aquí está mi solución:

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

Componente funcional

En realidad, para el componente funcional, la mejor práctica es la siguiente, creo un componente Spinner de muestra:

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
fuente