Skip to content

Latest commit

 

History

History
1463 lines (1126 loc) · 55 KB

AVANZADO.md

File metadata and controls

1463 lines (1126 loc) · 55 KB
react + ts logo

Cheatsheets para desarrolladores expertos en React que comienzan con TypeScript

Básico | Avanzado | Migrando | HOC | Inglés | 中文翻译 | Contribuir | Preguntas


Cheatsheet Avanzado

Esta Cheatsheet Avanzada ayuda a mostrar y explicar el uso avanzado de tipos genéricos para personas que escriben utilidades/funciones de tipo reutilizable/props de render/componentes de orden superior y bibliotecas TS+React.

  • También tiene varios consejos y trucos para usuarios profesionales.
  • Consejos para contribuir a DefinitelyTyped
  • El objetivo es aprovechar al máximo TypeScript.

Creación de bibliotecas React + TypeScript

La mejor herramienta para crear bibliotecas React + TS en este momento es tsdx. Ejecute npx tsdx create y seleccione la opción "react". Puede ver la Guía del usuario de React para obtener algunos consejos sobre las mejores prácticas y optimizaciones de la biblioteca React + TS para la producción.


Tabla de contenido para Cheatsheet Avanzado

Expandir tabla de contenido

Sección 0: Tipos de utilidad

Asumiremos el conocimiento de los tipos de utilidad cubiertos en la guía de typescript-utilities-guide del proyecto hermano. Busque bibliotecas incluidas allí también para sus necesidades de escritura.

Sección 1: Guías Avanzadas

Componentes de orden superior

Ahora hay una hoja de Cheatsheet de HOC dedicada que puede consultar para obtener algo de práctica sobre HOC.

Render Props

A veces querrás escribir una función que pueda tomar un elemento React o una cadena o algo más como Prop. El mejor tipo para usar en tal situación es React.ReactNode que se adapta a cualquier lugar normal, bueno, React Node encajaría:

export interface Props {
  label?: React.ReactNode;
  children: React.ReactNode;
}
export const Card = (props: Props) => {
  return (
    <div>
      {props.label && <div>{props.label}</div>}
      {props.children}
    </div>
  );
};

Si está utilizando una función como render prop con props.children:

export interface Props {
  children: (foo: string) => React.ReactNode;
}

Algo para agregar? Presentar un problema.

Componentes de Representación Condicional

Usar type guards!

// Props de botón
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  href?: undefined;
};

// Anchor props
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
  href?: string;
};

// Opciones de entrada / salida
type Overload = {
  (props: ButtonProps): JSX.Element;
  (props: AnchorProps): JSX.Element;
};

// Guard para verificar si existe href en props
const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps =>
  "href" in props;

// Componente
const Button: Overload = (props: ButtonProps | AnchorProps) => {
  // renderizado de anchor
  if (hasHref(props)) return <a {...props} />;
  // renderizado de botón
  return <button {...props} />;
};

// Uso
function App() {
  return (
    <>
      {/* 😎 Todo bien */}
      <Button target="_blank" href="https://www.google.com">
        Test
      </Button>
      {/* 😭 Error, `disabled` no existe en el elemento de anlaze */}
      <Button disabled href="x">
        Test
      </Button>
    </>
  );
}

Ver en TypeScript Playground

as props (pasando un componente para ser renderizado)

ElementType es bastante útil para cubrir la mayoría de los tipos que se pueden pasar a createElement, p.ej.

function PassThrough(props: { as: React.ElementType<any> }) {
  const { as: Component } = props;

  return <Component />;
}

Gracias @eps1lon por esto.

Componentes genéricos

Del mismo modo que puede crear funciones y clases genéricas en TypeScript, también puede crear componentes genéricos para aprovechar el sistema de tipos para la seguridad de tipos reutilizables. Tanto Props como State pueden aprovechar los mismos tipos genéricos, aunque probablemente tenga más sentido para Props que para State. Luego puede usar el tipo genérico para anotar los tipos de cualquier variable definida dentro del alcance de tufunción / clase.

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: Props<T>) {
  const { items, renderItem } = props;
  const [state, setState] = React.useState<T[]>([]); // Puede usar el tipo T en el alcance de la función Lista.
  return (
    <div>
      {items.map(renderItem)}
      <button onClick={() => setState(items)}>Clone</button>
      {JSON.stringify(state, null, 2)}
    </div>
  );
}

A continuación, puede utilizar los componentes genéricos y obtener una buena seguridad de tipos mediante la inferencia de tipos:

ReactDOM.render(
  <List
    items={["a", "b"]} // tipo de 'string' inferido
    renderItem={item => (
      <li key={item}>
        {item.toPrecision(3)} // Error: la propiedad 'toPrecision' no existe en
                 escriba 'string'.
      </li>
    )}
  />,
  document.body
);

A partir de TS 2.9, también puede proporcionar el parámetro de tipo en tuJSX para optar por la inferencia de tipos:

ReactDOM.render(
  <List<number>
    items={["a", "b"]} // Error: el tipo 'string' no es asignable para escribir 'number'.
    renderItem={item => <li key={item}>{item.toPrecision(3)}</li>}
  />,
  document.body
);

También puede usar genéricos usando el estilo de función de flecha de grande:

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

// Tenga en cuenta que <T se extiende desconocido> antes de la definición de la función.
// No puedes usar solo `<T>` ya que confundirá al analizador TSX ya sea una etiqueta JSX o una Declaración Genérica.
// También puedes usar <T,> https://github.com/microsoft/TypeScript/issues/15713#issuecomment-499474386
const List = <T extends unknown>(props: Props<T>) => {
  const { items, renderItem } = props;
  const [state, setState] = React.useState<T[]>([]); // Puedes usar el tipo T en el alcance de la función Lista.
  return (
    <div>
      {items.map(renderItem)}
      <button onClick={() => setState(items)}>Clone</button>
      {JSON.stringify(state, null, 2)}
    </div>
  );
};

Lo mismo para usar clases: (Crédito: al gist de Karol Majewski).

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

interface State<T> {
  items: T[];
}

class List<T> extends React.PureComponent<Props<T>, State<T>> {
  // Puedes usar el tipo T dentro de la clase Lista.
  state: Readonly<State<T>> = {
    items: []
  };
  render() {
    const { items, renderItem } = this.props;
    // Puedes usar el tipo T dentro de la clase Lista.
    const clone: T[] = items.slice(0);
    return (
      <div>
        {items.map(renderItem)}
        <button onClick={() => this.setState({ items: clone })}>Clone</button>
        {JSON.stringify(this.state, null, 2)}
      </div>
    );
  }
}

Aunque no puedes utilizar los parámetros de tipo genérico para miembros estáticos:

class List<T> extends React.PureComponent<Props<T>, State<T>> {
  // Los miembros estáticos no pueden hacer referencia a los parámetros de tipo de clase.ts(2302)
  static getDerivedStateFromProps(props: Props<T>, state: State<T>) {
    return { items: props.items };
  }
}

Para solucionar esto, debe convertir tu función estática en una función inferida de tipo:

class List<T> extends React.PureComponent<Props<T>, State<T>> {
  static getDerivedStateFromProps<T>(props: Props<T>, state: State<T>) {
    return { items: props.items };
  }
}

Componentes genéricos con children

children generalmente no se define como parte del tipo de props. A menos que children se defina explícitamente como parte del tipo props, un intento de usar props.children en JSX o en el cuerpo de la función fallará:

interface WrapperProps<T> {
  item: T;
  renderItem: (item: T) => React.ReactNode;
}

/* La propiedad 'children' no existe en el tipo 'WrapperProps <T>'. */
const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
  return (
    <div>
      {props.renderItem(props.item)}
      {props.children}
    </div>
  );
};

/*
Type '{ children: string; item: string; renderItem: (item: string) => string; }' no es asignable a tipo 'IntrinsicAttributes & WrapperProps<string>'.
 La propiedad 'children' no existe en el tipo'IntrinsicAttributes & WrapperProps<string>'.
*/

const wrapper = (
  <Wrapper item="test" renderItem={item => item}>
    {test}
  </Wrapper>
);

View in the TypeScript Playground

Para evitar eso, agregue children a la definición deWrapperProps (posiblemente reduzca tutipo, según sea necesario):

interface WrapperProps<T> {
  item: T;
  renderItem: (item: T) => React.ReactNode;
  children: string; // El componente solo aceptará una sola cadena strig child
}

const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
  return (
    <div>
      {props.renderItem(props.item)}
      {props.children}
    </div>
  );
};

o envuelva el tipo de los props en React.PropsWithChildren (this is what React.FC<> does):

interface WrapperProps<T> {
  item: T;
  renderItem: (item: T) => React.ReactNode;
}

const Wrapper = <T extends {}>(
  props: React.PropsWithChildren<WrapperProps<T>>
) => {
  return (
    <div>
      {props.renderItem(props.item)}
      {props.children}
    </div>
  );
};

Escribiendo un componente que acepta diferentes accesorios

Los componentes, y JSX en general, son análogos a las funciones. Cuando un componente puede renderizarse de manera diferente en función de sus props, es similar a cómo se puede sobrecargar una función para tener múltiples llamadas. Del mismo modo, puede sobrecargar las llamadas de un componente de función para enumerar todas sus diferentes "versiones".

Un caso de uso muy común para esto es representar algo como un botón o un ancla, en función de si recibe un atributo href.

type ButtonProps = JSX.IntrinsicElements["button"];
type AnchorProps = JSX.IntrinsicElements["a"];

// opcionalmente use un type guard personalizado
function isPropsForAnchorElement(
  props: ButtonProps | AnchorProps
): props is AnchorProps {
  return "href" in props;
}

function Clickable(props: ButtonProps | AnchorProps) {
  if (isPropsForAnchorElement(props)) {
    return <a {...props} />;
  } else {
    return <button {...props} />;
  }
}

Ni siquiera necesitan ser props completamente diferentes, siempre que tengan al menos una diferencia en las propiedades:

type LinkProps = Omit<JSX.IntrinsicElements["a"], "href"> & { to?: string };

function RouterLink(props: LinkProps | AnchorProps) {
  if ("to" in props) {
    return <a {...props} />;
  } else {
    return <Link {...props} />;
  }
}
Enfoque: Componentes Genéricos

Aquí hay una solución de ejemplo, vea la discusión adicional para otras soluciones. gracias a @jpavon

interface LinkProps {}
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<NavLinkProps, "href">;

const Link = <T extends {}>(
  props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
) => {
  if ((props as RouterLinkProps).to) {
    return <NavLink {...(props as RouterLinkProps)} />;
  } else {
    return <a {...(props as AnchorProps)} />;
  }
};

<Link<RouterLinkProps> to="/">Mi enlace</Link>; // bien
<Link<AnchorProps> href="/">Mi enlace</Link>; // bien
<Link<RouterLinkProps> to="/" href="/">
  Mi enlace
</Link>; // error
Enfoque: Composición

Si desea renderizar condicionalmente un componente, a veces es mejor usar React's composition model para tener componentes más simples y comprender mejor los tipos.

type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
type RouterLinkProps = Omit<AnchorProps, 'href'>

interface Button {
  as: React.ComponentClass | 'a'
}

const Button: React.FunctionComponent<Button> = (props) => {
  const {as: Component, children, ...rest} = props
  return (
    <Component className="button" {...rest}>{children}</Component>
  )
}

const AnchorButton: React.FunctionComponent<AnchorProps> = (props) => (
  <Button as="a" {...props} />
)

const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
  <Button as={NavLink} {...props} />
)

<LinkButton to="/login">Iniciar sesión</LinkButton>
<AnchorButton href="/login">Iniciar sesión</AnchorButton>
<AnchorButton href="/login" to="/test">Iniciar sesión</AnchorButton> // Error: la propiedad 'a' no existe en el tipo...

También es posible que desee utilizar Uniones discriminadas, consulte Expressive React Component APIs with Discriminated Unions.

Aquí hay una breve intuición para Tipos de Unión Discriminada:

type UserTextEvent = { value: string; target: HTMLInputElement };
type UserMouseEvent = { value: [number, number]; target: HTMLElement };
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
  if (typeof event.value === "string") {
    event.value; // string
    event.target; // HTMLInputElement | HTMLElement (!!!!)
    return;
  }
  event.value; // [number, number]
  event.target; // HTMLInputElement | HTMLElement (!!!!)
}

Even though we have narrowed based on event.value, the logic doesn't filter up and sideways to event.target. This is because a union type UserTextEvent | UserMouseEvent could be BOTH at once. So TypeScript needs a better hint. The solution is to use a literal type to tag each case of your union type:

type UserTextEvent = {
  type: "TextEvent";
  value: string;
  target: HTMLInputElement;
};
type UserMouseEvent = {
  type: "MouseEvent";
  value: [number, number];
  target: HTMLElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
  if (event.type === "TextEvent") {
    event.value; // string
    event.target; // HTMLInputElement
    return;
  }
  event.value; // [number, number]
  event.target; // HTMLElement
}

Para simplificar esto, también puede combinar esto con el concepto de User-Defined Type Guards.

function isString(a: unknown): a is string {
  return typeof a === "string";
}

Lea más sobre User-Defined Type Guards en esta guia.

Props: uno u otro pero no ambos

Use la palabra clave in, la sobrecarga de funciones y los tipos de unión para crear componentes que tengan uno u otro conjunto de props pero no ambos:

type Props1 = { foo: string };
type Props2 = { bar: string };

function MyComponent(props: Props1 | Props2) {
  if ("foo" in props) {
    // props.bar // error
    return <div>{props.foo}</div>;
  } else {
    // props.foo // error
    return <div>{props.bar}</div>;
  }
}
const UsageComponent: React.FC = () => (
  <div>
    <MyComponent foo="foo" />
    <MyComponent bar="bar" />
    {/* <MyComponent foo="foo" bar="bar"/> // invalid */}
  </div>
);

View in the TypeScript Playground

Lectura adicional: cómo prohibir pasar {} si tiene un tipo NoFields

Props: debe pasar ambos

type OneOrAnother<T1, T2> =
  | (T1 & { [K in keyof T2]?: undefined })
  | (T2 & { [K in keyof T1]?: undefined });

type Props = OneOrAnother<{ a: string; b: string }, {}>;

const a: Props = { a: "a" }; // error
const b: Props = { b: "b" }; // error
const ab: Props = { a: "a", b: "b" }; // ok

Gracias diegohaz

Props: opcionalmente, puedes pasar uno solo si se pasa el otro

Supongamos que desea un componente de texto que se acorta si se pasa el accesorio truncate pero se expande para mostrar el texto completo cuando se pasa el accesorioexpanded (por ejemplo, cuando el usuario hace clic en el texto).

Desea permitir que se pase expanded solo si también se pasa truncate, porque no hay uso para expanded si el texto no esta acortado.

Puede hacerlo mediante function overloads:

type CommonProps = {
  children: React.ReactNode;
  miscProps?: any;
};

type NoTruncateProps = CommonProps & { truncate?: false };

type TruncateProps = CommonProps & { truncate: true; expanded?: boolean };

// Function overloads acepta ambos tipos de props NoTruncateProps y TruncateProps
function Text(props: NoTruncateProps): JSX.Element;
function Text(props: TruncateProps): JSX.Element;
function Text(props: CommonProps & { truncate?: boolean; expanded?: boolean }) {
  const { children, truncate, expanded, ...otherProps } = props;
  const classNames = truncate ? ".truncate" : "";
  return (
    <div className={classNames} aria-expanded={!!expanded} {...otherProps}>
      {children}
    </div>
  );
}

Usando el componente de texto:

const App: React.FC = () => (
  <>
    {/* todos estos typecheck */}
    <Text>not truncated</Text>
    <Text truncate>truncated</Text>
    <Text truncate expanded>
      truncate-able but expanded
    </Text>
    {/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type '{ truncate: true; expanded?: boolean | undefined; }'. */}
    <Text expanded>truncate-able but expanded</Text>
  </>
);

Omitir atributo de un tipo

Nota: Omitir se agregó como una utilidad de primera clase en TS 3.5! 🎉

A veces, cuando se cruzan tipos, queremos definir nuestra propia versión de un atributo. Por ejemplo, quiero que mi componente tenga una etiqueta, pero el tipo con el que me estoy cruzando también tiene un atributo etiqueta. Aquí se explica cómo extraer ese tipo:

export interface Props {
  label: React.ReactNode; // esto entrará en conflicto con la etiqueta InputElement
}

// esto viene incorporado con TS 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// uso
export const Checkbox = (
  props: Props & Omit<React.HTMLProps<HTMLInputElement>, "label">
) => {
  const { label } = props;
  return (
    <div className="Checkbox">
      <label className="Checkbox-label">
        <input type="checkbox" {...props} />
      </label>
      <span>{label}</span>
    </div>
  );
};

Cuando tucomponente define múltiples accesorios, aumentan las posibilidades de esos conflictos. Sin embargo, puede indicar explícitamente que todos sus campos deben eliminarse del componente subyacente utilizando el operador keyof:

export interface Props {
  label: React.ReactNode; // entra en conflicto con la etiqueta de InputElement
  onChange: (text: string) => void; // entra en conflicto con onChange de InputElement
}

export const Textbox = (
  props: Props & Omit<React.HTMLProps<HTMLInputElement>, keyof Props>
) => {
  // implementar el componente Textbox ...
};

Type Zoo

Como puede ver en el ejemplo anterior de Omitir, también puede escribir una lógica significativa en sus tipos. type-zoo es un buen conjunto de herramientas de operadores que puede desear consultar (incluye Omitir), así como tipos de utilidad(especialmente para aquellos que migran desde Flow).

Extracción de tipos de Prop de un componente

(Contribuido por @ferdaber)

Hay muchos lugares donde desea reutilizar algunas piesas de props debido a props drilling, para que pueda exportar el tipo de accesorios como parte del módulo o extraerlos (de cualquier manera funciona).

La ventaja de extraer los tipos de props es que no necesitas exportar todo. Y el componente fuente se propagará a todos los componentes consumidores.

import { ComponentProps, JSXElementConstructor } from "react";

// va un paso más allá y se resuelve con las propiedades propTypes y defaultProps
type ApparentComponentProps<
  C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = C extends JSXElementConstructor<infer P>
  ? JSX.LibraryManagedAttributes<C, P>
  : ComponentProps<C>;

También puede usarlos para escribir controladores de eventos personalizados si no están escritos en los propios sitios de llamadas (es decir, en línea con el atributo JSX):

// my-inner-component.tsx
export function MyInnerComponent(props: {
  onSomeEvent(
    event: ComplexEventObj,
    moreArgs: ComplexArgs
  ): SomeWeirdReturnType;
}) {
  /* ... */
}

// my-consuming-component.tsx
export function MyConsumingComponent() {
  // event y moreArgs se escriben contextualmente junto con el valor de retorno
  const theHandler: Props<typeof MyInnerComponent>["onSomeEvent"] = (
    event,
    moreArgs
  ) => {};
  return <MyInnerComponent onSomeEvent={theHandler} />;
}

Manejo de Excepciones

Puede proporcionar buena información cuando suceden cosas malas.

class InvalidDateFormatError extends RangeError {}
class DateIsInFutureError extends RangeError {}

/**
 * // opcional docblock
 * @throws {InvalidDateFormatError} El usuario ingresó la fecha incorrectamente
 * @throws {DateIsInFutureError} El usuario ingresó la fecha en el futuro
 *
 */
function parse(date: string) {
  if (!isValid(date))
    throw new InvalidDateFormatError("no es un formato de fecha válido");
  if (isInFuture(date))
    throw new DateIsInFutureError("la fecha es en el futuro");
  // ...
}

try {
  // llamar a parse(date)) en alguna parte
} catch (e) {
  if (e instanceof InvalidDateFormatError) {
    console.error("formato de fecha inválido", e);
  } else if (e instanceof DateIsInFutureError) {
    console.warn("la fecha es en el futuro", e);
  } else {
    throw e;
  }
}

View in TypeScript Playground

Simplemente lanzar una excepción está bien, sin embargo, sería bueno hacer que TypeScript recuerde al consumidor de tu código para manejar tu excepción. Podemos hacerlo simplemente volviendo en lugar de lanzar:

function parse(
  date: string
): Date | InvalidDateFormatError | DateIsInFutureError {
  if (!isValid(date))
    return new InvalidDateFormatError("no es un formato de fecha válido");
  if (isInFuture(date))
    return new DateIsInFutureError("la fecha es en el futuro");
  // ...
}

// ahora el consumidor * tiene * para manejar los errores
let result = parse("mydate");
if (result instanceof InvalidDateFormatError) {
  console.error("invalid date format", result.message);
} else if (result instanceof DateIsInFutureError) {
  console.warn("date is in future", result.message);
} else {
  /// usa el resultado de forma segura
}

// alternativamente, puede manejar todos los errores
if (result instanceof Error) {
  console.error("error", result);
} else {
  /// usa el resultado de forma segura
}

También puede describir excepciones con tipos de datos de propósito especial (no diga mónadas ...) como los tipos de datos Try,Option (o Maybe) yEither:

interface Option<T> {
  flatMap<U>(f: (value: T) => None): None
  flatMap<U>(f: (value: T) => Option<U>): Option<U>
  getOrElse(value: T): T
}
class Some<T> implements Option<T> {
  constructor(private value: T) {}
  flatMap<U>(f: (value: T) => None): None
  flatMap<U>(f: (value: T) => Some<U>): Some<U>
  flatMap<U>(f: (value: T) => Option<U>): Option<U> {
    return f(this.value)
  }
  getOrElse(): T {
    return this.value
  }
}
class None implements Option<never> {
  flatMap<U>(): None {
    return this
  }
  getOrElse<U>(value: U): U {
    return value
  }
}

// ahora puedes usarlo como:
let result = Option(6) // Algunos <número>
              .flatMap(n => Option(n*3)) // Algunos <número>
              .flatMap(n = new None) // Nada
              .getOrElse(7)

// or:
let result = ask() // Opción<string>
              .flatMap(parse) // Option<Date>
              .flatMap(d => new Some(d.toISOString()) // Opción<string>
              .getOrElse('cadena de análisis de error')

Bibliotecas de Terceros

A veces, DefinitelyTyped se puede equivocar o no puede abordar tucaso de uso. Puedes declarar tu propio archivo con el mismo nombre de interfaz. Typecript fusionará interfaces con el mismo nombre.

Sección 2: Patrones útiles por versión de TypeScript

Las versiones de TypeScript a menudo introducen nuevas formas de hacer cosas; Esta sección ayuda a los usuarios actuales de React + TypeScript a actualizar las versiones de TypeScript y explorar los patrones comúnmente utilizados por las aplicaciones y bibliotecas TypeScript + React. Esto puede tener duplicaciones con otras secciones; si detecta alguna error, abre un issue.

Las guías de versiones de TypeScript anteriores a 2.9 no están escritas, ¡no dudes en enviar un PR! Además de la comunicación oficial del equipo de TS, también recomendamos el blog de Marius Schulz para las notas de la versión.

TypeScript 2.9

[Notas de la versión | Publicación del blog]

  1. Escriba argumentos para tagged template strings (por ejemplo, styled-components):
export interface InputFormProps {
  foo: string; // esto se entiende dentro de la cadena de plantilla a continuación
}

export const InputForm = styledInput<InputFormProps>`
    color:
        ${({ themeName }) => (themeName === "dark" ? "black" : "white")};
    border-color: ${({ foo }) => (foo ? "red" : "black")};
`;
  1. JSX Generics

microsoft/TypeScript#22415

Ayuda a escribir / usar componentes genéricos:

// en lugar de
<Formik render={(props: FormikProps<Values>) => ....}/>

// uso
<Formik<Values> render={props => ...}/>
<MyComponent<number> data={12} />

Más información: https://github.com/basarat/typescript-book/blob/master/docs/jsx/react.md#react-jsx-tip-generic-components

TypeScript 3.0

[Notas de la versión | Publicación del blog]

  1. Parametros rest con tipo para escribir argumentos de longitud variable:
// `rest` acepta cualquier numero de strings - incluso ninguno!
function foo(...rest: string[]) {
  // ...
}

foo("hello"); // funciona
foo("hello", "world"); // también funciona
  1. Soporte para propTypes ystatic defaultProps en JSX usando LibraryManagedAttributes:
export interface Props {
  name: string;
}

export class Greet extends React.Component<Props> {
  render() {
    const { name } = this.props;
    return <div>Hola ${name.toUpperCase()}!</div>;
  }
  static defaultProps = { name: "mundo" };
}

// ¡Verificaciones de tipo! ¡No se necesitan aserciones de tipo!
let el = <Greet />;
  1. new Unknown type

Para escribir API para forzar verificaciones de tipo, no específicamente relacionadas con React, pero muy útiles para tratar con respuestas de API:

interface IComment {
  date: Date;
  message: string;
}

interface IDataService1 {
  getData(): any;
}

let service1: IDataService1;
const response = service1.getData();
response.a.b.c.d; // ERROR DE TIEMPO DE EJECUCIÓN

// ----- comparar con -------

interface IDataService2 {
  getData(): unknown; // ooo
}

let service2: IDataService2;
const response2 = service2.getData();
// response2.a.b.c.d; // COMPILE TIME ERROR if you do this

if (typeof response === "string") {
  console.log(response.toUpperCase()); // `response` ahora tiene el tipo 'string'
}

También puede afirmar un tipo, o utilizar un protector de tipo contra un tipo desconocido. Esto es mejor que recurrir a any.

  1. Referencias de proyectos

Las referencias de proyecto permiten que los proyectos TypeScript dependan de otros proyectos TypeScript, específicamente, permitiendo que los archivos tsconfig.json hagan referencia a otros archivos tsconfig.json. Esto permite que las bases de código grandes escalen sin recompilar cada parte de la base de código cada vez, dividiéndola en múltiples proyectos.

En cada carpeta, cree un tsconfig.json que incluya al menos:

{
  "compilerOptions": {
    "composite": true, // le dice a TSC que es un subproyecto de un proyecto más grande
    "declaration": true, // archivos de declaración emmet .d.ts ya que las referencias de proyecto no tienen acceso a los archivos ts de origen. Importante para que referencias de proyectos pueda trabajar!
    "declarationMap": true, // mapas de origen para .d.ts
    "rootDir": "." // especifique compilarlo en relación con el proyecto raíz en.
  },
  "include": [
    "./**/*.ts
  ],
  "references": [ // (opcional) matriz de subproyectos de los que depende tusubproyecto
    {
      "path": "../myreferencedproject", // debe tener tsconfig.json
      "prepend": true // concatenar js y mapas fuente generados por este subproyecto, si y solo si se usa outFile
    }
  ]
}

y la raíz tsconfig.json que hace referencia al subproyecto de nivel superior:

{
  "files: [],
  "references": [
    {"path": "./proj1"},
    {"path": "./proj2"},
  ]
}

y debe ejecutar tsc --build o tsc -b.

Para guardar la repetitiva tsconfig, puede usar la opción extend:

{
  "extends": "../tsconfig.base",
  // otras cosas
}

TypeScript 3.1

[Notas de la versión | Publicación del blog]

  1. Declaraciones de propiedades sobre funciones

Adjuntar propiedades a funciones como esta "simplemente funciona" ahora:

export const FooComponent = ({ name }) => <div>Hola, soy {name}</div>;

FooComponent.defaultProps = {
  name: "laurosilvacom"
};

TypeScript 3.2

[Notas de la versión | Publicación del blog]

Nada específicamente relacionado con React.

TypeScript 3.3

[Notas de la versión | Publicación del blog]

Nada específicamente relacionado con React.

TypeScript 3.4

[Notas de la versión | Publicación del blog]

  1. const assertions
export function useLoading() {
  const [isLoading, setState] = React.useState(false);
  const load = (aPromise: Promise<any>) => {
    setState(true);
    return aPromise.finally(() => setState(false));
  };
  return [isLoading, load] as const; // infiere [boolean, typeof load] instead of (boolean | typeof load)[]
}

Más información sobre los lugares que puede usar aserciones const.

TypeScript 3.5

[Notas de la versión | Publicación del blog]

  1. ¡Tipo incorporado de <<Omit>> !!

  2. Inferencia de tipo de orden superior de constructores genéricos

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
  props: P;
  constructor(props: P);
}

declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;

type NestedProps<T> = { foo: number; stuff: T };

declare class GenericComponent<T> extends Component<NestedProps<T>> {}

// type ahora es  'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

Consulte también Notas de la actualización de Google a 3.5

TypeScript 3.6

[Notas de la versión | Publicación del blog]

Nada es particularmente específico de React, pero el playground recibió una actualización y Las clases Ambientales y Funciones pueden fusionarse

TypeScript 3.7

[Notas de la versión | Publicación del blog]

  1. Optional Chaining
let x = foo?.bar.baz();

// es equivalente a

let x = foo === null || foo === undefined ? undefined : foo.bar.baz();

// Optional Element access
function tryGetFirstElement<T>(arr?: T[]) {
  return arr?.[0];
}

// Llamada opcional
async function makeRequest(url: string, log?: (msg: string) => void) {
  log?.(`Request started at ${new Date().toISOString()}`);
  const result = (await fetch(url)).json();
  log?.(`Request finished at at ${new Date().toISOString()}`);
  return result;
}
  1. Fusión nula
let x = foo ?? bar();

// es equivalente a

let x = foo !== null && foo !== undefined ? foo : bar();

POR LO GENERAL DEBES USAR ?? SIEMPRE QUE USAS NORMALMENTE || a menos que realmente te refieras a falsedad:

function ShowNumber({ value }: { value: number }) {
  let _value = value || 0.5; // reemplazará 0 con 0.5 incluso si el usuario se refiere a 0
  // etc...
}
  1. Funciones de Aserción
function assert(condition: any, msg?: string): asserts condition {
  if (!condition) {
    throw new AssertionError(msg);
  }
}
function yell(str) {
  assert(typeof str === "string");

  return str.toUppercase();
  //         ~~~~~~~~~~~
  // error: La propiedad 'toUppercase' no existe en el tipo 'string'.
  //        Querías decir 'toUpperCase'?
}

También puede hacer assert sin una función personalizada You can also assert without a custom function:

function assertIsString(val: any): asserts val is string {
  if (typeof val !== "string") {
    throw new AssertionError("Not a string!");
  }
}
function yell(str: any) {
  assertIsString(str);

  // Ahora Typescript sabe que 'str' es de tipo 'string'
  return str.toUppercase();
  //         ~~~~~~~~~~~
  // error: La propiedad 'toUppercase' no existe el tipo 'string'.
  //        Querías decir 'toUpperCase'?
}
  1. ts-nocheck

Ahora puedes añadir // @ts-nocheck a la parte superior de los archivos Typescript! bueno para las migraciones.

TypeScript Roadmap y Especificaciones

https://github.com/Microsoft/TypeScript/wiki/Roadmap

¿Sabía también que puede leer la especificación de TypeScript en línea? https://github.com/microsoft/TypeScript/blob/master/doc/spec.md

Sección 3: Misceláneas

A veces, escribir React no se trata solo de React. Si bien no nos centramos en otras bibliotecas como Redux (ver más abajo para obtener más información al respecto), aquí hay algunos consejos sobre otras preocupaciones comunes al hacer aplicaciones con React + TypeScript.

Escribir bibliotecas de TypeScript en lugar de aplicaciones

propTypes puede parecer innecesario con TypeScript, especialmente cuando se compilan aplicaciones React + TypeScript, pero siguen siendo relevantes al escribir bibliotecas que pueden ser utilizadas por desarrolladores que trabajan en Javascript.

interface IMyComponentProps {
  autoHeight: boolean;
  secondProp: number;
}

export class MyComponent extends React.Component<IMyComponentProps, {}> {
  static propTypes = {
    autoHeight: PropTypes.bool,
    secondProp: PropTypes.number.isRequired
  };
}

Algo para agregar? Abre un issue.

Componentes comentados

TypeScript utiliza TSDoc, una variante de JSDoc para Typecript. Esto es muy útil para escribir bibliotecas de componentes y tener descripciones útiles emergentes en autocompletado y otras herramientas (como la tabla de documentos de Docz). Lo principal que debes recordar es usar la sintaxis /** YOUR_COMMENT_HERE * / en la línea justo encima de lo que esté anotando.

import React from "react";

interface MyProps {
  /** Descripción de la "etiqueta" de props.
   * @default foobar
   * */
  label?: string;
}

/**
 * Descripción general del componente en formato JSDoc. Markdown es *compatible*.
 */
export default function MyComponent({ label = "foobar" }: MyProps) {
  return <div>Hola mundo {label}</div>;
}

Ver TypeScript Playground

Algo hace falta? Abre un issue.

Componentes Namespaced

A menudo, cuando se crean componentes o componentes similares que tienen una relación parent-child, es útil asignar Namespaced a sus componentes. Los tipos se pueden agregar fácilmente usando Object.assign ();

import React from "react";

const Input = (props: any) => <input {...props} />;

const Form = React.forwardRef<HTMLDivElement, any>(
  ({ children, ...otherProps }, ref) => (
    <form {...otherProps} ref={ref}>
      {children}
    </form>
  )
);

/**
 * Los componentes exportados ahora se pueden usar como `<Form>` y `<Form.Input>`
 */
export default Object.assign(Form, { Input: Input });

Ver TypeScript Playground

(Contribuido por @bryceosterhaus, ver más discusión)

Algo hace falta? Abre un issue.

Desarrollo de Sistemas de Diseño

Me gusta Docz, que toma básicamente 1 línea de configuración para aceptar Typecript. Sin embargo, le falta mucho trabajo, se vienen muchos cambios importantes ya que todavía es <v1.0.

Para desarrollar con Storybook, lea los documentos que escribí aquí: https://storybook.js.org/configurations/typescript-config/. Esto incluye la generación automática de documentación de types, lo cual es increíble :)

Algo para agregar? Presentar un problema.

Migrando desde Flow

Debe consultar los proyectos grandes que están migrando desde Flow para obtener referencias and tips:

Bibliotecas útiles:

Si tiene consejos específicos en esta área, ¡presente un PR!

Algo hace falta? Abre un issue.

Prettier

No hay ningún secreto real para Prettier para TypeScript. ¡Pero es una gran idea correr Prettier en cada commit!

yarn add -D prettier husky lint-staged

// inside package.json
{
  //...
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "linters": {
      "src/*.{ts,tsx,js,jsx,css,scss,md}": [
        "prettier --trailing-comma es5 --single-quote --write",
        "git add"
      ],
      "ignore": [
        "**/dist/*, **/node_modules/*"
      ]
    }
  },
  "prettier": {
    "printWidth": 80,
    "semi": false,
    "singleQuote": true,
    "trailingComma": "es5"
  }
}

Integrar esto con ESlint puede ser un problema. Todavía no hemos escrito mucho sobre esto, por favor contribuya si tiene una opinión firme. Aquí hay un gist útil.

Esto está configurado para ti en tsdx.

Testing

¡Sí, puedes probar tus tipos! No debe usarlo para TODO, pero puede ayudar a prevenir regresiones:

Trabajar con bibliotecas que no son de TypeScript (escribe tu propio index.d.ts)

Digamos que deseas usar de-indedent, pero no está escrito o en DefinitelyTyped. Obtienes un error como este:

[ts]
Could not find a declaration file for module 'de-indent'. '/Users/swyx/Work/react-sfc-loader/node_modules/de-indent/index.js' implicitly has an 'any' type.
  Try `npm install @types/de-indent` if it exists or add a new declaration (.d.ts) file containing `declare module 'de-indent';` [7016]

Así que sea crea un archivo .d.ts en cualquier parte de tu proyecto con la definición del módulo:

// de-indent.d.ts
declare module "de-indent" {
  function deindent(): void;
  export = deindent; // exportación predeterminada
}
Más discusión

¿Algún otro consejo? ¡Por favor contribuya en este tema! mas referencias. Tenemos más discusión y ejemplos en nuestro issue aquí.

Sección 4: @types/react y @types/react-dom APIs

Los tipings @ types exportan tanto los tipos "públicos" destinados a su uso como los tipos "privados" que son para uso interno.

Consulte la Hoja de referencia de React TypeScript de SaltyCrane para obtener una buena referencia completa autogenerada.

@types/react

Enlace a .d.ts

Namespace: React

Interfaces y tipos más utilizados:

  • ReactNode - cualquier cosa que sea renderizabldentro de JSX, ¡esto NO es lo mismo que lo que puede representar un componente!
  • Component - clase base de todos los componentes basados en clases
  • PureComponent - clase base para todos los componentes optimizados basados en clases
  • FC, FunctionComponent - Una interfaz completa para componentes de funciones, a menudo utilizada para escribir componentes externos en lugar de escribir los tuyos
  • CSSProperties - usado para escribir objetos de estilo
  • all events: se usa para escribir controladores de eventos
  • all event handlers: se usa para escribir controladores de eventos
  • all consts: Children, Fragment, ... son todos públicos y reflejan el espacio de nombres React runtime

No se usa comúnmente pero es bueno saberlo

  • Ref - solía escribirse innerRef
  • ElementType - utilizado para componentes de orden superior u operaciones en componentes
  • ComponentType - utilizado para componentes de orden superior donde no se trata específicamente con los componentes intrínsecos
  • ReactPortal - se usa si necesita específicamente escribir un accesorio como portal, de lo contrario es parte de ReactNode
  • ComponentClass - Una interfaz completa para la función de constructor producida de una declaración de clase que extiende Componente, a menudo utilizada para escribir componentes externos en lugar de escribirla.
  • JSXElementConstructor - todo lo que TypeScript considere válido puede entrar en la etiqueta de apertura de una expresión JSX
  • ComponentProps - props de un componente
  • ComponentPropsWithRef - props de un componente donde una clase se base deun componentn ref prop con su propia instancia de type
  • ComponentPropsWithoutRef - accesorios de un componente sin el apoyo de ref
  • all methods: createElement, cloneElement, ... son todos públicos y reflejan la API de tiempo de ejecución de React

@Nota de Ferdaber: Desaliento el uso de la mayoría de los tipos ... Element debido a lo desconocido de JSX .Element. Casi siempre debes suponer que cualquier cosa producida por React.createElement es el tipo base React.ReactElement.

Namespace: JSX

  • Element - El tipo de cualquier expresión JSX
  • LibraryManagedAttributes - Especifica otros lugares donde los elementos JSX pueden declarar e inicializar tipos de props. Se usa para resolver defaultProps y propTypes estáticos con el tipo de accesorios internos de un componente.
  • IntrinsicElements - todos los componentes integrados posibles que se pueden escribir como un type en minúscula en JSX

No se usa comúnmente pero es bueno saberlo

  • IntrinsicAttributes conjunto de atributos que admiten todos los IntrinsicElements ... básicamente solo key.
  • ElementChildrenAttribute nombre de la propiedad que TS examina para determinar qué tipos de children admite un componente. Básicamente la propiedad children
  • ElementAttributesProperty nombre de la propiedad que TS observa para descubrir qué atributos admite un componente. Básicamente la propiedad props (para una instancia de clase)

No usar Interno/Desaprobado

Cualquier cosa que no esté en la lista anterior se considera de tipo interno y no público. Si no está seguro, puedes consultar la fuente de @types/react. Los tipos se anotan en consecuencia.

  • SFCElement
  • SFC
  • ComponentState
  • LegacyRef
  • StatelessComponent
  • ReactType

Agregando Atributos No Estandarizados

Los atributos permitidos en los componentes del host como button oimg siguen el Estándar de vida HTML. Nuevas características que aún no forman parte de la especificación o solo son implementados por ciertos navegadores, por lo tanto, causarán un error de tipo. Si tu específicamente escribes código para estos navegadores usa módulo de aumento para seguir comprobando el tipo de componentes sin tener que usar any o@ ts-ignore.

En este ejemplo, agregaremos el atributo loading que agrega soporte para lazy-loading imágenes en Chrome:

// react-unstable-attributes.d.ts
import "react";

declare module "react" {
  interface ImgHTMLAttributes<T> extends HTMLAttributes<T> {
    loading?: "auto" | "eager" | "lazy";
  }
}

@types/react-dom

Para ser escrito

Mi pregunta no se responde aquí!