
Cheatsheets para desarrolladores expertos en React que comienzan con TypeScript
Básico | Avanzado | Migrando | HOC | 中文翻译 | ¡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 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
-
- Escribir bibliotecas de TypeScript en lugar de aplicaciones
- Componentes Comentados
- Componentes Namespaced
- Desarrollo de Sistemas de Diseño
- Migrando desde Flow
- Prettier
- 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
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
- 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
https://github.com/Microsoft/TypeScript/wiki/Roadmap
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"
}
}
Esto está configurado para ti en tsdx.
⚠️ 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.
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í.