Skip to content

Latest commit

 

History

History
1344 lines (1053 loc) · 48.3 KB

AVANZADO.md

File metadata and controls

1344 lines (1053 loc) · 48.3 KB
react + ts logo

Cheatsheets para desarrolladores expertos en React que comienzan con TypeScript

Básico | Avanzado | Migrando | HOC | 中文翻译 | ¡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

  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 Roadmap

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

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

Esto está configurado para ti en tsdx.

Linting

⚠️ Nota que TSLint ahora está en mantenimiento y deberías intentar usar ESLint en su lugar. Si está interesado en los consejos de TSLint, consulte este PR desde @azdanov. El resto de esta sección solo se centra en ESLint.

⚠️ Este es un tema en evolución. typescript-eslint-parser ya no se mantiene y el trabajo ha comenzado recientemente sobretypescript-eslint en la comunidad ESLint para lleve ESLint con TSLint.

Siga los documentos de TypeScript + ESLint en https://github.com/typescript-eslint/typescript-eslint:

yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint

agregue un script lint a supackage.json:

  "scripts": {
    "lint": "eslint 'src/**/*.ts'"
  },

y en .eslintrc.json adecuado:

{
  "env": {
    "es6": true,
    "node": true,
    "jest": true
  },
  "extends": "eslint:recommended",
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "parserOptions": {
    "ecmaVersion": 2017,
    "sourceType": "module"
  },
  "rules": {
    "indent": ["error", 2],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "single"],
    "no-console": "warn",
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": [
      "error",
      { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }
    ],
    "no-empty": "warn"
  }
}

Esto está tomado de el tsdx PR que es para bibliotecas.

Más opciones de .eslintrc.json se pueden desear para aplicaciones:

{
  "extends": [
    "airbnb",
    "prettier",
    "prettier/react",
    "plugin:prettier/recommended",
    "plugin:jest/recommended",
    "plugin:unicorn/recommended"
  ],
  "plugins": ["prettier", "jest", "unicorn"],
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "env": {
    "es6": true,
    "browser": true,
    "jest": true
  },
  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  },
  "overrides": [
    {
      "files": ["**/*.ts", "**/*.tsx"],
      "parser": "typescript-eslint-parser",
      "rules": {
        "no-undef": "off"
      }
    }
  ]
}

Puede leer una guía de configuración más completa de TypeScript + ESLint aquí de Matterhorn, en particular consulte https://github.com/MatterhornDev/learn-typescript-linting.

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