1638453600
En este curso typescript desde cero, vamos a empezar desde cero.
Este video es mucho más que un tutorial de TypeScript.
Aprenderás como instalar TypeScript, tipos básicos, inferencia de tipos y
la composición de tipos en TypeScript.
1654588030
TypeScript Deep Dive
I've been looking at the issues that turn up commonly when people start using TypeScript. This is based on the lessons from Stack Overflow / DefinitelyTyped and general engagement with the TypeScript community. You can follow for updates and don't forget to ★ on GitHub 🌹
If you are here to read the book online get started.
Book is completely free so you can copy paste whatever you want without requiring permission. If you have a translation you want me to link here. Send a PR.
You can also download one of the Epub, Mobi, or PDF formats from the actions tab by clicking on the latest build run. You will find the files in the artifacts section.
All the amazing contributors 🌹
Share URL: https://basarat.gitbook.io/typescript/
Author: Basarat
Source Code: https://github.com/basarat/typescript-book/
License: View license
1638453600
En este curso typescript desde cero, vamos a empezar desde cero.
Este video es mucho más que un tutorial de TypeScript.
Aprenderás como instalar TypeScript, tipos básicos, inferencia de tipos y
la composición de tipos en TypeScript.
1638504000
🔥 🔥 En TypeScript, cualquier archivo que contiene una import / export.
Los módulos se ejecutan dentro de su propio ámbito, y no en el ámbito global.
1654310400
En este artículo, creará una aplicación de pila completa utilizando GraphQL y Node.js en el backend. Mientras tanto, nuestro frontend usará la graphql-request
biblioteca para realizar operaciones de red en nuestro backend.
Cada vez que los desarrolladores construyen un servidor GraphQL usando Apollo, la biblioteca genera una "interfaz" que se ve así:
Esta interfaz permite a los usuarios realizar consultas o solicitudes de mutación al servidor a través de un código. Sin embargo, hablemos del elefante en la habitación: no parece muy fácil de usar. Dado que la interfaz no presenta ningún botón ni ningún elemento de interfaz útil, puede ser difícil para muchos usuarios navegar por su aplicación. En consecuencia, esto reduce su base de usuarios. Entonces, ¿cómo resolvemos este problema?
Aquí es donde graphql-request
entra en juego. Es una biblioteca de código abierto que permite a los usuarios realizar consultas en un servidor GraphQL. Cuenta con las siguientes características:
graphql-request
es una de las muchas bibliotecas que permite TypeScript. Una de las principales ventajas de Typescript es que permite un código estable y predecible.Por ejemplo, mira el siguiente programa:
let myNumber = 9; //here, myNumber is an integer
myNumber = 'hello'; //now it is a string.
myNumber = myNumber + 10; //even though we are adding a string to an integer,
//JavaScript won't return an error. In the real world, it might bring unexpected outputs.
//However, in Typescript, we can tell the compiler..
//what data types we need to choose.
let myNumber:number = 39; //tell TS that we want to declare an integer.
myNumber = 9+'hello'; //returns an error. Therefore, it's easier to debug the program
//this promises stability and security.
En este artículo, crearemos una aplicación de pila completa utilizando GraphQL y TypeScript. Aquí, usaremos el apollo-server-express
paquete para construir un servidor backend. Además, para la interfaz, usaremos Next y graphql-request
consumiremos nuestra API GraphQL.
Para inicializar un proyecto Node.js en blanco, ejecute estos comandos de terminal:
mkdir graphql-ts-tutorial #create project folder
cd graphql-ts-tutorial
npm init -y #initialize the app
Cuando termine, ahora tenemos que decirle a Node que necesitamos usar TypeScript en nuestra base de código:
#configure our Typescript:
npx tsc --init --rootDir app --outDir dist --esModuleInterop --resolveJsonModule --lib es6 --module commonjs --allowJs true --noImplicitAny true
mkdir app #our main code folder
mkdir dist #Typescript will use this folder to compile our program.
A continuación, instale estas dependencias:
#development dependencies. Will tell Node that we will use Typescript
npm install -d ts-node @types/node typescript @types/express nodemon
#Installing Apollo Server and its associated modules. Will help us build our GraphQL
#server
npm install apollo-server-express apollo-server-core express graphql
Después de este paso, navegue a su app
carpeta. Aquí, crea los siguientes archivos:
index.ts
: Nuestro archivo principal. Esto ejecutará y ejecutará nuestro servidor Express GraphQLdataset.ts
: Esto servirá como nuestra base de datos, que se servirá al clienteResolvers.ts
: Este módulo manejará los comandos del usuario. Aprenderemos sobre los resolutores más adelante en este artículo.Schema.ts
: como sugiere el nombre, este archivo almacenará los esquemas necesarios para enviar datos al clienteAl final, la estructura de carpetas debería verse así:
En esta sección, crearemos una base de datos ficticia que se utilizará para enviar los datos solicitados. Para hacerlo, vaya a app/dataset.ts
y escriba el siguiente código:
let people: { id: number; name: string }[] = [
{ id: 1, name: "Cassie" },
{ id: 2, name: "Rue" },
{ id: 3, name: "Lexi" },
];
export default people;
people
id
de tipo number
y name
de tipostring
Aquí, ahora crearemos un esquema para nuestro servidor GraphQL.
En pocas palabras, un esquema de GraphQL es una descripción del conjunto de datos que los clientes pueden solicitar desde una API. Este concepto es similar al de la biblioteca Mongoose .
Para crear un esquema, vaya al app/Schema.ts
archivo. Allí escribe el siguiente código:
import { gql } from "apollo-server-express"; //will create a schema
const Schema = gql`
type Person {
id: ID!
name: String
}
#handle user commands
type Query {
getAllPeople: [Person] #will return multiple Person instances
getPerson(id: Int): Person #has an argument of 'id` of type Integer.
}
`;
export default Schema;
//export this Schema so we can use it in our project
Desglosemos este código pieza por pieza:
Schema
variable contiene nuestro esquema GraphQLPerson
esquema. Tendrá dos campos: id
de tipo ID
y name
de tipoString
getAllPeople
comando, el servidor devolverá una matriz de Person
objetosgetPerson
comando, GraphQL devolverá una sola Person
instanciaAhora que hemos codificado nuestro esquema, nuestro siguiente paso es definir nuestros resolutores.
En términos simples, un resolver es un grupo de funciones que generan una respuesta para una consulta de GraphQL. En otras palabras, un resolver sirve como un controlador de consultas GraphQL.
En Resolvers.ts
, escribe el siguiente código:
import people from "./dataset"; //get all of the available data from our database.
const Resolvers = {
Query: {
getAllPeople: () => people, //if the user runs the getAllPeople command
//if the user runs the getPerson command:
getPerson: (_: any, args: any) => {
console.log(args);
//get the object that contains the specified ID.
return people.find((person) => person.id === args.id);
},
},
};
export default Resolvers;
Query
objeto que maneja todas las consultas entrantes que van al servidorgetAllPeople
comando, el programa devolverá todos los objetos presentes en nuestra base de datosgetPerson
comando requiere un argumento id
. Esto devolverá una Person
instancia con el ID coincidente¡Ya casi hemos terminado! Ahora que hemos creado tanto nuestro esquema como nuestro resolutor, nuestro siguiente paso es vincularlos.
En index.js
, escribe este bloque de código:
import { ApolloServer } from "apollo-server-express";
import Schema from "./Schema";
import Resolvers from "./Resolvers";
import express from "express";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
import http from "http";
async function startApolloServer(schema: any, resolvers: any) {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs: schema,
resolvers,
//tell Express to attach GraphQL functionality to the server
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
}) as any;
await server.start(); //start the GraphQL server.
server.applyMiddleware({ app });
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve) //run the server on port 4000
);
console.log(`Server ready at http://localhost:4000${server.graphqlPath}`);
}
//in the end, run the server and pass in our Schema and Resolver.
startApolloServer(Schema, Resolvers);
¡Vamos a probarlo! Para ejecutar el código, use este comando Bash:
npx nodemon app/index.ts
Esto creará un servidor en la localhost:4000/graphql
URL.
Aquí puede ver sus esquemas disponibles dentro de la interfaz de usuario:
¡Esto significa que nuestro código funciona!
Todas nuestras consultas de GraphQL irán dentro del panel de Operación . Para verlo en acción, escriba este fragmento dentro de este cuadro:
#make a query:
query {
#get all of the people available in the server
getAllPeople {
#procure their IDs and names.
id
name
}
}
Para ver el resultado, haga clic en el botón Ejecutar :
Incluso podemos buscar una entidad específica a través de la getPerson
consulta:
query ($getPersonId: Int) { #the argument will be of type Integer
getPerson(id: 1) {
#get the person with the ID of 1
name
id
}
}
En el mundo de GraphQL, las mutaciones son comandos que tienen efectos secundarios en la base de datos. Ejemplos comunes de esto incluyen:
Para manejar mutaciones, vaya a su Schema.ts
módulo. Aquí, dentro de la Schema
variable, agregue las siguientes líneas de código:
const Schema = gql`
#other code..
type Mutation {
#the addPerson commmand will accept an argument of type String.
#it will return a 'Person' instance.
addPerson(name: String): Person
}
`;
Nuestro próximo paso es crear un resolver para manejar esta mutación. Para hacerlo, dentro del Resolvers.ts
archivo, agregue este bloque de código:
const Resolvers = {
Query: {
//..further code..
},
//code to add:
//all our mutations go here.
Mutation: {
//create our mutation:
addPerson: (_: any, args: any) => {
const newPerson = {
id: people.length + 1, //id field
name: args.name, //name field
};
people.push(newPerson);
return newPerson; //return the new object's result
},
},
};
addPerson
mutación acepta un name
argumento.name
se pasa a, el programa creará un nuevo objeto con una name
clave coincidentepush
método para agregar este objeto al conjunto de people
datos .¡Eso es todo! Para probarlo, ejecute este código dentro de la ventana Operaciones :
#perform a mutation on the server
mutation($name: String) {
addPerson(name:"Hussain") { #add a new person with the name "Hussain"
#if the execution succeeds, return its 'id' and 'name` to the user.
id
name
}
}
Verifiquemos si GraphQL ha agregado la nueva entrada a la base de datos:
query {
getAllPeople { #get all the results within the 'people' database.
#return only their names
name
}
}
Hemos construido con éxito nuestro servidor. En esta sección, crearemos una aplicación cliente usando Next que escuchará al servidor y procesará datos en la interfaz de usuario.
Como primer paso, inicialice una aplicación Next.js en blanco así:
npx create-next-app@latest graphql-client --ts
touch constants.tsx #our query variables go here.
Para realizar operaciones GraphQL, utilizaremos la biblioteca graphql-request . Este es un módulo mínimo y de código abierto que nos ayudará a realizar mutaciones y consultas en nuestro servidor:
npm install graphql-request graphql
npm install react-hook-form #to capture user input
En esta sección, codificaremos nuestras consultas y mutaciones para ayudarnos a realizar operaciones GraphQL. Para hacerlo, vaya a constants.tsx
y agregue el siguiente código:
import { gql } from "graphql-request";
//create our query
const getAllPeopleQuery = gql`
query {
getAllPeople { #run the getAllPeople command
id
name
}
}
`;
//Next, declare a mutation
const addPersonMutation = gql`
mutation addPeople($name: String!) {
addPerson(name: $name) { #add a new entry. Argument will be 'name'
id
name
}
}
`;
export { getAllPeopleQuery, addPersonMutation };
getAllPeopleQuery
variable. Cuando el usuario ejecuta esta consulta, el programa le indicará al servidor que obtenga todas las entradas presentes en la base de datos.addPerson
mutación le dice a GraphQL que agregue una nueva entrada con su name
campo respectivoexport
palabra clave para vincular nuestras variables con el resto del proyecto.En pages/index.ts
, escribe el siguiente código:
import type { NextPage, GetStaticProps, InferGetStaticPropsType } from "next";
import { request } from "graphql-request"; //allows us to perform a request on our server
import { getAllPeopleQuery } from "../constants";
import Link from "next/link";
const Home: NextPage = ({
result, //extract the 'result' prop
}: InferGetStaticPropsType<typeof getStaticProps>) => {
return (
<div className={styles.container}>
{result.map((item: any) => { //render the 'result' array to the UI
return <p key={item.id}>{item.name}</p>;
})}
<Link href="/addpage">Add a new entry </Link>
</div>
);
};
//fetch data from the server
export const getStaticProps: GetStaticProps = async () => {
//the first argument is the URL of our GraphQL server
const res = await request("http://localhost:4000/graphql", getAllPeopleQuery);
const result = res.getAllPeople;
return {
props: {
result,
}, // will be passed to the page component as props
};
};
export default Home;
Aquí hay un desglose de este código pieza por pieza:
getStaticProps
método, le indicamos a Next que ejecute el getAllPeople
comando en nuestro servidor GraphQLHome
componente funcional. Esto significa que ahora podemos mostrar el resultado en la interfaz de usuario.map
método para representar todos los resultados del getAllPeople
comando en la interfaz de usuario. Cada elemento de párrafo mostrará los name
campos de cada entradaLink
componente para redirigir al usuario a la addpage
ruta. Esto permitirá al usuario agregar una nueva Person
instancia a la tabla .Para probar el código, ejecute el siguiente comando de terminal:
npm run dev
Este será el resultado:
Nuestro servidor GraphQL incluso se actualiza en tiempo real.
Ahora que hemos realizado con éxito una consulta, incluso podemos realizar mutaciones a través de la graphql-request
biblioteca.
Dentro de su pages
carpeta, cree un nuevo archivo llamado addpage.tsx
. Como sugiere el nombre, este componente permitirá al usuario agregar una nueva entrada a la base de datos. Aquí, comience escribiendo el siguiente bloque de código:
import type { NextPage, GetStaticProps, InferGetStaticPropsType } from "next";
import { request } from "graphql-request";
import { addPersonMutation } from "../constants";
const AddPage: NextPage = () => {
return (
<div>
<p>We will add a new entry here. </p>
</div>
);
};
export default AddPage;
En este fragmento de código, estamos creando una página en blanco con un fragmento de texto. Estamos haciendo esto para asegurarnos de que nuestro sistema de enrutamiento de URL funcione.
¡Esto significa que usamos el enrutamiento con éxito! A continuación, escribe este fragmento en tu addpage.tsx
archivo:
import { useForm } from "react-hook-form";
const { register, handleSubmit } = useForm();
//if the user submits the form, then the program will output the value of their input.
const onSubmit = (data: any) => console.log(data);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}> {/*Bind our handler to this form.*/}
{/* The user's input will be saved within the 'name' property */}
<input defaultValue="test" {...register("name")} />
<input type="submit" />
</form>
</div>
);
Esta será la salida:
Ahora que hemos capturado con éxito la entrada del usuario, nuestro último paso es agregar su entrada al servidor.
Para hacerlo, cambie el onSubmit
controlador ubicado en pages/addpage.tsx
el archivo de esta manera:
const onSubmit = async (data: any) => {
const response = await request(
"http://localhost:4000/graphql",
addPersonMutation,
data
);
console.log(response);
};
request
funciónaddPerson
comando de mutación a nuestro encabezado de solicitud. Esto le indicará a GraphQL que realice la addMutation
acción en nuestro servidorEste será el resultado:
¡Y hemos terminado!
Aquí está el código fuente completo de este proyecto.
En este artículo, aprendió a crear una aplicación completa con GraphQL y TypeScript. Ambas son habilidades extremadamente cruciales dentro del mundo de la programación, ya que tienen una gran demanda en la actualidad.
Si encontró alguna dificultad en este código, le aconsejo que deconstruya el código y juegue con él para que pueda comprender completamente este concepto.
Muchas Gracias Por Leer! ¡Feliz codificación!
Esta historia se publicó originalmente en https://blog.logrocket.com/build-graphql-app-node-js-typescript-graphql-request/
1637567653
Los genéricos son una característica fundamental de los lenguajes de tipado estático, lo que permite a los desarrolladores pasar tipos como parámetros a otro tipo, función u otra estructura. Cuando un desarrollador convierte su componente en un componente genérico, le da a ese componente la capacidad de aceptar y hacer cumplir la escritura que se pasa cuando se usa el componente, lo que mejora la flexibilidad del código, hace que los componentes sean reutilizables y elimina la duplicación.
TypeScript es totalmente compatible con los genéricos como una forma de introducir seguridad de tipos en componentes que aceptan argumentos y devuelven valores cuyo tipo será indeterminado hasta que se consuman más adelante en su código. En este tutorial, probará ejemplos del mundo real de genéricos de TypeScript y explorará cómo se usan en funciones, tipos, clases e interfaces . También utilizará genéricos para crear tipos mapeados y tipos condicionales, lo que le ayudará a crear componentes de TypeScript que tengan la flexibilidad de aplicarse a todas las situaciones necesarias en su código.
Todos los ejemplos que se muestran en este tutorial se crearon con la versión 4.2.3 de TypeScript.
Antes de entrar en la aplicación de genéricos, este tutorial primero analizará la sintaxis de los genéricos de TypeScript, seguido de un ejemplo para ilustrar su propósito general.
Los genéricos aparecen en el código TypeScript entre corchetes angulares, en el formato , donde representa un tipo pasado. se puede leer como un tipo genérico . En este caso, funcionará de la misma forma que los parámetros en las funciones, como marcadores de posición para un tipo que se declarará cuando se cree una instancia de la estructura. Por lo tanto, los tipos genéricos especificados entre paréntesis angulares también se conocen como parámetros de tipo genérico o simplemente parámetros de tipo . También pueden aparecer varios tipos genéricos en una sola definición, como .<T>T<T>TT<T, K, A>
Nota: Por convención, los programadores suelen utilizar una sola letra para nombrar un tipo genérico. Esta no es una regla de sintaxis, y puede nombrar genéricos como cualquier otro tipo en TypeScript, pero esta convención ayuda a transmitir inmediatamente a quienes leen su código que un tipo genérico no requiere un tipo específico.
Los genéricos pueden aparecer en funciones, tipos, clases e interfaces. Cada una de estas estructuras se tratará más adelante en este tutorial, pero por ahora se utilizará una función como ejemplo para ilustrar la sintaxis básica de los genéricos.
Para ver cuán útiles son los genéricos, imagine que tiene una función de JavaScript que toma dos parámetros: un objeto y una matriz de claves. La función devolverá un nuevo objeto basado en el original, pero solo con las claves que desee:
function pickObjectKeys(obj, keys) {
let result = {}
for (const key of keys) {
if (key in obj) {
result[key] = obj[key]
}
}
return result
}
Este fragmento muestra la pickObjectKeys()
función, que itera sobre la keys
matriz y crea un nuevo objeto con las claves especificadas en la matriz.
A continuación, se muestra un ejemplo que muestra cómo utilizar la función:
const language = {
name: "TypeScript",
age: 8,
extensions: ['ts', 'tsx']
}
const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])
Esto declara un objeto language
, luego aísla la propiedad age
y extensions
con la pickObjectKeys()
función. El valor de ageAndExtensions
sería el siguiente:
{
age: 8,
extensions: ['ts', 'tsx']
}
Si tuviera que migrar este código a TypeScript para que sea seguro para los tipos, tendría que usar genéricos. Puede refactorizar el código agregando las siguientes líneas resaltadas:
function pickObjectKeys<T, K extends keyof T>(obj: T, keys: K[]) {
let result = {} as Pick<T, K>
for (const key of keys) {
if (key in obj) {
result[key] = obj[key]
}
}
return result
}
const language = {
name: "TypeScript",
age: 8,
extensions: ['ts', 'tsx']
}
const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])
<T, K extends keyof T>
declara dos tipos de parámetros para la función, donde K
se asigna un tipo que es la unión de las claves en T
. A continuación obj
, el parámetro de función se establece en cualquier tipo que T
represente y keys
en una matriz de cualquier tipo que K
represente. Dado que T
en el caso de los language
conjuntos de objetos age
como un número y extensions
como una matriz de cadenas, ageAndExtensions
ahora a la variable se le asignará el tipo de un objeto con propiedades age: number
y extensions: string[]
.
Esto impone un tipo de retorno basado en los argumentos proporcionados pickObjectKeys
, lo que permite a la función la flexibilidad de imponer una estructura de tipificación antes de que sepa el tipo específico que necesita aplicar. Esto también agrega una mejor experiencia de desarrollador al usar la función en un IDE como Visual Studio Code, que creará sugerencias para el keys
parámetro según el objeto que proporcionó. Esto se muestra en la siguiente captura de pantalla:
Con una idea de cómo se crean los genéricos en TypeScript, ahora puede pasar a explorar el uso de genéricos en situaciones específicas. Este tutorial cubrirá primero cómo se pueden usar los genéricos en funciones.
Uno de los escenarios más comunes para usar genéricos con funciones es cuando tiene un código que no se escribe fácilmente para todos los casos de uso. Para que la función se aplique a más situaciones, puede incluir escritura genérica. En este paso, ejecutará un ejemplo de una identity
función para ilustrar esto. También explorará un ejemplo asincrónico de cuándo pasar parámetros de tipo a su genérico directamente y cómo crear restricciones y valores predeterminados para sus parámetros de tipo genérico.
Eche un vistazo a la siguiente función, que devuelve lo que se pasó como primer argumento:
function identity(value) {
return value;
}
Puede agregar el siguiente código para que la función sea segura para los tipos en TypeScript:
function identity<T>(value: T): T {
return value;
}
Convirtió su función en una función genérica que acepta el parámetro de tipo genérico T
, que es el tipo del primer argumento, luego configuró el tipo de retorno para que sea el mismo : T
.
A continuación, agregue el siguiente código para probar la función:
function identity<T>(value: T): T {
return value;
}
const result = identity(123);
result
tiene el tipo 123
, que es el número exacto que ingresó. TypeScript aquí infiere el tipo genérico del código de llamada en sí. De esta forma, el código de llamada no necesita pasar ningún parámetro de tipo. También puede ser explícito y establecer los parámetros de tipo genérico en el tipo que desee:
function identity<T>(value: T): T {
return value;
}
const result = identity<number>(123);
En este código, result
tiene el tipo number
. Al pasar el tipo con el <number>
código, le está haciendo saber explícitamente a TypeScript que desea que el parámetro T
de tipo genérico de la identity
función sea de tipo number
. Esto impondrá el number
tipo como argumento y valor de retorno.
Pasar parámetros de tipo directamente también es útil cuando se utilizan tipos personalizados. Por ejemplo, eche un vistazo al siguiente código:
type ProgrammingLanguage = {
name: string;
};
function identity<T>(value: T): T {
return value;
}
const result = identity<ProgrammingLanguage>({ name: "TypeScript" });
En este código, result
tiene el tipo personalizado ProgrammingLanguage
porque se pasa directamente a la identity
función. Si no incluye el parámetro de tipo de forma explícita, result
tendrá el tipo en su { name: string }
lugar.
Otro ejemplo que es común cuando se trabaja con JavaScript es el uso de una función contenedora para recuperar datos de una API:
async function fetchApi(path: string) {
const response = await fetch(`https://example.com/api${path}`)
return response.json();
}
Esta función asincrónica toma una ruta de URL como argumento, usa la API de recuperación para realizar una solicitud a la URL y luego devuelve un valor de respuesta JSON . En este caso, el tipo de retorno de la fetchApi
función será Promise<any>
, que es el tipo de retorno de la json()
llamada al response
objeto de recuperación .
Tener any
como tipo de retorno no es muy útil. any
significa cualquier valor de JavaScript y, al usarlo, está perdiendo la verificación de tipos estática, uno de los principales beneficios de TypeScript. Si sabe que la API devolverá un objeto con una forma determinada, puede hacer que esta función sea segura para los tipos mediante el uso de genéricos:
async function fetchApi<ResultType>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
El código resaltado convierte su función en una función genérica que acepta el ResultType
parámetro de tipo genérico. Este tipo genérico se utiliza en el tipo de retorno de la función: Promise<ResultType>
.
Nota: como es su función async
, debe devolver un Promise
objeto. El Promise
tipo TypeScript es en sí mismo un tipo genérico que acepta el tipo de valor al que se resuelve la promesa.
Si observa más de cerca su función, verá que el genérico no se está utilizando en la lista de argumentos o en cualquier otro lugar donde TypeScript pueda inferir su valor. Esto significa que el código de llamada debe pasar explícitamente un tipo para este genérico al llamar a su función.
A continuación, se muestra una posible implementación de la fetchApi
función genérica para recuperar datos de usuario:
type User = {
name: string;
}
async function fetchApi<ResultType>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi<User[]>('/users')
export {}
En este código, está creando un nuevo tipo llamado User
y usando una matriz de ese tipo ( User[]
) como el tipo para el ResultType
parámetro genérico. La data
variable ahora tiene el tipo en User[]
lugar de any
.
Nota: Como está utilizando await
para procesar de forma asincrónica el resultado de su función, el tipo de retorno será el tipo de T
in Promise<T>
, que en este caso es el tipo genérico ResultType
.
Al crear su fetchApi
función genérica como lo está haciendo, el código de llamada siempre debe proporcionar el parámetro de tipo. Si el código de llamada no incluye el tipo genérico, ResultType
estaría vinculado a unknown
. Tomemos, por ejemplo, la siguiente implementación:
async function fetchApi<ResultType>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return
response.json();
}
const data = await fetchApi('/users')
console.log(data.a)
export {}
Este código intenta acceder a una a
propiedad teórica de data
. Pero dado que el tipo de data
es unknown
, este código no podrá acceder a una propiedad del objeto.
Si no planea agregar un tipo específico a cada llamada de su función genérica, puede agregar un tipo predeterminado al parámetro de tipo genérico. Esto se puede hacer agregando = DefaultType
justo después del tipo genérico, así:
async function fetchApi<ResultType = Record<string, any>>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi('/users')
console.log(data.a)
export {}
Con este código, ya no es necesario que pase un tipo al ResultType
parámetro genérico al llamar a la fetchApi
función, ya que tiene un tipo predeterminado de Record<string, any>
. Esto significa que TypeScript reconocerá data
como un objeto con claves de tipo string
y valores de tipo any
, lo que le permitirá acceder a sus propiedades.
En algunas situaciones, un parámetro de tipo genérico debe permitir que solo ciertas formas pasen al genérico. Para crear esta capa adicional de especificidad para su genérico, puede poner restricciones en su parámetro.
Imagine que tiene una restricción de almacenamiento en la que solo se le permite almacenar objetos que tienen valores de cadena para todas sus propiedades. Para eso, puede crear una función que tome cualquier objeto y devuelva otro objeto con las mismas claves que el original, pero con todos sus valores transformados en cadenas. Se llamará a esta función stringifyObjectKeyValues
.
Esta función será una función genérica. De esta manera, puede hacer que el objeto resultante tenga la misma forma que el objeto original. La función se verá así:
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
return Object.keys(obj).reduce((acc, key) => ({
...acc,
[key]: JSON.stringify(obj[key])
}), {} as { [K in keyof T]: string })
}
En este código, stringifyObjectKeyValues
usa el reduce
método de matriz para iterar sobre una matriz de las claves originales, secuenciando los valores y agregándolos a una nueva matriz.
Para asegurarse de que el código de llamada siempre pase un objeto a su función, está utilizando una restricción de tipo en el tipo genérico T
, como se muestra en el siguiente código resaltado:
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
// ...
}
extends Record<string, any>
se conoce como restricción de tipo genérico y le permite especificar que su tipo genérico debe ser asignable al tipo que viene después de la extends
palabra clave. En este caso, Record<string, any>
indica un objeto con claves de tipo string
y valores de tipo any
. Puede hacer que su parámetro de tipo amplíe cualquier tipo válido de TypeScript.
Al llamar reduce
, el tipo de retorno de la función reductora se basa en el valor inicial del acumulador. El {} as { [K in keyof T]: string }
código establece el tipo del valor inicial del acumulador a { [K in keyof T]: string }
mediante el uso de un tipo fundido en un objeto vacío, {}
. El tipo { [K in keyof T]: string }
crea un nuevo tipo con las mismas claves que T
, pero con todos los valores establecidos para tener el string
tipo. Esto se conoce como tipo mapeado , que este tutorial explorará con más detalle en una sección posterior.
El siguiente código muestra la implementación de su stringifyObjectKeyValues
función:
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
return Object.keys(obj).reduce((acc, key) => ({
...acc,
[key]: JSON.stringify(obj[key])
}), {} as { [K in keyof T]: string })
}
const stringifiedValues = stringifyObjectKeyValues({ a: "1", b: 2, c: true, d: [1, 2, 3]})
La variable stringifiedValues
tendrá el siguiente tipo:
{
a: string;
b: string;
c: string;
d: string;
}
Esto asegurará que el valor de retorno sea consistente con el propósito de la función.
Esta sección cubrió múltiples formas de usar genéricos con funciones, incluida la asignación directa de parámetros de tipo y la creación de valores predeterminados y restricciones a la forma del parámetro. A continuación, analizará algunos ejemplos de cómo los genéricos pueden hacer que las interfaces y las clases se apliquen a más situaciones.
Al crear interfaces y clases en TypeScript, puede resultar útil utilizar parámetros de tipo genérico para establecer la forma de los objetos resultantes. Por ejemplo, una clase podría tener propiedades de diferentes tipos dependiendo de lo que se pasa al constructor. En esta sección, verá la sintaxis para declarar parámetros de tipo genérico en clases e interfaces y examinará un caso de uso común en aplicaciones HTTP.
Para crear una interfaz genérica, puede agregar la lista de parámetros de tipo justo después del nombre de la interfaz:
interface MyInterface<T> {
field: T
}
Esto declara una interfaz que tiene una propiedad field
cuyo tipo está determinado por el tipo pasado T
.
Para las clases, es casi la misma sintaxis:
class MyClass<T> {
field: T
constructor(field: T) {
this.field = field
}
}
Un caso de uso común de interfaces / clases genéricas es cuando tiene un campo cuyo tipo depende de cómo el código del cliente usa la interfaz / clase. Supongamos que tiene una HttpApplication
clase que se usa para manejar solicitudes HTTP a su API, y que algún valor de contexto se pasará a cada controlador de solicitudes. Una de esas formas de hacer esto sería:
class HttpApplication<Context> {
context: Context
constructor(context: Context) {
this.context = context;
}
// ... implementation
get(url: string, handler: (context: Context) => Promise<void>): this {
// ... implementation
return this;
}
}
Esta clase almacena un context
cuyo tipo se pasa como el tipo de argumento para la handler
función en el get
método. Durante el uso, el tipo de parámetro pasado al get
controlador se inferiría correctamente de lo que se pasa al constructor de la clase.
...
const context = { someValue: true };
const app = new HttpApplication(context);
app.get('/api', async () => {
console.log(context.someValue)
});
En esta implementación, TypeScript inferirá el tipo de context.someValue
as boolean
.
Después de haber analizado algunos ejemplos de genéricos en clases e interfaces, ahora puede pasar a crear tipos personalizados genéricos. La sintaxis para aplicar genéricos a tipos es similar a cómo se aplican a interfaces y clases. Eche un vistazo al siguiente código:
type MyIdentityType<T> = T
Este tipo genérico devuelve el tipo que se pasa como parámetro de tipo. Imagina que implementaste este tipo con el siguiente código:
...
type B = MyIdentityType<number>
En este caso, el tipo B
sería de tipo number
.
Los tipos genéricos se usan comúnmente para crear tipos de ayuda, especialmente cuando se usan tipos mapeados. TypeScript proporciona muchos tipos de ayudantes prediseñados. Un ejemplo es el Partial
tipo, que toma un tipo T
y devuelve otro tipo con la misma forma que T
, pero con todos sus campos configurados como opcionales. La implementación de se Partial
ve así:
type Partial<T> = {
[P in keyof T]?: T[P];
};
El tipo Partial
aquí toma un tipo, itera sobre sus tipos de propiedad y luego los devuelve como opcionales en un nuevo tipo.
Nota: Dado Partial
que ya está integrado en TypeScript, compilar este código en su entorno de TypeScript volvería a declarar Partial
y arrojaría un error. La implementación de lo Partial
citado aquí es solo para fines ilustrativos.
Para ver cuán poderosos son los tipos genéricos, imagine que tiene un objeto literal que almacena los costos de envío desde una tienda a todas las demás tiendas en su red de distribución comercial. Cada tienda estará identificada por un código de tres caracteres, así:
{
ABC: {
ABC: null,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}
Este objeto es una colección de objetos que representan la ubicación de la tienda. Dentro de cada ubicación de tienda, hay propiedades que representan el costo de envío a otras tiendas. Por ejemplo, el costo de envío de ABC
a DEF
es 12
. El costo de envío de una tienda a sí mismo es null
, ya que no habrá ningún envío.
Para asegurarse de que las ubicaciones de otras tiendas tengan un valor constante y de que una tienda se envíe siempre a sí misma null
, puede crear un tipo de ayuda genérico:
type IfSameKeyThanParentTOtherwiseOtherType<Keys extends string, T, OtherType> = {
[K in Keys]: {
[SameThanK in K]: T;
} &
{ [OtherThanK in Exclude<Keys, K>]: OtherType };
};
El tipo IfSameKeyThanParentTOtherwiseOtherType
recibe tres tipos genéricos. La primera Keys
, son todas las claves que desea asegurarse de que tenga su objeto. En este caso, es una unión de todos los códigos de las tiendas. T
es el tipo para cuando el campo del objeto anidado tiene la misma clave que la clave del objeto principal, que en este caso representa la ubicación de una tienda que se envía a sí mismo. Finalmente, OtherType
es el tipo para cuando la clave es diferente, representando un envío de tienda a otra tienda.
Puedes usarlo así:
...
type Code = 'ABC' | 'DEF' | 'GHI'
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType<Code, null, number> = {
ABC: {
ABC: null,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}
Este código ahora aplica la forma del tipo. Si establece alguna de las claves en un valor no válido, TypeScript nos dará un error:
...
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType<Code, null, number> = {
ABC: {
ABC: 12,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}
Dado que el costo de envío entre ABC
y él mismo ya no es null
, TypeScript arrojará el siguiente error:
OutputType 'number' is not assignable to type 'null'.(2322)
Ahora ha probado el uso de genéricos en interfaces, clases y tipos de ayuda personalizados. A continuación, explorará más un tema que ya ha surgido varias veces en este tutorial: la creación de tipos mapeados con genéricos.
Al trabajar con TypeScript, hay ocasiones en las que necesitará crear un tipo que debería tener la misma forma que otro tipo. Esto significa que debería tener las mismas propiedades, pero con el tipo de propiedades establecido en algo diferente. Para esta situación, el uso de tipos asignados puede reutilizar la forma de tipo inicial y reducir el código repetido en su aplicación.
En TypeScript, esta estructura se conoce como tipo mapeado y se basa en genéricos. En esta sección, verá cómo crear un tipo mapeado.
Imagine que desea crear un tipo que, dado otro tipo, devuelve un nuevo tipo donde todas las propiedades están configuradas para tener un boolean
valor. Puede crear este tipo con el siguiente código:
type BooleanFields<T> = {
[K in keyof T]: boolean;
}
En este tipo, está utilizando la sintaxis [K in keyof T]
para especificar las propiedades que tendrá el nuevo tipo. El keyof T
operador se utiliza para devolver una unión con el nombre de todas las propiedades disponibles en T
. Luego, está utilizando la K in
sintaxis para designar que las propiedades del nuevo tipo son todas las propiedades disponibles actualmente en el tipo de unión devuelto por keyof T
.
Esto crea un nuevo tipo llamado K
, que está vinculado al nombre de la propiedad actual. Esto se puede usar para acceder al tipo de esta propiedad en el tipo original usando la sintaxis T[K]
. En este caso, está configurando el tipo de propiedades para que sea un boolean
.
Un escenario de uso para este BooleanFields
tipo es la creación de un objeto de opciones. Imagina que tienes un modelo de base de datos, como un User
. Al obtener un registro para este modelo de la base de datos, también permitirá pasar un objeto que especifica qué campos devolver. Este objeto tendría las mismas propiedades que el modelo, pero con el tipo establecido en un booleano. Pasar true
un campo significa que desea que se devuelva y false
que desea que se omita.
Puede usar su BooleanFields
genérico en el tipo de modelo existente para devolver un nuevo tipo con la misma forma que el modelo, pero con todos los campos configurados para tener un boolean
tipo, como en el siguiente código resaltado:
type BooleanFields<T> = {
[K in keyof T]: boolean;
};
type User = {
email: string;
name: string;
}
type UserFetchOptions = BooleanFields<User>;
En este ejemplo, UserFetchOptions
sería lo mismo que crearlo así:
type UserFetchOptions = {
email: boolean;
name: boolean;
}
Al crear tipos asignados, también puede proporcionar modificadores para los campos. Un ejemplo es el tipo genérico existente disponible en TypeScript llamado Readonly<T>
. El Readonly<T>
tipo devuelve un nuevo tipo donde todas las propiedades del tipo pasado se establecen como readonly
propiedades. La implementación de este tipo se ve así:
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
Nota: Dado Readonly
que ya está integrado en TypeScript, compilar este código en su entorno de TypeScript volvería a declarar Readonly
y arrojaría un error. La implementación de lo Readonly
citado aquí es solo para fines ilustrativos.
Observe el modificador readonly
que se agrega como prefijo a la [K in keyof T]
parte en este código. Actualmente, los dos modificadores disponibles que se pueden usar en tipos mapeados son el readonly
modificador, que debe agregarse como prefijo a la propiedad, y el ?
modificador, que se puede agregar como sufijo a la propiedad. El ?
modificador marca el campo como opcional. Ambos modificadores pueden recibir un prefijo especial para especificar si el modificador debe eliminarse ( -
) o agregarse ( +
). Si solo se proporciona el modificador, +
se asume.
Ahora que puede usar tipos mapeados para crear nuevos tipos basados en formas de tipo que ya ha creado, puede pasar al caso de uso final para genéricos: escritura condicional.
En esta sección, probará otra característica útil de los genéricos en TypeScript: la creación de tipos condicionales. Primero, analizará la estructura básica de la escritura condicional. Luego, explorará un caso de uso avanzado creando un tipo condicional que omita los campos anidados de un tipo de objeto basado en la notación de puntos.
Los tipos condicionales son tipos genéricos que tienen un tipo resultante diferente según alguna condición . Por ejemplo, eche un vistazo al siguiente tipo genérico IsStringType<T>
:
type IsStringType<T> = T extends string ? true : false;
En este código, que está creando un nuevo tipo genérico llamado IsStringType
que recibe un único parámetro de tipo, T
. Dentro de la definición de su tipo, está utilizando una sintaxis que se ve como una expresión condicional utilizando el operador ternario en JavaScript: T extends string ? true : false
. Esta expresión condicional verifica si el tipo T
extiende el tipo string
. Si es así, el tipo resultante será el tipo exacto true
; de lo contrario, se establecerá en el tipo false
.
Nota: esta expresión condicional se evalúa durante la compilación. TypeScript solo funciona con tipos, así que asegúrese de leer siempre los identificadores dentro de una declaración de tipo como tipos, no como valores. En este código, está utilizando el tipo exacto de cada valor booleano true
y false
.
Para probar este tipo condicional, pase algunos tipos como su parámetro de tipo:
type IsStringType<T> = T extends string ? true : false;
type A = "abc";
type B = {
name: string;
};
type ResultA = IsStringType<A>;
type ResultB = IsStringType<B>;
En este código, está creando dos tipos A
y B
. El tipo A
es el tipo del literal de cadena "abc"
, mientras que el tipo B
es el tipo de un objeto que tiene una propiedad llamada name
de tipo string
. Luego está usando ambos tipos con su IsStringType
tipo condicional y almacenando el tipo resultante en dos nuevos tipos, ResultA
y ResultB
.
Si marca el tipo resultante de ResultA
y ResultB
, notará que el ResultA
tipo está establecido en el tipo exacto true
y que el ResultB
tipo está establecido en false
. Esto es correcto, ya A
que extiende el string
tipo y B
no extiende el string
tipo, ya que se establece en el tipo de un objeto con una única name
propiedad de tipo string
.
Una característica útil de los tipos condicionales es que le permite inferir información de tipo dentro de la extends
cláusula utilizando la palabra clave especial infer
. Este nuevo tipo se puede utilizar en la true
rama de la enfermedad. Un uso posible de esta función es recuperar el tipo de retorno de cualquier tipo de función.
Escriba el siguiente GetReturnType
tipo para ilustrar esto:
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;
En este código, está creando un nuevo tipo genérico, que es un tipo condicional llamado GetReturnType
. Este tipo genérico acepta un único parámetro de tipo, T
. Dentro de la declaración de tipo en sí, está verificando si el tipo T
extiende un tipo que coincide con una firma de función que acepta un número variable de argumentos (incluido cero), y luego está infiriendo el tipo de retorno de esa función creando un nuevo tipo U
, que está disponible para ser utilizado dentro de la true
rama de la condición. El tipo de U
estará vinculado al tipo del valor de retorno de la función pasada. Si el tipo pasado T
no es una función, el código devolverá el tipo never
.
Use su tipo con el siguiente código:
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;
function someFunction() {
return true;
}
type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>;
En este código, está creando una función llamada someFunction
, que devuelve true
. Luego está utilizando el typeof
operador para pasar el tipo de esta función al GetReturnType
genérico y almacenar el tipo resultante en el ReturnTypeOfSomeFunction
tipo.
Como el tipo de su someFunction
variable es una función, el tipo condicional evaluaría la true
rama de la condición. Esto devolverá el tipo U
como resultado. El tipo U
se infirió del tipo de retorno de la función, que en este caso es un boolean
. Si marca el tipo de ReturnTypeOfSomeFunction
, encontrará que está configurado correctamente para tener el boolean
tipo.
Los tipos condicionales son una de las funciones más flexibles disponibles en TypeScript y permiten la creación de algunos tipos de utilidades avanzadas. En esta sección, explorará uno de estos casos de uso creando un tipo condicional llamado NestedOmit<T, KeysToOmit>
. Este tipo de utilidad podrá omitir campos de un objeto, al igual que el Omit<T, KeysToOmit>
tipo de utilidad existente , pero también permitirá omitir campos anidados mediante el uso de la notación de puntos.
Usando su nuevo NestedOmit<T, KeysToOmit>
genérico, podrá usar el tipo como se muestra en el siguiente ejemplo:
type SomeType = {
a: {
b: string,
c: {
d: number;
e: string[]
},
f: number
}
g: number | string,
h: {
i: string,
j: number,
},
k: {
l: number,<F3>
}
}
type Result = NestedOmit<SomeType, "a.b" | "a.c.e" | "h.i" | "k">;
Este código declara un tipo nombrado SomeType
que tiene una estructura de varios niveles de propiedades anidadas. Usando su NestedOmit
genérico, pasa el tipo, luego enumera las claves de las propiedades que le gustaría omitir. Observe cómo puede usar la notación de puntos en el segundo parámetro de tipo para identificar las claves que se deben omitir. Luego, el tipo resultante se almacena en formato Result
.
La construcción de este tipo condicional utilizará muchas características disponibles en TypeScript, como tipos de literales de plantilla, genéricos, tipos condicionales y tipos mapeados.
Para probar este genérico, comience creando un tipo genérico llamado NestedOmit
que acepte dos parámetros de tipo:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string>
Se llama al primer parámetro de tipo T
, que debe ser un tipo que se pueda asignar al Record<string, any>
tipo. Este será el tipo de objeto del que desea omitir propiedades. Se llama al segundo parámetro de tipo KeysToOmit
, que debe ser de tipo string
. Utilizará esto para especificar las claves que desea omitir de su tipo T
.
A continuación, verifique si KeysToOmit
se puede asignar al tipo ${infer KeyPart1}.${infer KeyPart2}
agregando el siguiente código resaltado:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
Aquí, está utilizando un tipo de cadena de literal de plantilla mientras aprovecha los tipos condicionales para inferir otros dos tipos dentro del propio literal de plantilla. Al inferir dos partes del tipo de cadena literal de plantilla, está dividiendo la cadena en otras dos cadenas. La primera parte se asignará al tipo KeyPart1
y contendrá todo lo que esté antes del primer punto. La segunda parte se asignará al tipo KeyPart2
y contendrá todo después del primer punto. Si pasó "a.b.c"
como KeysToOmit
, inicialmente KeyPart1
se establecería en el tipo de cadena exacto "a"
y KeyPart2
se establecería en "b.c"
.
A continuación, agregará el operador ternario para definir la primera true
rama de la condición:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
Esto se usa KeyPart1 extends keyof T
para verificar si KeyPart1
es una propiedad válida del tipo dado T
. En caso de que tenga una clave válida, agregue el siguiente código para que la condición se evalúe en una intersección entre los dos tipos:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit<T, KeyPart1>
& {
[NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
}
Omit<T, KeyPart1>
es un tipo creado mediante el Omit
ayudante que se envía de forma predeterminada con TypeScript. En este punto, KeyPart1
no está en notación de puntos: contendrá el nombre exacto de un campo que contiene campos anidados que desea omitir del tipo original. Debido a esto, puede utilizar de forma segura el tipo de utilidad existente.
Está utilizando Omit
para eliminar algunos campos anidados que están dentro T[KeyPart1]
, y para hacerlo, debe reconstruir el tipo de T[KeyPart1]
. Para evitar la reconstrucción de todo el T
tipo, utilice Omit
para eliminar solo KeyPart1
de T
, conservando otros campos. Luego, está reconstruyendo T[KeyPart1]
en el tipo de la siguiente parte.
[NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
es un tipo mapeado donde las propiedades son las asignables KeyPart1
, lo que significa la parte de la que acaba de extraer KeysToOmit
. Este es el padre de los campos que desea eliminar. Si pasó a.b.c
, durante la primera evaluación de su condición lo sería NewKeys in "a"
. Luego, está configurando el tipo de esta propiedad para que sea el resultado de llamar de forma recursiva a su NestedOmit
tipo de utilidad, pero ahora pasa como el primer parámetro de tipo el tipo de esta propiedad adentro T
usando T[NewKeys]
, y pasando como el segundo parámetro de tipo las claves restantes en notación de puntos , disponible en KeyPart2
.
En la false
rama de la condición interna, devuelve el tipo actual vinculado T
, como si KeyPart1
no fuera una clave válida de T
:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit<T, KeyPart1>
& {
[NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
}
: T
Esta rama del condicional significa que está intentando omitir un campo que no existe en T
. En este caso, no es necesario ir más lejos.
Finalmente, en la false
rama de la condición externa, use el Omit
tipo de utilidad existente para omitir KeysToOmit
de Type
:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit<T, KeyPart1>
& {
[NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
}
: T
: Omit<T, KeysToOmit>;
Si la condición KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
es false
, significa KeysToOmit
que no está usando notación de puntos y, por lo tanto, puede usar el Omit
tipo de utilidad existente .
Ahora, para usar su nuevo NestedOmit
tipo condicional, cree un nuevo tipo llamado NestedObject
:
type NestedObject = {
a: {
b: {
c: number;
d: number;
};
e: number;
};
f: number;
};
Luego NestedOmit
invocalo para omitir el campo anidado disponible en a.b.c
:
type Result = NestedOmit<NestedObject, "a.b.c">;
En la primera evaluación del tipo condicional, la condición externa sería true
, ya que el tipo de literal de cadena "a.b.c"
se puede asignar al tipo de literal de plantilla `${infer KeyPart1}.${infer KeyPart2}`
. En este caso, KeyPart1
se infiere que el tipo de literal de cadena "a"
y KeyPart2
se infiere que el resto de la cadena, en este caso "b.c"
.
Ahora se evaluará la condición interna. Esto evaluará a true
, ya que KeyPart1
en este punto es una clave de T
. KeyPart1
ahora es "a"
y T
tiene una propiedad "a"
:
type NestedObject = {
a: {
b: {
c: number;
d: number;
};
e: number;
};
f: number;
};
Avanzando con la evaluación de la condición, ahora se encuentra dentro de la true
rama interna . Esto crea un nuevo tipo que es una intersección de otros dos tipos. El primer tipo es el resultado de utilizar el Omit
tipo de utilidad on T
para omitir los campos que se pueden asignar a KeyPart1
, en este caso, el a
campo. El segundo tipo es un nuevo tipo que está construyendo llamando de forma NestedOmit
recursiva.
Si pasa por la siguiente evaluación de NestedOmit
, para la primera llamada recursiva, el tipo de intersección ahora está creando un tipo para usar como el tipo del a
campo. Esto recrea el a
campo sin los campos anidados que necesita omitir.
En la evaluación final de NestedOmit
, se devolvería la primera condición false
, ya que el tipo de cadena pasado es "c"
ahora. Cuando esto sucede, omite el campo del objeto con el ayudante incorporado. Esto devolvería el tipo para el b
campo, que es el tipo original con c
omitido. La evaluación ahora termina y TypeScript devuelve el nuevo tipo que desea usar, con el campo anidado omitido.
En este tutorial, explorará los genéricos según se aplican a funciones, interfaces, clases y tipos personalizados. También usó genéricos para crear tipos mapeados y condicionales. Cada uno de estos hace que los genéricos sean una herramienta poderosa que tiene a su disposición cuando usa TypeScript. Usarlos correctamente le evitará repetir código una y otra vez, y hará que los tipos que ha escrito sean más flexibles. Esto es especialmente cierto si es un autor de una biblioteca y planea hacer que su código sea legible para una amplia audiencia.
#typescript