El manual React sigue la regla 80/20: aprende en el 20% del tiempo el 80% de un tema.
Encuentro que este enfoque ofrece una descripción completa. Este libro no trata de cubrir todo lo relacionado con React, pero debería brindarle los bloques de construcción básicos para salir y convertirse en un gran desarrollador de React.
Espero que el contenido de este tutorial extenso lo ayude a lograr lo que desea: aprender los conceptos básicos de React .
Una introducción a la libreria de React
¿Qué es React?
React es una biblioteca de JavaScript que tiene como objetivo simplificar el desarrollo de interfaces visuales.
Desarrollado en Facebook y lanzado al mundo en 2013, impulsa algunas de las aplicaciones más utilizadas, impulsando Facebook e Instagram entre otras innumerables aplicaciones.
Su objetivo principal es facilitar el razonamiento sobre una interfaz y su estado en cualquier momento, dividiendo la interfaz de usuario en una colección de componentes.
¿Por qué React es tan popular?
React ha conquistado el mundo del desarrollo web frontend. ¿Por qué?
Menos complejo que las otras alternativas
En el momento en que se anunció React, Ember.js y Angular 1.x eran las opciones predominantes como marco. Ambos impusieron tantas convenciones en el código que portar una aplicación existente no fue conveniente en absoluto.
React tomó la decisión de ser muy fácil de integrar en un proyecto existente, porque así es como tenían que hacerlo en Facebook para introducirlo en la base de código existente. Además, esos 2 marcos trajeron demasiado a la mesa, mientras que React solo eligió implementar la capa de Vista en lugar del MVC completa.
Tiempo perfecto
En ese momento, Google anunció Angular 2.x, junto con la incompatibilidad hacia atrás y los cambios importantes que iba a traer. Pasar de Angular 1 a 2 fue como pasar a un marco diferente, por lo que esto, junto con las mejoras en la velocidad de ejecución que prometió React, lo convirtió en algo que los desarrolladores estaban ansiosos por probar.
Respaldado por Facebook
Tener el respaldo de Facebook, por supuesto, beneficiará a un proyecto si resulta tener éxito.
Actualmente, Facebook tiene un gran interés en React, ve el valor de ser de código abierto y esto es una gran ventaja para todos los desarrolladores que lo usan en sus propios proyectos.
¿React es fácil de aprender?
Aunque dije que React es más simple que los marcos alternativos, sumergirse en React sigue siendo complicado, pero principalmente debido a las tecnologías corolarias que se pueden integrar con React, como Redux y GraphQL.
React en sí mismo tiene una API muy pequeña, y básicamente necesitas comprender 4 conceptos para comenzar:
- Componentes
- JSX
- State
- Props
Cómo instalar React en su computadora de desarrollo
¿Cómo instalas React?
React es una biblioteca, por lo que decir instalar puede sonar un poco extraño. Tal vez configuración sea una palabra mejor, pero entiendes el concepto.
Hay varias formas de configurar React para que pueda usarse en su aplicación o sitio.
Cargue React directamente en la página web
La más simple es agregar el archivo javascript de React en la página directamente. Esto es mejor cuando su aplicación React interactuará con los elementos presentes en una sola página y no controlará todo el aspecto de la navegación.
En este caso, agrega 2 etiquetas de secuencia de comandos al final de la body
etiqueta:
<html>
...
<body>
...
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"
crossorigin
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"
crossorigin
></script>
</body>
</html>
Aquí cargamos React y React DOM. ¿Por qué 2 bibliotecas? Porque React es 100% independiente del navegador y se puede usar fuera de él (por ejemplo, en dispositivos móviles con React Native). De ahí la necesidad de React DOM, para agregar los contenedores para el navegador.
Después de esas etiquetas, puede cargar sus archivos JavaScript que usan React, o incluso JavaScript en línea en una script
etiqueta:
<script src="app.js"></script>
<!-- or -->
<script>
//my app
</script>
Para usar JSX necesita un paso adicional: cargar la libreria de Babel
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
y cargue sus scripts con el text/babel
tipo especial MIME:
<script src="app.js" type="text/babel"><;/script>
Ahora puede agregar JSX en su archivo app.js:
const Button = () => {
return <button>Click me!</button>
}
ReactDOM.render(<Button />, document.getElementById('root'))
Vea este ejemplo simple de Glitch: https://glitch.com/edit/#!/react-example-inline-jsx?path=script.js
Comenzar de esta manera con la etiqueta script para importar la libreria de react es bueno para crear prototipos y permite un inicio rápido sin tener que configurar un flujo de trabajo complejo.
Cómo usar create-react-app
create-react-app
es un proyecto destinado a ponerte al día con React en poco tiempo, y cualquier aplicación de React que necesite superar una sola página encontrará que create-react-app
satisface esa necesidad.
Empiece por usar npx, que es una forma fácil de descargar y ejecutar comandos de Node.js sin instalarlos. npx
viene con npm
(desde la versión 5.2) y si aún no tiene npm instalado, hágalo ahora desde https://nodejs.org (npm está instalado con Node).
Si no está seguro de qué versión de npm tiene, ejecute npm -v
para comprobar si necesita actualizar.
Cuando lo ejecute npx create-react-app <app-name>
, npx
va a descargar la create-react-app
versión más reciente , ejecutarla y luego eliminarla de su sistema. Esto es genial porque nunca tendrás una versión desactualizada en tu sistema, y cada vez que la ejecutes, obtendrás el último y mejor código disponible.
Empecemos entonces:
npx create-react-app todolist
El comando create-react-app
creó una estructura de archivos en la carpeta ( todolist
en este caso) e inicializó un repositorio de Git .
También agregó algunos comandos en el package.json
archivo, por lo que puede iniciar la aplicación inmediatamente yendo a la carpeta y ejecutándola npm start
.
Además del comando npm start
, create-react-app
agregaró algunos otros comandos, como por ejemplo:
npm run build
: para construir los archivos de la aplicación React en la carpetabuild
, listos para ser implementados en un servidornpm test
: para ejecutar la suite de pruebas usando Jest
Dado que create-react-app
es un conjunto de convenciones de denominador común y una cantidad limitada de opciones, es probable que en algún momento sus necesidades demanden algo único que supere las capacidades de create-react-app
.
CodeSandbox
Una manera fácil de tener la estructura de create-react-app
, sin instalar, es ir a https://codesandbox.io/s y elegir “React”.
CodeSandbox es una excelente manera de iniciar un proyecto de React sin tener que instalarlo localmente.
Codepen
Otra gran solución es Codepen .
Puede usar este proyecto de inicio de Codepen que ya viene preconfigurado con React, con soporte para Hooks: https://codepen.io/flaviocopes/pen/VqeaxB
Los “pens” de Codepen son excelentes para proyectos rápidos con un archivo JavaScript, mientras que los “Projects” son excelentes para proyectos con varios archivos, como los que usaremos más al crear aplicaciones React.
Una cosa a tener en cuenta es que en Codepen, debido a cómo funciona internamente, no usa la sintaxis normal de import
para importar módulos, sino que para importar, por ejemplo useState
, usa
const { useState } = React
y no
import { useState } from 'react'
SECCIÓN 1: CONCEPTOS BÁSICOS MODERNOS DE JAVASCRIPT QUE NECESITA SABER PARA USAR REACT
Descubra si tiene que aprender algo antes de sumergirse en el aprendizaje de React
Si está dispuesto a aprender React, primero debe tener algunas cosas en su haber. Hay algunas tecnologías de requisitos previos con las que debe estar familiarizado, en particular las relacionadas con algunas de las funciones de JavaScript más recientes que usará una y otra vez en React.
A veces, la gente piensa que React proporciona una característica en particular, pero en cambio es solo una sintaxis de JavaScript moderna.
No tiene sentido ser un experto en esos temas de inmediato, pero cuanto más te sumerjas en React, más necesitarás dominarlos.
Mencionaré una lista de cosas para que se ponga al día rápidamente.
Variables
Una variable es un literal asignado a un identificador, por lo que puede hacer referencia a él y usarlo más adelante en el programa.
Las variables en JavaScript no tienen ningún tipo adjunto. Una vez que asigna un tipo de literal específico a una variable, luego puede reasignar la variable para alojar cualquier otro tipo, sin errores de tipo ni ningún problema.
Esta es la razón por la que a veces se hace referencia a JavaScript como “sin tipo”.
Se debe declarar una variable antes de poder usarla. Hay 3 maneras de hacer esto, el uso var
, let
o const
, y esos 3 formas difieren en cómo se puede interactuar con la variable más adelante.
Utilizando var
Hasta ES2015, var
era el único constructo disponible para definir variables.
var a = 0
Si olvida agregar var
, estará asignando un valor a una variable no declarada y los resultados pueden variar.
En entornos modernos, con el modo estricto habilitado, obtendrá un error. En entornos más antiguos (o con el modo estricto desactivado), esto simplemente inicializará la variable y la asignará al objeto global.
Si no inicializa la variable cuando la declara, tendrá el undefined
valor hasta que le asigne un valor.
var a //typeof a === 'undefined'
Puede volver a declarar la variable muchas veces, anulándola:
var a = 1
var a = 2
El Scope es la parte del código donde la variable es visible.
Una variable inicializada con var
fuera de cualquier función se asigna al objeto global, tiene un alcance global y es visible en todas partes. Una variable inicializada var
dentro de una función se asigna a esa función, es local y solo es visible dentro de ella, al igual que un parámetro de función.
Cualquier variable definida en una función con el mismo nombre que una variable global tiene prioridad sobre la variable global, sombreándola.
Es importante comprender que un bloque (identificado por un par de llaves) no define un nuevo alcance. Un nuevo alcance solo se crea cuando se crea una función, porque var
no tiene alcance de bloque, sino alcance de función.
Dentro de una función, cualquier variable definida en ella es visible en todo el código de la función, incluso si la variable se declara al final de la función, todavía se puede hacer referencia al principio, porque JavaScript antes de ejecutar el código en realidad mueve todas las variables en la parte superior (algo que se llama hoisting). Para evitar confusiones, siempre declare las variables al comienzo de una función.
Utilizando let
let
es una nueva característica introducida en ES2015 y es esencialmente una versión de alcance de bloque de var
. Su alcance se limita al bloque, declaración o expresión donde está definido, y todos los bloques internos contenidos.
Los desarrolladores de JavaScript modernos pueden optar por usar solo let
y descartar completamente el uso de var
.
Si
let
parece un término oscuro, simplemente léalolet color = 'red'
porque deja que el color sea rojo y todo tiene mucho más sentido.
Definir let
fuera de cualquier función, al contrario var
, no crea una variable global.
Utilizando const
Las variables declaradas con var
o let
pueden cambiarse más adelante en el programa y reasignarse. Una vez que const
se inicializa a, su valor no se puede volver a cambiar y no se puede reasignar a un valor diferente.
const a = 'test'
No podemos asignar un literal diferente a la variable const
. Sin embargo, podemos mutar a
si se trata de un objeto que proporciona métodos que mutan su contenido.
const
no proporciona inmutabilidad, solo se asegura de que la referencia no se pueda cambiar.
const
tiene alcance de bloque, igual que let
.
Los desarrolladores de JavaScript modernos pueden optar por usar siempre const
para variables que no necesitan reasignarse más adelante en el programa.
¿Por qué? Porque siempre debemos usar la construcción más simple disponible para evitar cometer errores en el futuro.
Funciones de flecha
Las funciones de flecha se introdujeron en ES6 / ECMAScript 2015 y, desde su introducción, cambiaron para siempre la apariencia (y el funcionamiento) del código JavaScript.
En mi opinión, este cambio fue tan acogedor que ahora rara vez se ve el uso de la function
palabra clave en las bases de código modernas.
Visualmente, es un cambio simple y bienvenido, que le permite escribir funciones con una sintaxis más corta, desde:
const myFunction = function() {
//...
}
para
const myFunction = () => {
//...
}
Si el cuerpo de la función contiene solo una declaración, puede omitir los corchetes y escribir todo en una sola línea:
const myFunction = () => doSomething()
Los parámetros se pasan entre paréntesis:
const myFunction = (param1, param2) => doSomething(param1, param2)
Si tiene un parámetro (y solo uno), puede omitir los paréntesis por completo:
const myFunction = param => doSomething(param)
Gracias a esta breve sintaxis, las funciones de flecha fomentan el uso de funciones pequeñas .
Retorno implícito
Las funciones de flecha le permiten tener un retorno implícito: los valores se devuelven sin tener que usar la return
palabra clave.
Funciona cuando hay una declaración de una línea en el cuerpo de la función:
const myFunction = () => 'test'
myFunction() //'test'
Otro ejemplo, al devolver un objeto, recuerde envolver las llaves entre paréntesis para evitar que se considere la función de envoltura de los corchetes del cuerpo:
const myFunction = () => ({ value: 'test' })
myFunction() //{value: 'test'}
Cómo funciona this en las funciones de flecha
this
es un concepto que puede resultar complicado de captar, ya que varía mucho según el contexto y también varía según el modo de JavaScript ( modo estricto o no).
Es importante aclarar este concepto porque las funciones de flecha se comportan de manera muy diferente en comparación con las funciones regulares.
Cuando se define como un método de un objeto, en una función regular se refiere this
al objeto, por lo que puede hacer:
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: function() {
return `${this.manufacturer} ${this.model}`
}
}
car.fullName()
volverá a llamar "Ford Fiesta"
.
El alcance de this
con funciones de flecha se hereda del contexto en ejecución. Una función de flecha no se vincula en absoluto a this
, por lo que su valor se buscará en la pila de llamadas, por lo que en este código car.fullName()
no funcionará y devolverá la cadena "undefined undefined"
:
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: () => {
return `${this.manufacturer} ${this.model}`
}
}
Debido a esto, las funciones de flecha no son adecuadas como métodos de objeto.
Las funciones de flecha tampoco se pueden usar como constructores, cuando la instanciación de un objeto generará un TypeError
.
Aquí es donde se deben usar las funciones regulares en su lugar, cuando no se necesita un contexto dinámico .
Esto también es un problema al manejar eventos. Los oyentes de eventos DOM configurados con this
para ser el elemento de destino, y si confía this
en un controlador de eventos, es necesaria una función regular:
const link = document.querySelector('#link')
link.addEventListener('click', () => {
// this === window
})
const link = document.querySelector('#link')
link.addEventListener('click', function() {
// this === link
})
Rest y spread
Puede expandir una matriz, un objeto o una cadena utilizando el operador de extensión ...
.
Comencemos con un ejemplo de matriz. Dado
const a = [1, 2, 3]
puedes crear una nueva matriz usando
const b = [...a, 4, 5, 6]
También puede crear una copia de una matriz usando
const c = [...a]
Esto también funciona para objetos. Clonar un objeto con:
const newObj = { ...oldObj }
Usando cadenas, el operador de propagación crea una matriz con cada carácter de la cadena:
const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']
Este operador tiene algunas aplicaciones bastante útiles. El más importante es la capacidad de usar una matriz como argumento de función de una manera muy simple:
const f = (foo, bar) => {}
const a = [1, 2]
f(...a)
(en el pasado, podía hacer esto usando, f.apply(null, a)
pero eso no es tan agradable y legible)
El elemento rest es útil cuando se trabaja con desestructuración de matrices :
const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers
y difundir elementos :
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sumOfNumbers = sum(...numbers)
ES2018 introduce propiedades de rest, que son las mismas pero para objetos.
Propiedades de rest:
const { first, second, ...others } = {
first: 1,
second: 2,
third: 3,
fourth: 4,
fifth: 5
}
first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }
Las propiedades de propagación permiten crear un nuevo objeto combinando las propiedades del objeto pasado después del operador de propagación:
const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
Desestructuración de objetos y matrices
Dado un objeto, utilizando la sintaxis de desestructuración, puede extraer solo algunos valores y ponerlos en variables con nombre:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54 //made up
}
const { firstName: name, age } = person //name: Tom, age: 54
name
y age
contener los valores deseados.
La sintaxis también funciona en matrices:
const a = [1, 2, 3, 4, 5]
const [first, second] = a
Esta declaración crea 3 nuevas variables al obtener los elementos con índice 0, 1, 4 de la matriz a
:
const [first, second, , , fifth] = a
Plantillas literales
Las plantillas literales son una nueva característica de ES2015 / ES6 que le permite trabajar con cadenas de una manera novedosa en comparación con ES5 y versiones anteriores.
La sintaxis a primera vista es muy simple, solo use comillas inversas en lugar de comillas simples o dobles:
const a_string = `something`
Son únicos porque proporcionan muchas características que las cadenas normales construidas con comillas no ofrecen, en particular:
- ofrecen una gran sintaxis para definir cadenas de varias líneas
- proporcionan una forma sencilla de interpolar variables y expresiones en cadenas
- le permiten crear DSL con etiquetas de plantilla (DSL significa lenguaje específico de dominio, y se usa, por ejemplo, en React by Styled Components, para definir CSS para un componente)
Analicemos cada uno de estos en detalle.
Cadenas multilínea
Antes de ES6, para crear una cadena que abarque dos líneas, tenía que usar el carácter \
al final de una línea:
const string =
'first part \
second part'
Esto permite crear una cadena en 2 líneas, pero se representa en una sola línea:
first part second part
Para representar la cadena en varias líneas también, debe agregar explícitamente al \n
final de cada línea, así:
const string =
'first line\n \
second line'
o
const string = 'first line\n' + 'second line'
Las plantillas literales hacen que las cadenas de varias líneas sean mucho más simples.
Una vez que se abre una plantilla literal con la comilla invertida, solo presiona enter para crear una nueva línea, sin caracteres especiales, y se representa como está:
const string = `Hey
this
string
is awesome!`
Tenga en cuenta que el espacio es significativo, así que haga esto:
const string = `First
Second`
va a crear una cadena como esta:
First
Second
Una manera fácil de solucionar este problema es tener una primera línea vacía y agregar el método trim () justo después de la tilde de cierre, lo que eliminará cualquier espacio antes del primer carácter:
const string = `
First
Second`.trim()
Interpolación
Las plantillas literales proporcionan una forma sencilla de interpolar variables y expresiones en cadenas.
Lo hace utilizando la ${...}
sintaxis:
const myVariable = 'test'
const string = `something ${myVariable}` //something test
dentro de ${}
puede agregar cualquier cosa, incluso expresiones:
const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? 'x' : 'y'}`
Clases
En 2015, el estándar ECMAScript 6 (ES6) introdujo clases.
JavaScript tiene una forma poco común de implementar la herencia: herencia prototípica. La herencia de prototipos , aunque en mi opinión es excelente, es diferente a la implementación de la herencia de la mayoría de los otros lenguajes de programación populares, que se basan en clases.
Las personas que provenían de Java o Python u otros lenguajes tuvieron dificultades para comprender las complejidades de la herencia prototípica, por lo que el comité ECMAScript decidió espolvorear azúcar sintáctica sobre la herencia prototípica para que se parezca a cómo funciona la herencia basada en clases en otras implementaciones populares.
Esto es importante: JavaScript bajo el capó sigue siendo el mismo y puede acceder a un prototipo de objeto de la forma habitual.
Una definición de clase
Así es como se ve una clase.
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
Una clase tiene un identificador, que podemos usar para crear nuevos objetos usando new ClassIdentifier()
.
Cuando se inicializa el objeto, constructor
se llama al método y se pasan los parámetros.
Una clase también tiene tantos métodos como necesite. En este caso hello
es un método y se puede llamar a todos los objetos derivados de esta clase:
const flavio = new Person('Flavio')
flavio.hello()
Herencia de clases
Una clase puede extender otra clase y los objetos inicializados usando esa clase heredan todos los métodos de ambas clases.
Si la clase heredada tiene un método con el mismo nombre que una de las clases más altas en la jerarquía, el método más cercano tiene prioridad:
class Programmer extends Person {
hello() {
return super.hello() + ' I am a programmer.'
}
}
const flavio = new Programmer('Flavio')
flavio.hello()
(el programa anterior imprime ” Hola, soy Flavio. Soy un programador “).
Las clases no tienen declaraciones explícitas de variables de clase, pero debe inicializar cualquier variable en el constructor.
Dentro de una clase, puede hacer referencia a la llamada de la clase principal super()
.
Métodos estáticos
Normalmente, los métodos se definen en la instancia, no en la clase.
Los métodos estáticos se ejecutan en la clase en su lugar:
class Person {
static genericHello() {
return 'Hello'
}
}
Person.genericHello() //Hello
Métodos privados
JavaScript no tiene una forma incorporada para definir métodos privados o protegidos.
Hay soluciones, pero no se describirán aquí.
Getters y setters
Puede agregar métodos con el prefijo get
o set
para crear un getter y setter, que son dos piezas de código diferentes que se ejecutan en función de lo que está haciendo: acceder a la variable o modificar su valor.
class Person {
constructor(name) {
this.name = name
}
set name(value) {
this.name = value
}
get name() {
return this.name
}
}
Si solo tiene un getter, la propiedad no se puede establecer y se ignorará cualquier intento de hacerlo:
class Person {
constructor(name) {
this.name = name
}
get name() {
return this.name
}
}
Si solo tiene un setter, puede cambiar el valor pero no acceder a él desde el exterior:
class Person {
constructor(name) {
this.name = name
}
set name(value) {
this.name = value
}
}
Callbacks
Las computadoras son asincrónicas por diseño.
Asincrónico significa que las cosas pueden suceder independientemente del flujo del programa principal.
En las computadoras de consumo actuales, cada programa se ejecuta durante un intervalo de tiempo específico y luego detiene su ejecución para permitir que otro programa continúe su ejecución. Esto funciona en un ciclo tan rápido que es imposible notarlo, y creemos que nuestras computadoras ejecutan muchos programas simultáneamente, pero esto es una ilusión (excepto en máquinas multiprocesador).
Los programas utilizan internamente interrupciones , una señal que se emite al procesador para llamar la atención del sistema.
No entraré en los aspectos internos de esto, pero tenga en cuenta que es normal que los programas sean asincrónicos y detengan su ejecución hasta que necesiten atención y la computadora pueda ejecutar otras cosas mientras tanto. Cuando un programa está esperando una respuesta de la red, no puede detener el procesador hasta que finalice la solicitud.
Normalmente, los lenguajes de programación son sincrónicos y algunos proporcionan una forma de gestionar la asincronicidad, en el lenguaje o mediante bibliotecas. C, Java, C #, PHP, Go, Ruby, Swift, Python, todos son sincrónicos por defecto. Algunos de ellos manejan async mediante el uso de subprocesos, generando un nuevo proceso.
JavaScript es síncrono por defecto y es de un solo hilo. Esto significa que el código no puede crear nuevos subprocesos y ejecutarse en paralelo.
Las líneas de código se ejecutan en serie, una tras otra, por ejemplo:
const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()
Pero JavaScript nació dentro del navegador, su trabajo principal, en un principio, era responder a las acciones del usuario, como onClick
, onMouseOver
, onChange
, onSubmit
y así sucesivamente. ¿Cómo podría hacer esto con un modelo de programación síncrona?
La respuesta estaba en su entorno. El navegador proporciona una forma de hacerlo al proporcionar un conjunto de API que pueden manejar este tipo de funcionalidad.
Más recientemente, Node.js introdujo un entorno de E/S sin bloqueo para extender este concepto al acceso a archivos, llamadas de red, etc.
No puede saber cuándo un usuario va a hacer clic en un botón, por lo que lo que debe hacer es definir un controlador de eventos para el evento de clic . Este controlador de eventos acepta una función, que se llamará cuando se active el evento:
document.getElementById('button').addEventListener('click', () => {
//item clicked
})
Esto es la llamada a los callbacks .
Una devolución de llamada es una función simple que se pasa como un valor a otra función y solo se ejecutará cuando ocurra el evento. Podemos hacer esto porque JavaScript tiene funciones de primera clase, que se pueden asignar a variables y pasar a otras funciones (llamadas funciones de orden superior).
Es común envolver todo el código de su cliente en un detector de eventos como load
en el objecto window
, que ejecuta la función de callback solo cuando la página está lista:
window.addEventListener('load', () => {
//window loaded
//do what you want
})
Los callbacks se utilizan en todas partes, no solo en eventos DOM.
Un ejemplo común es el uso de temporizadores:
setTimeout(() => {
// runs after 2 seconds
}, 2000)
Las solicitudes XHR también aceptan una devolución de llamada, en este ejemplo, asignando una función a una propiedad que se llamará cuando ocurra un evento en particular (en este caso, el estado de la solicitud cambia):
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
}
}
xhr.open('GET', 'https://yoursite.com')
xhr.send()
Manejo de errores en devoluciones de llamada
¿Cómo maneja los errores con devoluciones de llamada? Una estrategia muy común es usar lo que adoptó Node.js: el primer parámetro en cualquier función de callback es el objeto de error : primer parametro de callback es error.
Si no hay ningún error, el objeto es null
. Si hay un error, contiene alguna descripción del error y otra información.
fs.readFile('/file.json', (err, data) => {
if (err !== null) {
//handle error
console.log(err)
return
}
//no errors, process data
console.log(data)
})
El problema de los Callbacks
¡Las devoluciones de llamada son ideales para casos simples!
Sin embargo, cada callback agrega un nivel de anidamiento, y cuando tiene muchas callback, el código comienza a complicarse muy rápidamente:
window.addEventListener('load', () => {
document.getElementById('button').addEventListener('click', () => {
setTimeout(() => {
items.forEach(item => {
//your code here
})
}, 2000)
})
})
Este es solo un código simple de 4 niveles, pero he visto muchos más niveles de anidación y no es divertido.
¿Cómo resolvemos esto?
ALTERNATIVAS A LOS CALLBACKS
A partir de ES6, JavaScript introdujo varias características que nos ayudan con el código asincrónico que no implica el uso de devoluciones de llamada:
- Promesas o Promise (ES6)
- Async/Await (ES8)
Promesas o Promise
Las promesas son una forma de lidiar con el código asincrónico, sin escribir demasiadas devoluciones de llamada en su código.
Aunque han existido durante años, se estandarizaron e introdujeron en ES2015, y ahora han sido reemplazados en ES2017 por funciones asíncronas.
Las funciones asíncronas usan la API de promesas como su bloque de construcción, por lo que comprenderlas es fundamental incluso si en el código más nuevo probablemente usará funciones asíncronas en lugar de promesas.
Cómo funcionan las promesas, en resumen
Una vez que se ha llamado a una promesa, comenzará en estado pendiente . Esto significa que la función de llamada continúa con la ejecución, mientras espera la promesa de hacer su propio procesamiento, y le da a la función de llamada alguna retroalimentación.
En este punto, la función de llamada espera a que devuelva la promesa en un estado resuelto o en un estado rechazado , pero como sabe, JavaScript es asincrónico, por lo que la función continúa su ejecución mientras la promesa funciona .
¿Qué promesas de uso de JS API?
Además de su propio código y código de biblioteca, las API web modernas estándar como Fetch o Service Workers utilizan las promesas .
Es poco probable que en JavaScript moderno no uses promesas, así que comencemos a sumergirnos en ellas.
Creando una promesa
La API de Promise expone un constructor de Promise, que inicializa usando new Promise()
:
let done = true
const isItDoneYet = new Promise((resolve, reject) => {
if (done) {
const workDone = 'Here is the thing I built'
resolve(workDone)
} else {
const why = 'Still working on something else'
reject(why)
}
})
Como puede ver, la promesa verifica la constante global done
y, si eso es cierto, devolvemos una promesa resuelta; de lo contrario, una promesa rechazada.
Usando resolve
y reject
podemos comunicar un valor, en el caso anterior solo devolvemos una cadena, pero también podría ser un objeto.
Consumiendo una promesa
En la última sección, presentamos cómo se crea una promesa.
Ahora veamos cómo se puede consumir o usar la promesa .
const isItDoneYet = new Promise()
//...
const checkIfItsDone = () => {
isItDoneYet
.then(ok => {
console.log(ok)
})
.catch(err => {
console.error(err)
})
}
La ejecución checkIfItsDone()
ejecutará la promesa isItDoneYet()
y esperará a que se resuelva, utilizando la devolución de llamada then
, y si hay un error, lo manejará en la devolución de llamada catch
.
Encadenando promesas
Una promesa puede devolver a otra promesa, creando una cadena de promesas.
Un gran ejemplo de encadenamiento de promesas lo da la API Fetch , una capa sobre la API XMLHttpRequest, que podemos usar para obtener un recurso y poner en cola una cadena de promesas para ejecutar cuando se recupere el recurso.
La API Fetch es un mecanismo basado en promesas, y llamar fetch()
es equivalente a definir nuestra propia promesa usando new Promise()
.
Ejemplo:
const status = response => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
}
return Promise.reject(new Error(response.statusText))
}
const json = response => response.json()
fetch('/todos.json')
.then(status)
.then(json)
.then(data => {
console.log('Request succeeded with JSON response', data)
})
.catch(error => {
console.log('Request failed', error)
})
En este ejemplo, llamamos fetch()
para obtener una lista de elementos TODO del todos.json
archivo que se encuentra en la raíz del dominio y creamos una cadena de promesas.
La ejecución fetch()
devuelve una respuesta , que tiene muchas propiedades, y dentro de las que hacemos referencia:
status
, un valor numérico que representa el código de estado HTTPstatusText
, un mensaje de estado, que esOK
si la solicitud se realizó correctamente
response
también tiene un metedo json()
, que devuelve una promesa que se resolverá con el contenido del cuerpo procesado y transformado en JSON.
Entonces, dadas esas premisas, esto es lo que sucede: la primera promesa en la cadena es una función que definimos, llamamos status()
, que verifica el estado de la respuesta y si no es una respuesta exitosa (entre 200 y 299), rechaza la promesa.
Esta operación hará que la cadena de promesas omita todas las promesas encadenadas enumeradas y pasará directamente a la declaración catch()
en la parte inferior, registrando el texto Request failed
junto con el mensaje de error.
Si eso tiene éxito, llama a la función json() que definimos. Dado que la promesa anterior, cuando tuvo éxito, devolvió el objecto response
, lo obtenemos como entrada para la segunda promesa.
En este caso, devolvemos los datos JSON procesados, por lo que la tercera promesa recibe el JSON directamente:
.then((data) => {
console.log('Request succeeded with JSON response', data)
})
y simplemente lo registramos en la consola.
Manejo de errores
En el ejemplo anterior, en la sección anterior, teníamos un catch
que se adjuntó a la cadena de promesas.
Cuando algo en la cadena de promesas falla y genera un error o rechaza la promesa, el control pasa a la declaración catch()
más cercana en la cadena.
new Promise((resolve, reject) => {
throw new Error('Error')
}).catch(err => {
console.error(err)
})
// o
new Promise((resolve, reject) => {
reject('Error')
}).catch(err => {
console.error(err)
})
Errores en cascada
Si dentro del catch()
muestra un error, puede agregar un segundo catch()
para manejarlo, y así sucesivamente.
new Promise((resolve, reject) => {
throw new Error('Error')
})
.catch(err => {
throw new Error('Error')
})
.catch(err => {
console.error(err)
})
Orquestando promesas con Promise.all()
Si necesita sincronizar diferentes promesas, Promise.all()
ayuda a definir una lista de promesas y a ejecutar algo cuando todas estén resueltas.
Ejemplo:
const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
Promise.all([f1, f2])
.then(res => {
console.log('Array of results', res)
})
.catch(err => {
console.error(err)
})
La sintaxis de asignación de desestructuración de ES2015 le permite también hacer
Promise.all([f1, f2]).then(([res1, res2]) => {
console.log('Results', res1, res2)
})
Por supuesto fetch
, no está limitado a usar promesas, siempre está lista para su uso.
Orquestando promesas con Promise.race()
La Promise.race()
se ejecuta tan pronto como una de las promesas que se resuelve, ejecuta la callback y adjunta solo una vez con el resultado de la primera promesa resuelta.
Ejemplo:
const promiseOne = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
const promiseTwo = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([promiseOne, promiseTwo]).then(result => {
console.log(result) // 'two'
})
Async / Await
JavaScript evolucionó en muy poco tiempo de devoluciones de llamada a promesas (ES2015), y dado que ES2017 JavaScript asincrónico es aún más simple con la sintaxis async / await.
Las funciones asincrónicas son una combinación de promesas y generadores y, básicamente, son una abstracción de nivel superior a las promesas. Permítanme repetir: async / await se basa en promesas .
¿Por qué se introdujeron async/await?
Reducen la repetición de las promesas y la limitación de “no romper la cadena” de las promesas encadenadas.
Cuando se introdujeron las Promesas en ES2015, estaban destinadas a resolver un problema con el código asincrónico, y lo hicieron, pero durante los 2 años que separaron ES2015 y ES2017, quedó claro que las promesas no podían ser la solución final .
Las promesas se introdujeron para resolver el famoso problema del infierno de devolución de llamada, pero introdujeron complejidad por sí mismas y complejidad de sintaxis.
Eran buenas primitivas en torno a las cuales se podía exponer a los desarrolladores una mejor sintaxis, por lo que, cuando llegó el momento, obtuvimos funciones asíncronas .
Hacen que el código parezca sincrónico, pero asincrónico y sin bloqueo detrás de escena.
Cómo funciona
Una función asincrónica devuelve una promesa, como en este ejemplo:
const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
Cuando desee llamar a esta función, anteponga await
y el código de llamada se detendrá hasta que la promesa se resuelva o rechace . Una advertencia: la función del cliente debe definirse como async
. He aquí un ejemplo:
const doSomething = async () => {
console.log(await doSomethingAsync())
}
Un ejemplo rapido
Este es un ejemplo simple de async / await usado para ejecutar una función de forma asincrónica:
const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
const doSomething = async () => {
console.log(await doSomethingAsync())
}
console.log('Before')
doSomething()
console.log('After')
El código anterior imprimirá lo siguiente en la consola del navegador:
Before
After
I did something //after 3s
Promete todas las cosas
Anteponer la palabra clave async
a cualquier función significa que la función devolverá una promesa.
Incluso si no lo hace explícitamente, internamente hará que devuelva una promesa.
Por eso este código es válido:
const aFunction = async () => {
return 'test'
}
aFunction().then(alert) // This will alert 'test'
y es lo mismo que:
const aFunction = async () => {
return Promise.resolve('test')
}
aFunction().then(alert) // This will alert 'test'
El código es mucho más sencillo de leer.
Como puede ver en el ejemplo anterior, nuestro código parece muy simple. Compárelo con el código usando promesas simples, con funciones de encadenamiento y devolución de llamada.
Y este es un ejemplo muy simple, los mayores beneficios surgirán cuando el código sea mucho más complejo.
Por ejemplo, así es como obtendría un recurso JSON y lo analizaría usando promesas:
const getFirstUserData = () => {
return fetch('/users.json') // get users list
.then(response => response.json()) // parse JSON
.then(users => users[0]) // pick first user
.then(user => fetch(`/users/${user.name}`)) // get user data
.then(userResponse => userResponse.json()) // parse JSON
}
getFirstUserData()
Y aquí está la misma funcionalidad proporcionada usando await / async:
const getFirstUserData = async () => {
const response = await fetch('/users.json') // get users list
const users = await response.json() // parse JSON
const user = users[0] // pick first user
const userResponse = await fetch(`/users/${user.name}`) // get user data
const userData = await userResponse.json() // parse JSON
return userData
}
getFirstUserData()
Varias funciones asíncronas en serie
Las funciones asíncronas se pueden encadenar muy fácilmente y la sintaxis es mucho más legible que con promesas simples:
const promiseToDoSomething = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 10000)
})
}
const watchOverSomeoneDoingSomething = async () => {
const something = await promiseToDoSomething()
return something + ' and I watched'
}
const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
const something = await watchOverSomeoneDoingSomething()
return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {
console.log(res)
})
Imprimirá:
I did something and I watched and I watched as well
Depuración más sencilla
Las promesas de depuración son difíciles porque el depurador no pasará por encima del código asincrónico.
Async/await lo hace muy fácil porque para el compilador es como un código sincrónico.
Fuente:
https://medium.freecodecamp.org/the-react-handbook-b71c27b0a795
https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Template_literals
0 comentarios