Cheatsheets para desarrolladores expertos en React que comienzan con TypeScript
Básico | Avanzado | Migrando | HOC | Inglés | 中文翻译 | Contribuir | Preguntas
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.
- Asegúrese de consultar también la guía de
basarat
's para la configuración de la biblioteca tsconfig. - Desde el mundo angular, consulte https://github.com/bitjson/typescript-starter
Expandir tabla de contenido
- Sección 0: Tipos de utilidad
- Sección 1: Componentes reutilizables / Utilidades de tipos
- Componentes de orden superior
- Render Props
- Componentes de Representación Condicional
as
props (pasando un componente para ser renderizado- Componentes genéricos
- Escribiendo un componente que acepta diferentes accesorios
- Props: uno u otro pero no ambos
- Props: debe pasar ambos
- Props: opcionalmente, puedes pasar uno solo si se pasa el otro
- Omitir atributo de un tipo
- Type Zoo
- Extracción de tipos de Prop de un componente
- Manejo de excepciones
- Bibliotecas de Terceros
- Sección 2: Patrones útiles por versión de TypeScript
- Sección 3: Misceláneas
- Escribir bibliotecas de TypeScript en lugar de aplicaciones
- Componentes Comentados
- Componentes Namespaced
- Desarrollo de Sistemas de Diseño
- Migrando desde Flow
- Prettier
- Testing
- Linting
- Trabajar con bibliotecas que no son de TypeScript (escribe tu propio index.d.ts)
- Sección 4: @types/react y @types/react-dom APIs
- Agregando atributos no estandarizados
- @types/react-dom
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.
Ahora hay una hoja de Cheatsheet de HOC dedicada que puede consultar para obtener algo de práctica sobre HOC.
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.
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>
</>
);
}
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.
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 };
}
}
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>
);
};
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.
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
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
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>
</>
);
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 ...
};
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).
(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} />;
}
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;
}
}
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')
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.
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.
[Notas de la versión | Publicación del blog]
- 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")};
`;
- JSX Generics
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
[Notas de la versión | Publicación del blog]
- 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
- Soporte para
propTypes
ystatic defaultProps
en JSX usandoLibraryManagedAttributes
:
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 />;
- 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
.
- 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
}
[Notas de la versión | Publicación del blog]
- 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"
};
[Notas de la versión | Publicación del blog]
Nada específicamente relacionado con React.
[Notas de la versión | Publicación del blog]
Nada específicamente relacionado con React.
[Notas de la versión | Publicación del blog]
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.
[Notas de la versión | Publicación del blog]
-
¡Tipo incorporado de
<<Omit>>
!! -
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
[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
[Notas de la versión | Publicación del blog]
- 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;
}
- 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...
}
- 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'?
}
ts-nocheck
Ahora puedes añadir // @ts-nocheck
a la parte superior de los archivos Typescript! bueno para las migraciones.
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
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.
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.
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>;
}
Algo hace falta? Abre un issue.
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 });
(Contribuido por @bryceosterhaus, ver más discusión)
Algo hace falta? Abre un issue.
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.
Debe consultar los proyectos grandes que están migrando desde Flow para obtener referencias and tips:
Bibliotecas útiles:
- https://github.com/bcherny/flow-to-typescript
- https://github.com/Khan/flow-to-ts
- https://github.com/piotrwitek/utility-types
Si tiene consejos específicos en esta área, ¡presente un PR!
Algo hace falta? Abre un issue.
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.
¡Sí, puedes probar tus tipos! No debe usarlo para TODO, pero puede ayudar a prevenir regresiones:
- https://github.com/azz/jest-runner-tsc
- https://github.com/SamVerschueren/tsd
- https://github.com/ikatyang/dts-jest (Demo)
- https://github.com/microsoft/dtslint (Intro to dtslint
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í.
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.
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 clasesPureComponent
- clase base para todos los componentes optimizados basados en clasesFC
,FunctionComponent
- Una interfaz completa para componentes de funciones, a menudo utilizada para escribir componentes externos en lugar de escribir los tuyosCSSProperties
- 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 escribirseinnerRef
ElementType
- utilizado para componentes de orden superior u operaciones en componentesComponentType
- utilizado para componentes de orden superior donde no se trata específicamente con los componentes intrínsecosReactPortal
- se usa si necesita específicamente escribir un accesorio como portal, de lo contrario es parte deReactNode
ComponentClass
- Una interfaz completa para la función de constructor producida de una declaración de clase que extiendeComponente
, 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 JSXComponentProps
- props de un componenteComponentPropsWithRef
- props de un componente donde una clase se base deun componentnref
prop con su propia instancia de typeComponentPropsWithoutRef
- accesorios de un componente sin el apoyo deref
- 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 JSXLibraryManagedAttributes
- Especifica otros lugares donde los elementos JSX pueden declarar e inicializar tipos de props. Se usa para resolverdefaultProps
ypropTypes
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 losIntrinsicElements
... básicamente solokey
.ElementChildrenAttribute
nombre de la propiedad que TS examina para determinar qué tipos de children admite un componente. Básicamente la propiedadchildren
ElementAttributesProperty
nombre de la propiedad que TS observa para descubrir qué atributos admite un componente. Básicamente la propiedadprops
(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
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";
}
}
Para ser escrito