Cómo TypeScript te ayuda a escribir un código mejor

Cómo TypeScript te ayuda a escribir mejor código

TypeScript está tomando el control de la web. En este artículo te daré una descripción general de los beneficios de TypeScript y cómo puede ayudarte a crear sitios web con menos errores.

Aprenderás cómo TypeScript ayuda con el manejo de casos excepcionales, la detección de errores tipográficos, la refactorización del código y cómo los tipos hacen que el código sea más fácil de entender.

La última encuesta de State of JavaScript encontró que los desarrolladores pasan más tiempo escribiendo código TypeScript que JavaScript. La propia encuesta de GitHub establece una afirmación más modesta, diciendo que TypeScript es solo el cuarto lenguaje más utilizado en la plataforma, detrás de JavaScript, pero su uso ha crecido casi un 40% en un año. ¿Por qué está ocurriendo este cambio?

part0
Gráfico que muestra el porcentaje de tiempo que los desarrolladores dedican a escribir JavaScript frente a TypeScript

Si prefieres el formato de video, también puedes ver este artículo como video con un poco más de contenido.

¿Qué son los tipos?

Tal vez sepas que en JavaScript (al igual que en otros lenguajes de programación) existen diversos tipos de datos como cadenas de texto, números, arreglos, objetos, funciones, booleanos y valores indefinidos y nulos. Todos estos son tipos.

Pero JavaScript es de tipado dinámico, lo que significa que las variables pueden cambiar de tipo. Puedes asignar el valor de una variable a una cadena de texto en una línea y asignar la misma variable a un número en otra línea, por ejemplo:

let value = 'Hola';value = 3;

Por un lado, esto es una ventaja del lenguaje. Significa que es simple y muy flexible. No tienes que restringirte estableciendo tipos.

Pero por otro lado, puedes terminar complicándote si no tienes suficiente cuidado. Cuando escribes JavaScript, es tu responsabilidad usar el tipo correcto de valor en tu código.

Si accidentalmente utilizas tipos incorrectos, te encontrarás con errores. ¿Has intentado obtener la longitud de un valor indefinido? No lo intentes, te lanzará un error. Pero tal vez tuvieras un caso excepcional que cambió tu valor y no te diste cuenta:

value = 3;...console.log(value.length); // TypeError: No se pueden leer las propiedades de indefinido

Debido a que las variables pueden cambiar de tipo en cualquier momento, JavaScript solo verifica si tu código funcionará cuando lo ejecutes. No sabes que tienes un error hasta que ejecutas el código.

Y si solo tienes un error en un caso excepcional muy raro, tal vez ni siquiera te des cuenta de que tu código podría fallar durante mucho tiempo. Al escribir el código, JavaScript no te advierte que podrías tener un problema.

Por otro lado, TypeScript es de tipado estático. Esto significa que las variables no pueden cambiar de tipo. Esto hace que el código sea más predecible y permite que TypeScript analice el código mientras lo escribes y detecte errores a medida que ocurren (para que no aparezcan como errores en tu código más tarde).

Bien, ahora que tienes una comprensión básica de qué son los tipos y cómo JavaScript y TypeScript difieren en cómo los manejan, adentrémonos en la parte principal del tutorial.

Ya estás utilizando TypeScript

Revisemos lo básico: TypeScript te brinda información sobre los tipos. Te dice los tipos de tus variables, el tipo de los argumentos que necesitas pasar a tus funciones y qué tipo de datos devolverán.

Pero ¿qué pasa si te digo que ya estás utilizando TypeScript, incluso en JavaScript puro?

Vamos a ver este breve ejemplo. Este es un archivo de JavaScript puro en VS Code. Debido a que VS Code y TypeScript son ambos creados por Microsoft, VS Code ya viene con funciones integradas de TypeScript.

Grabación de pantalla de VS Code con un código JavaScript simple. En una línea hay una variable llamada 'input' que se establece con un valor de cadena de texto. La grabación muestra que cuando pasas el cursor sobre el nombre de la variable, en una ventana emergente de información se revela correctamente su tipo como cadena de texto. También muestra que otra variable llamada 'greeting' se establece con el valor de retorno de una función. Al pasar el cursor sobre este nombre de variable, también se muestra que su tipo es una cadena de texto. Por último, una tercera variable llamada 'greetingLength' se asigna a la longitud de 'greeting'. Al pasar el cursor sobre ella, se revela que su valor es un número.
Cuando pasas el cursor sobre tus variables, VS Code te muestra su tipo siempre que sea posible
function getGreeting(str) {	return `¡Hola ${str}!`;}let input = 'Bob'; // Tipo: stringlet greeting = getGreeting(input); // Tipo: stringlet greetingLength = greeting.length; // Tipo: numberconsole.log(greeting, greetingLength);

Si pasas el cursor sobre la variable input, VS Code te dirá que su tipo es string. Esto es bastante claro, ya que le asignamos un string en la misma línea.

Pero si vamos más allá y verificamos el tipo de greeting, también dice que es un string. Esto es un poco más interesante. El valor de greeting proviene de una función. ¿Cómo sabemos que es un string?

VS Code, con la ayuda de TypeScript, analiza la función, verifica cada posible retorno y concluye que lo único que esta función puede devolver es un string.

Por supuesto, este es un ejemplo muy simple. Pero incluso si tuviéramos una lógica más compleja con múltiples declaraciones de retorno diferentes, TypeScript seguiría analizando cada camino diferente y te diría cuáles son los posibles valores de retorno.

Para seguir un poco más con este ejemplo, si pasamos el cursor sobre la variable length, veremos que su tipo es un number. Esto puede parecer obvio nuevamente, pero la lógica detrás de esto es más inteligente de lo que parece.

La propiedad length de un string es de tipo number. Pero esto solo es verdad si estamos mirando un string. En este caso, sabemos que es un string porque TypeScript ya determinó que el tipo de retorno de nuestra función es un string. Hay varios pasos que ocurren detrás de escena.

Así que la primera razón por la que TypeScript es increíble: puedes ver los tipos de los valores con solo pasar el cursor sobre ellos. Y esto incluso funciona hasta cierto punto en JavaScript básico.

Verificación de los argumentos requeridos en funciones integradas

Veamos otro ejemplo. Este código todavía está en JavaScript, todavía no definimos los tipos explícitamente, pero el analizador de TypeScript todavía puede inferir los tipos.

Aquí tenemos un valor de entrada, nuevamente lo definimos como ‘bob’, y luego capitalizamos el string. Tomamos el primer carácter, lo convertimos a mayúscula, y luego agregamos el resto del string en minúsculas.

Captura de pantalla de VS Code con un código JavaScript simple. En el código, definimos una variable 'input', la capitalizamos y luego obtenemos su longitud. La grabación muestra que cuando pasas el cursor sobre las funciones utilizadas como charAt y toUpperCase, se revelan sus firmas.
Puedes verificar la firma de las funciones de JavaScript en VS Code
let input = 'bob';let formattedInput = 	input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();let formattedLength = formattedInput.length;console.log(greeting, greetingLength);

Podemos verificar la firma de estas funciones. Podemos ver que charAt requiere un parámetro de tipo number, toUpperCase no requiere ningún parámetro y la función slice tiene dos parámetros opcionales que pueden ser de tipo number o undefined.

Estas son anotaciones de TypeScript. Los signos de interrogación significan que un valor es opcional, y los signos de pipe indican que un tipo podría ser esto o aquello.

En resumen, sabemos que nuestro input formateado es de tipo string.

Donde JavaScript ya no puede inferir los tipos

Vamos a extraer la lógica que capitaliza nuestra entrada en una función. Los tipos deberían permanecer iguales, ¿verdad? Bueno, no exactamente. Ahora, nuestro input capitalizado es de tipo any.

VS Code con un código JavaScript simple. Primero, definimos una función simple que capitaliza un string, luego usamos esta función para capitalizar un string. La imagen muestra que cuando pasas el cursor sobre el valor de retorno, ahora su tipo es 'any'.
Después de haber extraído la lógica que capitaliza el input, el valor de retorno ahora es de tipo ‘any’

function capitalize(str) {    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput = capitalize(input); // Tipo: cualquier tipolet formattedLength = formattedInput.length;

¡Interesante! ¿Qué pasó? Nuestra función siempre debería devolver una cadena, ¿verdad? Si observamos el código completo en este caso específico, entonces sí. Debería devolver una cadena.

Pero tenemos que mirar la función de forma aislada. No podemos asumir lo que va a recibir. También podríamos pasarle un número y, en ese caso, fallaría. No se puede leer el primer carácter de un número ni convertirlo a mayúsculas o minúsculas.

En JavaScript no podemos indicar que necesitamos una cadena aquí, por lo que no podemos estar seguros de que la función devuelva una cadena. En TypeScript vamos a especificar el tipo de este parámetro para evitar confusiones.

Quizás notes que el ejemplo anterior con la función getGreeting era diferente. Allí, sin importar cuál fuera la entrada, la función siempre devolvía una string. Aquí, sin embargo, el resultado depende de la entrada.

¿Qué hay de los casos extremos en JavaScript?

¿Podríamos evitar obtener un error en caso de recibir una entrada incorrecta en JavaScript? ¡Sí!

Comprobar el tipo temprano y retornar antes de que la función pueda fallar es una forma de hacer que nuestra función sea segura ante fallos. Esto sigue siendo JavaScript y el operador typeof forma parte de JavaScript.

Ahora esta función no fallará. Pero introduje un error.

VS Code con el mismo archivo JavaScript simple que teníamos antes. Excepto que ahora nuestra función capitalize simplemente retorna si su argumento no es una cadena. La imagen muestra que el tipo del valor retornado cambió de any a string o undefined.
Después de agregar una declaración de retorno temprana a nuestra función capitalize, el tipo del valor retornado cambió de any a string o undefined
function capitalize(str) {    if (typeof str !== 'string') return;    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput = capitalize(input); // Tipo: string o undefinedlet formattedLength = formattedInput.length;

Si revisamos la firma de la función o el tipo del nuevo valor, ha cambiado de any a string | undefined.

En cierto sentido, eso es muy bueno. Limitamos los posibles valores de retorno y entendemos mejor lo que hace la función solo mirando el valor devuelto. Pero, por otro lado, en caso de que devuelva undefined, la aplicación fallará en la siguiente línea. No puedes comprobar la longitud de undefined.

Por supuesto, también podríamos haber devuelto una cadena vacía como alternativa y así no tendríamos este problema. Estamos trabajando con cadenas aquí. Pero este es un gran ejemplo de un tema que es muy fácil pasar por alto en JavaScript y que puede causarte muchos dolores de cabeza: los casos extremos.

¿Qué pasa si no escribiste la función capitalize y no sabes cómo funciona exactamente?

Tal vez también se encuentra en un archivo diferente y simplemente asumiste que siempre devolvería una cadena. Tal vez la función que usas es mucho más larga y complicada.

Tal vez verificaste la función, pero solo la última línea de ella y dijiste ‘De acuerdo, esta función devuelve una cadena’. Y te perdiste por completo que en una línea diferente, que también puede estar en medio de la función, hay otra declaración de retorno que devuelve un tipo de valor diferente.

El punto aquí es que los casos extremos pueden ocurrir y es muy fácil pasarlos por alto. Es una fuente típica de errores cuando los desarrolladores cubren la ruta feliz de una aplicación, pero son menos atentos al cubrir los casos extremos.

En JavaScript, debes tener en cuenta los casos extremos, probar diferentes escenarios y entradas de usuario, y escribir pruebas unitarias exhaustivas para asegurarte de que tu lógica no falle.

¿No sería genial si el editor te dijera que tienes un problema?

Convertir el Código a TypeScript

Después de toda esta introducción, finalmente convirtamos este código a TypeScript. Para hacerlo, simplemente cambiemos la extensión de .js a .ts y veamos qué sucede.

De inmediato, veremos un error que dice que nuestro formattedInput podría ser undefined. Y eso no funciona bien al obtener la longitud del valor. Ya descubrimos el error que causaba problemas antes y ni siquiera invertimos tiempo en agregar tipos a nuestro código.

VS Code con el mismo archivo que teníamos antes. Excepto que ahora cambiamos la extensión del archivo a TypeScript. La imagen muestra que aparece un nuevo error.
Después de cambiar la extensión a .ts, se revela un error que indica que nuestro valor en mayúscula podría ser indefinido
function capitalize(str) {    if (typeof str !== 'string') return;    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput = capitalize(input);let formattedLength = formattedInput.length; // Error

Antes de solucionar este error, activemos el modo strict. Por defecto, TypeScript puede ser muy permisivo, pero también obtenemos menos valor de él sin el modo strict.

Para ello, necesitamos un archivo tsconfig.json en la raíz de nuestro proyecto. Esto puede parecer un poco intimidante ahora, pero es probable que este archivo se genere automáticamente cuando creas un proyecto con cualquiera de los marcos de trabajo. Lo importante ahora es que tengamos el modo strict configurado como verdadero.

{    "compilerOptions": {      "target": "ESNext",      "module": "ESNext",      "strict": true,      "esModuleInterop": true,      "skipLibCheck": true,      "forceConsistentCasingInFileNames": true,      "lib": ["DOM", "ESNext"],      "moduleResolution": "node",      "noImplicitAny": true,      "allowSyntheticDefaultImports": true,      "baseUrl": "./src",      "paths": {        "*": ["src/*"]      },      "outDir": "./dist",      "rootDir": "./src",      "strictPropertyInitialization": false,      "noEmit": false,    },    "include": ["src/**/*.ts", "src/index.js"],    "exclude": ["node_modules"]  }

Esto mostrará más errores porque con esta configuración debemos definir los tipos de los argumentos de nuestra función.

VS Code con el mismo archivo TypeScript que antes. Ahora, con el modo estricto, aparece otro error que indica que debemos establecer el tipo de nuestro argumento de función.
Después de activar el modo estricto, debemos establecer el tipo de los argumentos de la función
function capitalize(str) { // Error    if (typeof str !== 'string') return;    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput = capitalize(input);let formattedLength = formattedInput.length; // Error

Entonces, especifiquemos que nuestra función capitalize requiere un string mediante el establecimiento del argumento como str: string. Y eso es todo lo que necesitamos agregar en este caso, porque es lo único que TypeScript no pudo deducir por sí mismo.

VS Code con el mismo archivo TypeScript que antes. Excepto que ahora establecimos el tipo del argumento de la función como string.
Estableciendo el tipo del argumento de la función como string
function capitalize(str: string) {    if (typeof str !== 'string') return;    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput = capitalize(input);let formattedLength = formattedInput.length; // Error

Un error común sobre TypeScript es que debes agregar tipos a todo. Aunque no es una mala práctica, no es estrictamente necesario. TypeScript es muy inteligente. Analiza el código y deduce los tipos de tantas cosas como sea posible.

Por supuesto, podemos especificar tipos en otros lugares. Podemos especificar que necesitamos que nuestro valor formattedInput sea un string mediante let formattedInput: string. Este es todo nuestro problema aquí. Pensamos que era un string, pero en algunos casos no lo era.

VS Code con el mismo archivo TypeScript que antes. Excepto que ahora la variable que establecimos como el valor devuelto de nuestra función capitalize debe ser un string. Esto revela que podría haber una incompatibilidad de tipos porque la función capitalize podría devolver indefinido.
Después de establecer el tipo de la variable ‘formattedInput’, el error cambió
function capitalize(str: string) {    if (typeof str !== 'string') return;    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput: string = capitalize(input); // Errorlet formattedLength = formattedInput.length;

Esto destaca nuestro problema de inmediato. Queremos que sea un string, pero nuestra función podría devolver undefined. Podemos leer en el popover que undefined no se puede asignar al tipo string.

Podemos ir más allá, diciendo que queremos que esta función devuelva un string. Esto de nuevo cambiará el error. Ahora el problema no es que no podamos asignar el valor devuelto a una variable string, sino que la función en sí misma devuelve un valor incorrecto.

VS Code con el mismo archivo TypeScript que antes. Excepto que ahora establecemos el tipo de retorno de nuestra función capitalize como string. Esto cambia nuestro error porque la función pude devolver undefined.
Después de establecer el tipo de retorno de la función capitalize, el error cambió de nuevo
function capitalize(str: string): string {    if (typeof str !== 'string') return; // Error    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}let input = 'bob';let formattedInput: string = capitalize(input);let formattedLength = formattedInput.length;

Para resolver esto, vamos a eliminar toda esta línea. Anteriormente agregamos esta línea para hacer la verificación de tipo, pero ahora dejamos que TypeScript haga toda la verificación de tipo por nosotros. La firma de la función ya indica que esta propiedad debe ser un string, no hay necesidad de verificarlo nuevamente. Nuestro código se simplificó y al mismo tiempo es más seguro.

Entonces, otra razón por la que TypeScript es increíble es que te obliga a pensar en situaciones límite. No solo pensar en ellas, sino también manejarlas. Algo que es muy fácil pasar por alto en JavaScript.

Refactorizando el código

Ahora que tenemos lo básico, pasemos a nuestro tercer tema: refactorización. Actualicemos un poco nuestra función de saludo y digamos que ahora toma dos argumentos: un nombre y un apellido. Imaginemos que esta es una función de utilidad que se utiliza en muchos lugares en un proyecto enorme y complejo:

VS Code con un archivo TypeScript simple. En este archivo, definimos una función 'getGreeting' que recibe dos argumentos, un nombre y un apellido. El tipo de ambos argumentos es un string.
Nuestra nueva función `getGreeting` actualizada que recibe un nombre y un apellido
export function getGreeting(firstName: string, lastName: string) {    const formattedFirstName = capitalize(firstName);    const formattedLastName = capitalize(lastName);    return `Hola ${formattedFirstName} ${formattedLastName}!`;}function capitalize(str: string): string {    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}

¿Qué pasa si decidimos que necesitamos refactorizar este código? En lugar de pasar dos strings, queremos pasar un objeto que tenga las propiedades de nombre y apellido.

En TypeScript, podemos definir precisamente la forma de un objeto. Podemos definir que tenemos que pasar un parámetro person que debe ser un objeto con una propiedad firstName y una propiedad lastName, ambas deben ser strings.

Podemos definir un tipo para este argumento. Decimos que tenemos un tipo Person con P mayúscula por convención. Este tipo describe un objeto con las propiedades firstName y lastName que son ambas strings.

Incluso podemos agregar más cosas si queremos, como agregar una propiedad birthday que tenga un tipo Date. Pero hagámoslo opcional porque no queremos ocuparnos de eso por ahora.

Agregando un signo de interrogación aquí hace que esta propiedad sea opcional. Podemos establecerla, pero no es obligatorio. Sin embargo, cuando intentamos usarla, tampoco podemos asumir que existe.

VS Code con el mismo archivo TypeScript. Excepto ahora definimos un tipo de persona personalizado así: 'type Person = { firstName: string, lastName: string, birthday?: Date }'. Luego, cambiamos los dos argumentos de la función a un único argumento 'person' con tipo 'Person'. Como resultado, el editor muestra errores en este archivo y también resalta en el explorador de archivos que los demás archivos tienen errores.
Después de cambiar el tipo de argumento de función, el editor resalta las partes que necesitamos cambiar
type Person = {    firstName: string,    lastName: string,    birthDay?: Date}export function getGreeting(person: Person) {    const formattedFirstName = capitalize(firstName);    const formattedLastName = capitalize(lastName);    return `¡Hola ${formattedFirstName} ${formattedLastName}!`;}function capitalize(str: string): string {    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}

Ahora podemos establecer que nuestro argumento person es de tipo Person.

Al hacer este cambio, el editor se ilumina en rojo. Dice que estoy tratando de usar variables que no existen. En esta función, me refiero a firstName y lastName, y ahora solo hay un objeto person.

Incluso más, los demás archivos en el explorador de archivos también se iluminan, diciendo que llamé a la función con dos argumentos aunque solo espera uno.

Vamos a corregir el error en este archivo y reemplazar firstName y lastName con person.firstName y person.lastName. TypeScript es muy exigente con el uso de variables que no existen.

Para darte un ejemplo aún mejor: ¿Qué pasaría si cometo un error tipográfico aquí? ¿Y si me falta una letra de firstName? Esto podría ser un problema muy fácil de pasar por alto en JavaScript. Aquí, sin embargo, no solo se resalta que no existe esa propiedad en un objeto Person, sino que incluso sugiere que tal vez quieras usar firstName en su lugar.

VS Code con el mismo archivo TypeScript. Excepto que ahora actualizamos la función para usar el nuevo argumento de función. Por error, cometimos un error tipográfico. El editor resalta el error y sugiere una corrección.
Si cometemos un error tipográfico, el editor sugiere correcciones
type Person = {    firstName: string,    lastName: string,    birthDay?: Date}export function getGreeting(person: Person) {    const formattedFirstName = capitalize(person.frstName);    const formattedLastName = capitalize(person.lastName);    return `¡Hola ${formattedFirstName} ${formattedLastName}!`;}function capitalize(str: string): string {    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();}

Luego, corrijamos los errores en los demás archivos. Como puedes ver, los archivos con error ya están resaltados en el explorador de archivos. Este es, por supuesto, un ejemplo muy simple, pero imagina que tienes un proyecto enorme y esta función se llama en cien lugares diferentes. Por supuesto, puedes ser minucioso y corregirlos uno por uno, pero en JavaScript sería muy fácil omitir uno de ellos.

VS Code con otro archivo TypeScript que llama a nuestra función previamente definida. Al actualizar la llamada a la función de la firma anterior de dos argumentos a nuestra nueva firma, TypeScript muestra errores relevantes si cometemos errores.
Cuando actualizamos las llamadas a la función, TypeScript muestra errores si cometemos errores
import { getGreeting } from "./utils";let greeting = getGreeting({ firstName: 'bob', lastName:  'marley' });console.log(greeting);

Ahora aquí, el error dice que estamos pasando dos parámetros cuando solo se espera uno. Si simplemente eliminamos el segundo argumento, dice que estamos pasando una cadena de texto, pero se esperaba un objeto de tipo Person. Si pasamos un objeto solo con el nombre, aún se quejará de que falta el apellido. Si agregamos el apellido, pero nuevamente con un error tipográfico, nuevamente dirá que tenemos una propiedad incorrecta e incluso sugiere que podríamos haber cometido un error tipográfico aquí.

TypeScript es muy preciso en cuanto a cuál es nuestro problema y podemos entender fácilmente cómo solucionarlo.

Ahora corrijamos el otro archivo. En lugar de ingresar el argumento en línea, también podemos definirlo como una variable y TypeScript reconocerá que un objeto de esta forma cumple con los criterios de esta función.

Si queremos asegurarnos de que nuestra variable sea de tipo Person, también podemos importar el tipo y asignarlo a este objeto. Primero, debemos exportarlo en el archivo de utilidades, y luego podemos importarlo igual que nuestra función, y luego asignarlo a nuestro objeto.

VS Code con otro archivo TypeScript llamando a nuestra función previamente definida. Esta vez no incrustamos el argumento de la función, sino que creamos una nueva variable para ello. Al establecer el tipo de este argumento, reutilizamos el tipo 'Person' definido anteriormente.
También podemos usar nuestro tipo Person en otros archivos
import { Person, getGreeting } from "./utils";let person: Person = {    firstName: 'bob',    lastName: 'dylan'}let greeting = getGreeting(person);console.log(greeting);

Resumen

TypeScript puede ser mucho más complicado que esto. Pero eso es lo básico. En su mayor parte, defines tus propios tipos y TypeScript se asegura de que los uses correctamente.

En resumen, hay tres razones principales por las que deberías considerar usar TypeScript:

  1. Obtienes información de tipo sobre las funciones
  2. Sabes lo que devuelven
  3. Sabes lo que esperan de ti, incluso sin mirar la propia función

TypeScript se asegura de que conectes correctamente tu aplicación. Te obliga a llamar a las funciones con los argumentos correctos y te obliga a pensar en casos extremos.

TypeScript ayuda mucho durante la refactorización. No te permite pasar por alto ninguna parte de tu código que necesites cambiar y no te permite cometer errores tipográficos.

Suscríbete para obtener más tutoriales sobre desarrollo web:

Hunor Márton BorbélyDesarrollo de juegos con JavaScript, tutoriales de codificación creativa, HTML canvas, SVG, Three.js y algo de React y Vue https://twitter.com/HunorBorbelyhttps://codepen.io/HunorMarton…favicon_144x144YouTubeAPkrFKaQ34YAITK6J0qgy6Iv6pms35dPhF68Hyy7BoYoLA=s900-c-k-c0x00ffffff-no-rj

Leave a Reply

Your email address will not be published. Required fields are marked *