Ellen  Yundt

Ellen Yundt

1638453600

Cómo Usar Enumeraciones Y Objetos En TypeScript

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.

#typescript 

What is GEEK

Buddha Community

Cómo Usar Enumeraciones Y Objetos En TypeScript

The Definitive Guide to TypeScript & Possibly The Best TypeScript Book

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 🌹

Reviews

  • Thanks for the wonderful book. Learned a lot from it. (link)
  • Its probably the Best TypeScript book out there. Good Job (link)
  • Love how precise and clear the examples and explanations are! (link)
  • For the low, low price of free, you get pages of pure awesomeness. Chock full of source code examples and clear, concise explanations, TypeScript Deep Dive will help you learn TypeScript development. (link)
  • Just a big thank you! Best TypeScript 2 detailed explanation! (link)
  • This gitbook got my project going pronto. Fluent easy read 5 stars. (link)
  • I recommend the online #typescript book by @basarat you'll love it.(link)
  • I've always found this by @basarat really helpful. (link)
  • We must highlight TypeScript Deep Dive, an open source book.(link)
  • Great online resource for learning. (link)
  • Thank you for putting this book together, and for all your hard work within the TypeScript community. (link)
  • TypeScript Deep Dive is one of the best technical texts I've read in a while. (link)
  • Thanks @basarat for the TypeScript Deep Dive Book. Help me a lot with my first TypeScript project. (link)
  • Thanks to @basarat for this great #typescript learning resource. (link)
  • Guyz excellent book on Typescript(@typescriptlang) by @basarat (link)
  • Leaning on the legendary @basarat's "TypeScript Deep Dive" book heavily at the moment (link)
  • numTimesPointedPeopleToBasaratsTypeScriptBook++; (link)
  • A book not only for typescript, a good one for deeper JavaScript knowledge as well. link
  • In my new job, we're using @typescriptlang, which I am new to. This is insanely helpful huge thanks, @basarat! link
  • Thank you for writing TypeScript Deep Dive. I have learned so much. link
  • Loving @basarat's @typescriptlang online book basarat.gitbooks.io/typescript/# loaded with great recipes! link
  • Microsoft doc is great already, but if want to "dig deeper" into TypeScript I find this book of great value link
  • Thanks, this is a great book 🤓🤓 link
  • Deep dive to typescript is awesome in so many levels. i find it very insightful. Thanks link
  • @basarat's intro to @typescriptlang is still one of the best going (if not THE best) link
  •  
  • This is sweet! So many #typescript goodies! link

Get Started

If you are here to read the book online get started.

Translations

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.

Other Options

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.

Special Thanks

All the amazing contributors 🌹

Share

Share URL: https://basarat.gitbook.io/typescript/

Author: Basarat
Source Code: https://github.com/basarat/typescript-book/ 
License: View license

#typescript #opensource 

Ellen  Yundt

Ellen Yundt

1638453600

Cómo Usar Enumeraciones Y Objetos En TypeScript

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.

#typescript 

Ellen  Yundt

Ellen Yundt

1638504000

Cómo Usar Espacios De Nombres Y Módulos En TypeScript

🔥 🔥  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.

#typescript 

Saul  Alaniz

Saul Alaniz

1654310400

Cree Una Aplicación GraphQL En Node.js Con TypeScript Y Graphql-reques

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-requestbiblioteca para realizar operaciones de red en nuestro backend.

¿Por qué usar graphql-request y TypeScript?

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-requestentra 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:

  • Ligero: esta biblioteca tiene un poco más de 21 kilobytes minimizados, lo que garantiza que su aplicación se mantenga en funcionamiento
  • API basada en promesas: esto brinda soporte para aplicaciones asíncronas
  • Compatibilidad con TypeScript: graphql-requestes 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-expresspaquete para construir un servidor backend. Además, para la interfaz, usaremos Next y graphql-requestconsumiremos nuestra API GraphQL.

Construyendo nuestro servidor

Inicialización del proyecto

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 appcarpeta. Aquí, crea los siguientes archivos:

  • index.ts: Nuestro archivo principal. Esto ejecutará y ejecutará nuestro servidor Express GraphQL
  • dataset.ts: Esto servirá como nuestra base de datos, que se servirá al cliente
  • Resolvers.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 cliente

Al final, la estructura de carpetas debería verse así:

Creando nuestra base de datos

En esta sección, crearemos una base de datos ficticia que se utilizará para enviar los datos solicitados. Para hacerlo, vaya a app/dataset.tsy 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;
  • Primero, creamos una matriz de objetos llamadapeople
  • Esta matriz tendrá dos campos: idde tipo numbery namede tipostring

Definiendo nuestro esquema

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.tsarchivo. 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:

  • La Schemavariable contiene nuestro esquema GraphQL
  • Primero, creamos un Personesquema. Tendrá dos campos: idde tipo IDy namede tipoString
  • Más adelante, le indicamos a GraphQL que si el cliente ejecuta el getAllPeoplecomando, el servidor devolverá una matriz de Personobjetos
  • Además, si el usuario usa el getPersoncomando, GraphQL devolverá una sola Personinstancia

Creando resolutores

Ahora 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;
  • Aquí, creamos un Queryobjeto que maneja todas las consultas entrantes que van al servidor
  • Si el usuario ejecuta el getAllPeoplecomando, el programa devolverá todos los objetos presentes en nuestra base de datos
  • Además, el getPersoncomando requiere un argumento id. Esto devolverá una Personinstancia con el ID coincidente
  • Al final, exportamos nuestro resolver para que pudiera vincularse con nuestra aplicación.

Configurando nuestro servidor

¡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/graphqlURL.

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 getPersonconsulta:

query ($getPersonId: Int) { #the argument will be of type Integer
  getPerson(id: 1) {
    #get the person with the ID of 1
    name
    id
  }
}

Creando mutaciones

En el mundo de GraphQL, las mutaciones son comandos que tienen efectos secundarios en la base de datos. Ejemplos comunes de esto incluyen:

  • Agregar un usuario a la base de datos: cuando un cliente se registra en un sitio web, el usuario realiza una mutación para guardar sus datos en su base de datos
  • Editar o eliminar un objeto: si un usuario modifica o elimina datos de una base de datos, esencialmente está creando una mutación en el servidor.

Para manejar mutaciones, vaya a su Schema.tsmódulo. Aquí, dentro de la Schemavariable, 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.tsarchivo, 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
    },
  },
};
  • La addPersonmutación acepta un nameargumento.
  • Cuando namese pasa a, el programa creará un nuevo objeto con una nameclave coincidente
  • A continuación, utilizará el pushmétodo para agregar este objeto al conjunto de peopledatos .
  • Finalmente, devolverá las propiedades del nuevo objeto al cliente.

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

Construyendo nuestro cliente

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

Creación de variables de consulta

En esta sección, codificaremos nuestras consultas y mutaciones para ayudarnos a realizar operaciones GraphQL. Para hacerlo, vaya a constants.tsxy 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 };
  • En la primera parte, creamos la getAllPeopleQueryvariable. Cuando el usuario ejecuta esta consulta, el programa le indicará al servidor que obtenga todas las entradas presentes en la base de datos.
  • Más tarde, la addPersonmutación le dice a GraphQL que agregue una nueva entrada con su namecampo respectivo
  • Al final, usamos la exportpalabra clave para vincular nuestras variables con el resto del proyecto.

Realización de consultas

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:

  • En el getStaticPropsmétodo, le indicamos a Next que ejecute el getAllPeoplecomando en nuestro servidor GraphQL
  • Posteriormente, devolvimos su respuesta al Homecomponente funcional. Esto significa que ahora podemos mostrar el resultado en la interfaz de usuario.
  • A continuación, el programa usó el mapmétodo para representar todos los resultados del getAllPeoplecomando en la interfaz de usuario. Cada elemento de párrafo mostrará los namecampos de cada entrada
  • Además, también usamos un Linkcomponente para redirigir al usuario a la addpageruta. Esto permitirá al usuario agregar una nueva Personinstancia 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.

Realizando mutaciones

Ahora que hemos realizado con éxito una consulta, incluso podemos realizar mutaciones a través de la graphql-requestbiblioteca.

Dentro de su pagescarpeta, 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.tsxarchivo:

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 onSubmitcontrolador ubicado en pages/addpage.tsxel archivo de esta manera:

const onSubmit = async (data: any) => {
  const response = await request(
    "http://localhost:4000/graphql",
    addPersonMutation,
    data
  );
  console.log(response);
};
  • Aquí, estamos realizando una solicitud de mutación a nuestro servidor GraphQL a través de la requestfunción
  • Además, también pasamos el addPersoncomando de mutación a nuestro encabezado de solicitud. Esto le indicará a GraphQL que realice la addMutationacción en nuestro servidor

Este será el resultado:

¡Y hemos terminado!

Conclusión

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/

#graphql #typescript #nodejs 

Cómo usar genéricos en TypeScript

Introducción

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.

Sintaxis genérica

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 keysmatriz 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 agey extensionscon la pickObjectKeys()función. El valor de ageAndExtensionsserí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 Kse 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 Trepresente y keysen una matriz de cualquier tipo que Krepresente. Dado que Ten el caso de los languageconjuntos de objetos agecomo un número y extensionscomo una matriz de cadenas, ageAndExtensionsahora a la variable se le asignará el tipo de un objeto con propiedades age: numbery 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 keysparámetro según el objeto que proporcionó. Esto se muestra en la siguiente captura de pantalla:

Sugerencia de TypeScript basada en el tipo de objeto

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.

Usar genéricos con 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 identityfunció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.

Asignar parámetros genéricos

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);

resulttiene 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, resulttiene 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 Tde tipo genérico de la identityfunción sea de tipo number. Esto impondrá el numbertipo como argumento y valor de retorno.

Pasar parámetros de tipo directamente

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, resulttiene el tipo personalizado ProgrammingLanguageporque se pasa directamente a la identityfunción. Si no incluye el parámetro de tipo de forma explícita, resulttendrá 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 fetchApifunción será Promise<any>, que es el tipo de retorno de la json()llamada al responseobjeto de recuperación .

Tener anycomo tipo de retorno no es muy útil. anysignifica 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 ResultTypepará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 Promiseobjeto. El Promisetipo 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 fetchApifunció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 Usery usando una matriz de ese tipo ( User[]) como el tipo para el ResultTypeparámetro genérico. La datavariable ahora tiene el tipo en User[]lugar de any.

Nota: Como está utilizando awaitpara procesar de forma asincrónica el resultado de su función, el tipo de retorno será el tipo de Tin Promise<T>, que en este caso es el tipo genérico ResultType.
 

Parámetros de tipo predeterminados

Al crear su fetchApifunció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, ResultTypeestarí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 apropiedad teórica de data. Pero dado que el tipo de dataes 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 = DefaultTypejusto 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 ResultTypeparámetro genérico al llamar a la fetchApifunción, ya que tiene un tipo predeterminado de Record<string, any>. Esto significa que TypeScript reconocerá datacomo un objeto con claves de tipo stringy valores de tipo any, lo que le permitirá acceder a sus propiedades.

Restricciones de parámetros de tipo

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, stringifyObjectKeyValuesusa el reducemé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 extendspalabra clave. En este caso, Record<string, any>indica un objeto con claves de tipo stringy 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 stringtipo. 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 stringifyObjectKeyValuesfunció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 stringifiedValuestendrá 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.

Uso de genéricos con interfaces, clases y tipos

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.

Interfaces y clases genéricas

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 fieldcuyo 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 HttpApplicationclase 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 contextcuyo tipo se pasa como el tipo de argumento para la handlerfunción en el getmétodo. Durante el uso, el tipo de parámetro pasado al getcontrolador 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.someValueas boolean.

Tipos genéricos

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 Bserí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 Partialtipo, que toma un tipo Ty devuelve otro tipo con la misma forma que T, pero con todos sus campos configurados como opcionales. La implementación de se Partialve así:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

El tipo Partialaquí toma un tipo, itera sobre sus tipos de propiedad y luego los devuelve como opcionales en un nuevo tipo.

Nota: Dado Partialque ya está integrado en TypeScript, compilar este código en su entorno de TypeScript volvería a declarar Partialy arrojaría un error. La implementación de lo Partialcitado 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 ABCa DEFes 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 IfSameKeyThanParentTOtherwiseOtherTyperecibe 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. Tes 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, OtherTypees 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 ABCy é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.

Crear 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 booleanvalor. 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 Toperador se utiliza para devolver una unión con el nombre de todas las propiedades disponibles en T. Luego, está utilizando la K insintaxis 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 BooleanFieldstipo 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 trueun campo significa que desea que se devuelva y falseque desea que se omita.

Puede usar su BooleanFieldsgené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 booleantipo, 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, UserFetchOptionsserí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 readonlypropiedades. La implementación de este tipo se ve así:

type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

Nota: Dado Readonlyque ya está integrado en TypeScript, compilar este código en su entorno de TypeScript volvería a declarar Readonlyy arrojaría un error. La implementación de lo Readonlycitado aquí es solo para fines ilustrativos.
 

Observe el modificador readonlyque 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 readonlymodificador, 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.

Crear tipos condicionales con genéricos

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.

Estructura básica de la escritura condicional

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 IsStringTypeque 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 Textiende 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 truey 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 Ay B. El tipo Aes el tipo del literal de cadena "abc", mientras que el tipo Bes el tipo de un objeto que tiene una propiedad llamada namede tipo string. Luego está usando ambos tipos con su IsStringTypetipo condicional y almacenando el tipo resultante en dos nuevos tipos, ResultAy ResultB.

Si marca el tipo resultante de ResultAy ResultB, notará que el ResultAtipo está establecido en el tipo exacto truey que el ResultBtipo está establecido en false. Esto es correcto, ya Aque extiende el stringtipo y Bno extiende el stringtipo, ya que se establece en el tipo de un objeto con una única namepropiedad de tipo string.

Una característica útil de los tipos condicionales es que le permite inferir información de tipo dentro de la extendscláusula utilizando la palabra clave especial infer. Este nuevo tipo se puede utilizar en la truerama 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 GetReturnTypetipo 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 Textiende 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 truerama de la condición. El tipo de Uestará vinculado al tipo del valor de retorno de la función pasada. Si el tipo pasado Tno 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 typeofoperador para pasar el tipo de esta función al GetReturnTypegenérico y almacenar el tipo resultante en el ReturnTypeOfSomeFunctiontipo.

Como el tipo de su someFunctionvariable es una función, el tipo condicional evaluaría la truerama de la condición. Esto devolverá el tipo Ucomo resultado. El tipo Use 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 booleantipo.

Caso de uso de tipo condicional avanzado

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 SomeTypeque tiene una estructura de varios niveles de propiedades anidadas. Usando su NestedOmitgené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 NestedOmitque 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 KeysToOmitse 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 KeyPart1y contendrá todo lo que esté antes del primer punto. La segunda parte se asignará al tipo KeyPart2y contendrá todo después del primer punto. Si pasó "a.b.c"como KeysToOmit, inicialmente KeyPart1se establecería en el tipo de cadena exacto "a"y KeyPart2se establecería en "b.c".

A continuación, agregará el operador ternario para definir la primera truerama 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 Tpara verificar si KeyPart1es 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 Omitayudante que se envía de forma predeterminada con TypeScript. En este punto, KeyPart1no 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 Omitpara 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 Ttipo, utilice Omitpara eliminar solo KeyPart1de 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 NestedOmittipo de utilidad, pero ahora pasa como el primer parámetro de tipo el tipo de esta propiedad adentro Tusando T[NewKeys], y pasando como el segundo parámetro de tipo las claves restantes en notación de puntos , disponible en KeyPart2.

En la falserama de la condición interna, devuelve el tipo actual vinculado T, como si KeyPart1no 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 falserama de la condición externa, use el Omittipo de utilidad existente para omitir KeysToOmitde 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 KeysToOmitque no está usando notación de puntos y, por lo tanto, puede usar el Omittipo de utilidad existente .

Ahora, para usar su nuevo NestedOmittipo condicional, cree un nuevo tipo llamado NestedObject:

type NestedObject = {
  a: {
    b: {
      c: number;
      d: number;
    };
    e: number;
  };
  f: number;
};

Luego NestedOmitinvocalo 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, KeyPart1se infiere que el tipo de literal de cadena "a"y KeyPart2se 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 KeyPart1en este punto es una clave de T. KeyPart1ahora es "a"y Ttiene 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 truerama interna . Esto crea un nuevo tipo que es una intersección de otros dos tipos. El primer tipo es el resultado de utilizar el Omittipo de utilidad on Tpara omitir los campos que se pueden asignar a KeyPart1, en este caso, el acampo. El segundo tipo es un nuevo tipo que está construyendo llamando de forma NestedOmitrecursiva.

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 acampo. Esto recrea el acampo 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 bcampo, que es el tipo original con comitido. La evaluación ahora termina y TypeScript devuelve el nuevo tipo que desea usar, con el campo anidado omitido.

Conclusión

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