Una de las cosas que más me gusta de React en comparación a otros frameworks que he usado es lo expuesto que estás a JavaScript cuando lo utilizas. No hay una plantilla DSL (JSX compila a Javascript). El componente API se ha simplificado con el uso de React Hooks y el framework te ofrece muy poca abstracción más allá de las preocupaciones básicas de la UI que pretende resolver.
Por tanto, aprender JavaScript es muy aconsejable para que que puedas crear aplicaciones efectivas con React. Así que aquí van una serie de características de JavaScript a las que te recomiendo que le dediques un tiempo y las aprendas para que seas lo más efectivo posible cuando trabajes con React.
Antes de ir más al detalle con la sintaxis, otra cosa que es muy importante que entiendas en relación a React es el concepto de una función clausura (closure). Tienes mucha información sobre esto aquí.
¡Y ya está! Vamos a ver las características de JavaScript que necesitas saber para React.
Plantillas literales
Las plantillas literales son como strings normales con súperpoderes.
const greeting = 'Hello'
const subject = 'World'
console.log(`${greeting} ${subject}!`)
console.log(greeting + ' ' + subject + '!')
function Box({className, ...props}) {
return <div className={`box ${className}`} {...props} />
}
MDN: Plantillas literales
Abreviaturas de propiedades
Esto es tan común y tan útil que ya lo hago sin pensar.
const a = 'hello'
const b = 42
const c = {d: [true, false]}
console.log({a, b, c})
console.log({a: a, b: b, c: c})
function Counter({initialCount, step}) {
const [count, setCount] = useCounter({initialCount, step})
return <button onClick={setCount}>{count}</button>
}
MDN: Object Initializer New notations in ECMAScript 2015
Funciones flecha (arrow functions)
Las funciones flecha son otra manera de escribir funciones en JavaScript, pero tienen algunas diferencias semánticas. Por suerte, en React no nos tenemos que preguntar nada acerca del [.rr-code]this[.rr-code] si usamos hooks en nuestro proyecto (en lugar de clases), pero las funciones flecha permiten funciones anónimas y returns implícitos más concisos, así que las querrás usar a menudo.
const getFive = () => 5
const addFive = a => a + 5
const divide = (a, b) => a / b
function getFive() {
return 5
}
function addFive(a) {
return a + 5
}
function divide(a, b) {
return a / b
}
function TeddyBearList({teddyBears}) {
return (
<ul>
{teddyBears.map(teddyBear => (
<li key={teddyBear.id}>
<span>{teddyBear.name}</span>
</li>
))}
</ul>
)
}
Algo a observar en el ejemplo anterior son las aperturas y los cierres de paréntesis. Ésta es una forma común de aprovechar las capacidades del return implícito de las funciones flecha cuando se trabaja con JSX.
MDN: Funciones flecha
Desestructuración (destructuring)
La desestructuración es probablemente mi característica favorita de JavaScript. Desestructuro objectos y arrays todo el tiempo (y si usas useState, tú probablemente lo estés haciendo también, como se ve aquí). Me encanta lo declarativo que es.
function makeCalculation({x, y: d, z = 4}) {
return Math.floor((x + d + z) / 3)
}
function makeCalculation(obj) {
const {x, y: d, z = 4} = obj
return Math.floor((x + d + z) / 3)
}
function makeCalculation(obj) {
const x = obj.x
const d = obj.y
const z = obj.z === undefined ? 4 : obj.z
return Math.floor((x + d + z) / 3)
}
function UserGitHubImg({username = 'ghost', ...props}) {
return <img src={`https://github.com/${username}.png`} {...props} />
}
MDN: La desestructuración.
Sin lugar a dudas, deberías leer ese artículo de la MDN. Seguro que aprendes algo nuevo. Cuando termines, intenta refactorizar esto usando una línea de destructuración:
function nestedArrayAndObject() {
const info = {
title: 'Once Upon a Time',
protagonist: {
name: 'Emma Swan',
enemies: [
{name: 'Regina Mills', title: 'Evil Queen'},
{name: 'Cora Mills', title: 'Queen of Hearts'},
{name: 'Peter Pan', title: `The boy who wouldn't grow up`},
{name: 'Zelena', title: 'The Wicked Witch'},
],
},
}
const title = info.title
const protagonistName = info.protagonist.name
const enemy = info.protagonist.enemies[3]
const enemyTitle = enemy.title
const enemyName = enemy.name
return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`
}
Parámetros predeterminados
Ésta es otra característica que uso frecuentemente. Es una manera muy poderosa de determinar de forma declarativa los valores por defecto de tus funciones.
function add(a, b = 0) {
return a + b
}
const add = (a, b = 0) => a + b
function add(a, b) {
b = b === undefined ? 0 : b
return a + b
}
function useLocalStorageState({
key,
initialValue,
serialize = v => v,
deserialize = v => v,
}) {
const [state, setState] = React.useState(
() => deserialize(window.localStorage.getItem(key)) || initialValue,
)
const serializedState = serialize(state)
React.useEffect(() => {
window.localStorage.setItem(key, serializedState)
}, [key, serializedState])
return [state, setState]
}
MDN: Parámetros predeterminados.
Rest / Spread
La sintaxis [.rr-code]…[.rr-code] puede ser considerada como una sintaxis de recopilación que opera en una colección de valores. La uso a menudo y te recomiendo que aprendas cómo y dónde puede ser usada. De hecho toma diferentes significados en diferentes contextos, así que aprender los matices te ayudará.
const arr = [5, 6, 8, 4, 9]
Math.max(...arr)
Math.max.apply(null, arr)
const obj1 = {
a: 'a from obj1',
b: 'b from obj1',
c: 'c from obj1',
d: {
e: 'e from obj1',
f: 'f from obj1',
},
}
const obj2 = {
b: 'b from obj2',
c: 'c from obj2',
d: {
g: 'g from obj2',
h: 'h from obj2',
},
}
console.log({...obj1, ...obj2})
console.log(Object.assign({}, obj1, obj2))
function add(first, ...rest) {
return rest.reduce((sum, next) => sum + next, first)
}
function add() {
const first = arguments[0]
const rest = Array.from(arguments).slice(1)
return rest.reduce((sum, next) => sum + next, first)
}
function Box({className, ...restOfTheProps}) {
const defaultProps = {
className: `box ${className}`,
children: 'Empty box',
}
return <div {...defaultProps} {...restOfTheProps} />
}
MDN: Sintaxis Spread MDN: Parámetros Rest
ESModules
Si estás construyendo una aplicación con herramientas modernas, probablemente soporte los módulos. Es una buena idea aprender cómo funciona la sintaxis porque cualquier aplicación (incluso de tamaño trivial) probablemente necesite usar módulos para la reutilización y organización de código.
export default function add(a, b) {
return a + b
}
export const foo = 'bar'
export function subtract(a, b) {
return a - b
}
export const now = new Date()
import('./some-module').then(
allModuleExports => {
},
error => {
},
)
import React, {Suspense, Fragment} from 'react'
const BigComponent = React.lazy(() => import('./big-component'))
MDN: Import MDN: Export
Como recurso añadido, di una charla entera sobre esta sintaxis. Puedes verla aquí.
Ternarios
Me encantan los ternarios. Son perfectamente declarativos. Especialmente en JSX.
const message = bottle.fullOfSoda
? 'The bottle has soda!'
: 'The bottle may not have soda :-('
let message
if (bottle.fullOfSoda) {
message = 'The bottle has soda!'
} else {
message = 'The bottle may not have soda :-('
}
function TeddyBearList({teddyBears}) {
return (
<React.Fragment>
{teddyBears.length ? (
<ul>
{teddyBears.map(teddyBear => (
<li key={teddyBear.id}>
<span>{teddyBear.name}</span>
</li>
))}
</ul>
) : (
<div>There are no teddy bears. The sadness.</div>
)}
</React.Fragment>
)
}
Soy consciente de que los ternarios pueden provocar una reacción instintiva de disgusto por parte de algunas personas que tuvieron que soportar tratar de encontrarle sentido a los ternarios antes de que llegara prettier y limpiara nuestro código. Si no estás usando prettier aún, te recomiendo que lo hagas. Prettier hará que tus ternarios sean mucho más fáciles de leer.
MDN: Operador condicional (ternario)
Métodos de array
Los arrays son fantásticos y uso sus métodos todo el rato. Probablemente los que uso con más frecuencia son:
- find
- some
- every
- includes
- map
- filter
- reduce
Aquí van algunos ejemplos:
const dogs = [
{
id: 'dog-1',
name: 'Poodle',
temperament: [
'Intelligent',
'Active',
'Alert',
'Faithful',
'Trainable',
'Instinctual',
],
},
{
id: 'dog-2',
name: 'Bernese Mountain Dog',
temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'],
},
{
id: 'dog-3',
name: 'Labrador Retriever',
temperament: [
'Intelligent',
'Even Tempered',
'Kind',
'Agile',
'Outgoing',
'Trusting',
'Gentle',
],
},
]
dogs.find(dog => dog.name === 'Bernese Mountain Dog')
dogs.some(dog => dog.temperament.includes('Aggressive'))
dogs.some(dog => dog.temperament.includes('Trusting'))
dogs.every(dog => dog.temperament.includes('Trusting'))
dogs.every(dog => dog.temperament.includes('Intelligent'))
dogs.map(dog => dog.name)
dogs.filter(dog => dog.temperament.includes('Faithful'))
dogs.reduce((allTemperaments, dog) => {
return [...allTemperaments, ...dog.temperament]
}, [])
function RepositoryList({repositories, owner}) {
return (
<ul>
{repositories
.filter(repo => repo.owner === owner)
.map(repo => (
<li key={repo.id}>{repo.name}</li>
))}
</ul>
)
}
MDN: Array
Operador de fusión nulo
Si un valor es [.rr-code]null[.rr-code] o [.rr-code]undefined[.rr-code], tal vez quieras recurrir a un valor por defecto.
x = x || 'some default'
add(null, 3)
function add(a, b) {
a = a == null ? 0 : a
b = b == null ? 0 : b
return a + b
}
function add(a, b) {
a = a ?? 0
b = b ?? 0
return a + b
}
function DisplayContactName({contact}) {
return <div>{contact.name ?? 'Unknown'}</div>
}
MDN: Nullish coalescing operator
Encadenamiento opcional
También conocido como el “operador Elvis”, te permite acceder de forma segura a propiedades y llamar a funciones que pueden existir o no. Antes del encadenamiento opcional, usábamos un apaño que dependía de si los valores eran false o true.
const streetName = user && user.address && user.address.street.name
const streetName = user?.address?.street?.name
const onSuccess = options?.onSuccess
onSuccess?.({data: 'yay'})
options?.onSuccess?.({data: 'yay'})
options?.onSuccess({data: 'yay'})
function UserProfile({user}) {
return (
<div>
<h1>{user.name}</h1>
<strong>{user.bio?.short ?? 'No bio provided'}</strong>
</div>
)
}
Una advertencia sobre esto es que si te encuentras haciendo mucho ?. en tu código, quizá debas considerar el lugar en el que esos valores se originan y asegurarte de que se devuelven los valores que se deben devolver.
MDN: Encadenamiento opcional
Promesas y async/await
Éste es un tema importante y puedes necesitar algo de práctica y tiempo para dominarlo. Las promesas están en todas partes en el ecosistema de JavaScript y gracias a lo afianzado que está React en ese ecosistema, también están ahí en todas partes (de hecho, React usa las promesas de forma interna).
Las promesas te ayudan a gestionar el código asíncrono y también son devueltas por muchas APIs del DOM y librerías de terceros. La sintaxis async/await es una sintaxis especial para trabajar con promesas. Las dos van de la mano.
function promises() {
const successfulPromise = timeout(100).then(result => `success: ${result}`)
const failingPromise = timeout(200, true).then(null, error =>
Promise.reject(`failure: ${error}`),
)
const recoveredPromise = timeout(300, true).then(null, error =>
Promise.resolve(`failed and recovered: ${error}`),
)
successfulPromise.then(log, logError)
failingPromise.then(log, logError)
recoveredPromise.then(log, logError)
}
function asyncAwaits() {
async function successfulAsyncAwait() {
const result = await timeout(100)
return `success: ${result}`
}
async function failedAsyncAwait() {
const result = await timeout(200, true)
return `failed: ${result}`
}
async function recoveredAsyncAwait() {
try {
const result = await timeout(300, true)
return `failed: ${result}`
} catch (error) {
return `failed and recovered: ${error}`
}
}
successfulAsyncAwait().then(log, logError)
failedAsyncAwait().then(log, logError)
recoveredAsyncAwait().then(log, logError)
}
function log(...args) {
console.log(...args)
}
function logError(...args) {
console.error(...args)
}
function timeout(duration = 0, shouldReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(`rejected after ${duration}ms`)
} else {
resolve(`resolved after ${duration}ms`)
}
}, duration)
})
}
function GetGreetingForSubject({subject}) {
const [isLoading, setIsLoading] = React.useState(false)
const [error, setError] = React.useState(null)
const [greeting, setGreeting] = React.useState(null)
React.useEffect(() => {
async function fetchGreeting() {
try {
const response = await window.fetch('https://example.com/api/greeting')
const data = await response.json()
setGreeting(data.greeting)
} catch (error) {
setError(error)
} finally {
setIsLoading(false)
}
}
setIsLoading(true)
fetchGreeting()
}, [])
return isLoading ? (
'loading...'
) : error ? (
'ERROR!'
) : greeting ? (
<div>
{greeting} {subject}
</div>
) : null
}
MDN: Promise MDN: función async MDN: await
Conclusión
Hay, desde luego, muchas características del lenguaje que son útiles al construir aplicaciones con React, pero éstas son algunas de mis favoritas y las que me encuentro usando una y otra vez. Espero que te resulte útil.
Si quieres ahondar algo más en esto, hay un taller de JavaScript que Kent C. Dodds grabó cuando trabajaba en Paypal y que puede resultarte de ayuda: ES6 and Beyond Workshop
¡Buena suerte!