Hoy en día, React es una de las bibliotecas de JavaScript más populares. Se puede utilizar para crear aplicaciones dinámicas y con capacidad de respuesta, permite un mejor rendimiento y se puede ampliar fácilmente. La lógica subyacente se basa en componentes que pueden reutilizarse en diferentes contextos, reduciendo la necesidad de escribir el mismo código varias veces. En resumen, con React puedes crear aplicaciones eficientes y potentes.

Así que nunca ha habido mejor momento para aprender a crear aplicaciones React.

Sin embargo, sin una sólida comprensión de algunas características clave de JavaScript, crear aplicaciones React puede resultar difícil o incluso imposible.

Por esta razón, hemos recopilado una lista de características y conceptos de JavaScript que necesitas conocer antes de empezar con React. Cuanto mejor entiendas estos conceptos, más fácil te resultará crear aplicaciones React profesionales.

Una vez dicho esto, en este artículo hablaremos de lo siguiente:

JavaScript y ECMAScript

JavaScript es un popular lenguaje de programación que se utiliza junto con HTML y CSS para crear páginas web dinámicas. Mientras que HTML se utiliza para crear la estructura de una página web y CSS para crear el estilo y la disposición de sus elementos, JavaScript es el lenguaje utilizado para añadir comportamiento a la página, es decir, para crear funcionalidad e interactividad.

Desde entonces, el lenguaje ha sido adoptado por los principales navegadores y se redactó un documento para describir la forma en que debía funcionar JavaScript: el estándar ECMAScript.

Desde 2015, se publica anualmente una actualización del estándar ECMAScript, por lo que cada año se añaden nuevas funciones a JavaScript.

ECMAScript 2015 fue la sexta versión del estándar, por lo que también se conoce como ES6. Las siguientes versiones están marcadas en progresión, por lo que nos referimos a ECMAScript 2016 como ES7, a ECMAScript 2017 como ES8, y así sucesivamente.

Debido a la frecuencia con la que se añaden nuevas funciones al estándar, es posible que algunas no sean compatibles con todos los navegadores. Entonces, ¿cómo puedes asegurarte de que las últimas funciones de JavaScript que añadas a tu aplicación JS funcionen como se espera en todos los navegadores web?

Tienes tres opciones:

  1. Esperar a que los principales navegadores soporten las nuevas funciones. Pero si es absolutamente necesaria esa increíble nueva función JS para tu aplicación, esta no es una opción.
  2. Utilizar un Polyfill, que es «un fragmento de código (normalmente JavaScript en la Web) utilizado para proporcionar una funcionalidad moderna en navegadores antiguos que no la soportan de forma nativa» (ver también mdn web docs).
  3. Utilizar un transpilador de JavaScript como Babel o Traceur, que convierten el código ECMAScript 2015+ en una versión de JavaScript compatible con todos los navegadores.

Sentencias vs Expresiones

Comprender la diferencia entre sentencias y expresiones es esencial a la hora de crear aplicaciones React. Volvamos por un momento a los conceptos básicos de la programación.

Un programa informático es una lista de instrucciones que debe ejecutar un ordenador. Estas instrucciones se denominan sentencias.

A diferencia de las sentencias, las expresiones son fragmentos de código que producen un valor. En una sentencias, una expresión es una parte que devuelve un valor y normalmente la vemos a la derecha de un signo igual.

Mientras que:

Las expresiones de JavaScript pueden ser bloques o líneas de código que suelen terminar con punto y coma o entre llaves.

Aquí tienes un ejemplo sencillo de sentencia en JavaScript:

document.getElementById("hello").innerHTML = "Hello World!";

La sentencia anterior escribe "Hello World!" en un elemento DOM con id="hello".

Como ya hemos dicho, las expresiones producen un valor o son ellas mismas un valor. Considera el siguiente ejemplo:

msg = document.getElementById("hello").value;

document.getElementById("hello").value es una expresión ya que devuelve un valor.

Un ejemplo adicional debería ayudar a aclarar la diferencia entre expresiones y sentencias:

const msg = "Hello World!";
function sayHello( msg ) {
	console.log( msg );
}

En el ejemplo anterior

  • la primera línea es una sentencia, donde "Hello World!" es una expresión,
  • la declaración de la función es una sentencia, donde el parámetro msg que se pasa a la función es una expresión,
  • la línea que imprime el mensaje en la consola es una sentencia, donde de nuevo el parámetro msg es una expresión.

Por Qué Son Importantes las Expresiones en React

Cuando construyes una aplicación React, puedes inyectar expresiones JavaScript en tu código JSX. Por ejemplo, puedes pasar una variable, escribir un manejador de eventos o una condición. Para ello, debes incluir tu código JS entre llaves.

Por ejemplo, puedes pasar una variable

const Message = () => {
	const name = "Carlo";
	return <p>Welcome {name}!</p>;
}

En resumen, las llaves indican a tu transpilador que procese el código entre llaves como código JS. Todo lo que viene antes de la etiqueta de apertura <p> y después de la etiqueta de cierre </p> es código JavaScript normal. Todo lo que hay dentro de las etiquetas de apertura <p> y de cierre </p> se procesa como código JSX.

Aquí tienes otro ejemplo:

const Message = () => {	
	const name = "Ann";
	const heading = <h3>Welcome {name}</h3>;
	return (
		<div>
			{heading}
			<p>This is your dashboard.</p>
		</div>
	);
}

También puedes pasar un objeto:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22',
		description: 'Content Writer'
	}

	return (
		<div>
			<h2>Welcome {person.name}</h2>
			<img
				className="card"
				src={person.avatar}
				alt={person.name}
			/>
			<p>Description: {person.description}.</p>
		</div>
	);
}

Y a continuación tienes un ejemplo más completo:

render(){
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: {
			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', width: '200px'
		}
	}

	return (
		<div style={person.theme}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

Observa las dobles llaves en los atributos style de los elementos img y div. Utilizamos dobles llaves para pasar dos objetos que contienen estilos de tarjeta y de imagen.

Una tarjeta de ejemplo creada con React
Una tarjeta de ejemplo creada con React

Te habrás dado cuenta de que en todos los ejemplos anteriores hemos incluido expresiones JavaScript en JSX.

Inmutabilidad en React

Mutabilidad e Inmutabilidad son dos conceptos clave en la programación funcional y orientada a objetos.

Inmutabilidad significa que un valor no puede cambiarse después de haberse creado. Mutabilidad significa, por supuesto, lo contrario.

En Javascript, los valores primitivos son inmutables, es decir, una vez creado un valor primitivo, no se puede modificar. Por el contrario, los arrays y los objetos son mutables porque sus propiedades y elementos pueden modificarse sin reasignar un nuevo valor.

Hay varias razones para utilizar objetos inmutables en JavaScript:

  • Mejora del rendimiento
  • Menor consumo de memoria
  • Seguridad de hilos
  • Programación y depuración más sencillas

Siguiendo el patrón de la inmutabilidad, una vez asignada una variable u objeto, no se puede volver a asignar ni modificar. Cuando necesites modificar datos, debes crear una copia de los mismos y modificar su contenido, dejando inalterado el contenido original.

La inmutabilidad también es un concepto clave en React.

La documentación de React afirma

El estado de un componente de clase está disponible como this.state. El campo de estado debe ser un objeto. No mutes el estado directamente. Si deseas cambiar el estado, llama a setState con el nuevo estado.

Cada vez que cambia el estado de un componente, React calcula si debe volver a renderizar el componente y actualizar el DOM virtual. Si React no tuviera constancia del estado anterior, no podría determinar si debe volver a renderizar el componente o no. La documentación de React proporciona un ejemplo excelente de esto.

¿Qué funciones de JavaScript podemos utilizar para garantizar la inmutabilidad del objeto de estado en React? ¡Averigüémoslo!

Declarar variables

Tienes tres formas de declarar una variable en JavaScript: var, let, y const.

La sentencia var existe desde el principio de JavaScript. Se utiliza para declarar una variable de ámbito global o de ámbito de función, inicializándola opcionalmente con un valor.

Cuando declaras una variable utilizando var, puedes volver a declarar y actualizar esa variable tanto en el ámbito global como en el local. El siguiente código está permitido:

// Declare a variable
var msg = "Hello!";

// Redeclare the same variable
var msg = "Goodbye!"

// Update the variable
msg = "Hello again!"

Las declaraciones var se procesan antes de que se ejecute ningún código. Como resultado, declarar una variable en cualquier parte del código equivale a declararla al principio. Este comportamiento se denomina hoisting (elevación).

Cabe señalar que sólo se eleva la declaración de la variable, no la inicialización, que sólo se produce cuando el flujo de control llega a la sentencia de asignación. Hasta ese momento, la variable es undefined:

console.log(msg); // undefined
var msg = "Hello!";
console.log(msg); // Hello!

El ámbito de una var declarada en una función JS es todo el cuerpo de esa función.

Esto significa que la variable no se define a nivel de bloque, sino a nivel de toda la función. Esto conlleva una serie de problemas que pueden hacer que tu código JavaScript tenga errores y sea difícil de mantener.

Para solucionar estos problemas, ES6 introdujo la palabra clavelet.

La declaración let declara una variable local de ámbito de bloque, inicializándola opcionalmente con un valor.

¿Cuáles son las ventajas de let sobre var? Aquí tienes algunas:

  • let declara una variable al ámbito de una sentencia de bloque, mientras que var declara una variable global o localmente a toda una función, independientemente del ámbito del bloque.
  • Las variables globales de let no son propiedades del objeto window . No puedes acceder a ellas con window.variableName.
  • let sólo es accesible una vez que se ha llegado a su declaración. La variable no se inicializa hasta que el flujo de control llega a la línea de código donde se declara (las declaraciones let no son hoisted).
  • Volver a declarar una variable con let lanza un SyntaxError.

Dado que las variables declaradas con var no se pueden incluir en un bloque, si defines una variable con var en un bucle o dentro de una sentencia if, se puede acceder a ella desde fuera del bloque, lo que puede provocar errores en el código.

El código del primer ejemplo se ejecuta sin errores. Ahora sustituye var por let en el bloque de código visto anteriormente:

console.log(msg);
let msg = "Hello!";
console.log(msg);

En el segundo ejemplo, utilizar let en lugar de var produce un Uncaught ReferenceError:

Uncaught ReferenceError en Chrome
Uncaught ReferenceError en Chrome

ES6 también introduce una tercera palabra clave const.

const es bastante similar a let, pero con una diferencia clave:

Considera el siguiente ejemplo:

const MAX_VALUE = 1000;
MAX_VALUE = 2000;

El código anterior generaría el siguiente TypeError:

Uncaught TypeError: Asignación a variable constante en Google Chrome
Error de tipo no detectado: Asignación a variable constante en Google Chrome

Además

Declarar un const sin darle un valor arrojaría el siguiente SyntaxError (consulta también ES6 En Profundidad: let y const):

Uncaught SyntaxError: Falta inicializador en la declaración en Chrome
Uncaught SyntaxError: Falta inicializador en la declaración const en Chrome

Pero si una constante es un array o un objeto, puedes editar propiedades o elementos dentro de ese array u objeto.

Por ejemplo, puedes cambiar, añadir y eliminar elementos de un array:

// Declare a constant array
const cities = ["London", "New York", "Sydney"];

// Change an item
cities[0] = "Madrid";

// Add an item
cities.push("Paris");

// Remove an item
cities.pop();

console.log(cities);

// Array(3)
// 0: "Madrid"
// 1: "New York"
// 2: "Sydney"

Pero no puedes reasignar el array:

const cities = ["London", "New York", "Sydney"];

cities = ["Athens", "Barcelona", "Naples"];

El código anterior daría lugar a un TypeError.

Uncaught TypeError: Asignación a variable constante.
Uncaught TypeError: Asignación a variable constante en Chrome

Puedes añadir, reasignar y eliminar propiedades y métodos de objetos:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language',
	content: 'JavaScript is a scripting language that enables you to create dynamically updating content.'
};

// add a new property
post.slug = "javascript-is-awesome";

// Reassign property
post.id = 5;

// Delete a property
delete post.excerpt;

console.log(post);

// {id: 5, name: 'JavaScript is awesome', content: 'JavaScript is a scripting language that enables you to create dynamically updating content.', slug: 'javascript-is-awesome'}

Pero no puedes reasignar el propio objeto. El siguiente código pasaría por un Uncaught TypeError:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};

post = {
	id: 1,
	name: 'React is powerful',
	excerpt: 'React lets you build user interfaces'
};

Object.freeze()

Ahora estamos de acuerdo en que utilizar const no siempre garantiza una inmutabilidad fuerte (especialmente cuando se trabaja con objetos y arrays). Entonces, ¿cómo puedes implementar el patrón de inmutabilidad en tus aplicaciones React?

En primer lugar, cuando quieras impedir que se modifiquen los elementos de un array o las propiedades de un objeto, puedes utilizar el método estático Object.freeze().

Congelar un objeto impide las ampliaciones y hace que las propiedades existentes no se puedan escribir ni configurar. Un objeto congelado ya no se puede modificar: no se pueden añadir nuevas propiedades, no se pueden eliminar las existentes, no se puede cambiar su enumerabilidad, configurabilidad, escritura o valor, y no se puede reasignar el prototipo del objeto. freeze() devuelve el mismo objeto que se le pasó.

Cualquier intento de añadir, cambiar o eliminar una propiedad fallará, ya sea silenciosamente o lanzando un TypeError, más comúnmente en modo estricto.

Puedes utilizar Object.freeze() de esta forma:

'use strict'
// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};
// Freeze the object
Object.freeze(post);

Si ahora intentas añadir una propiedad, recibirás un Uncaught TypeError:

// Add a new property
post.slug = "javascript-is-awesome"; // Uncaught TypeError
Uncaught TypeError: no se puede definir la propiedad "slug": El objeto no es extensible
Uncaught TypeError: no se puede definir la propiedad «slug»: El objeto no es extensible en Firefox

Cuando intentas reasignar una propiedad, obtienes otro tipo de TypeError:

// Reassign property
post.id = 5; // Uncaught TypeError
Reasignar una propiedad de sólo lectura lanza un Uncaught TypeError
Reasignar una propiedad de sólo lectura lanza un Uncaught TypeError
Uncaught TypeError: No se puede asignar a la propiedad de sólo lectura en Chrome
Uncaught TypeError: No se puede asignar a la propiedad de sólo lectura ‘id’ del objeto ‘#<Object>’ en Google Chrome

También puedes intentar eliminar una propiedad. El resultado será otro TypeError:

// Delete a property
delete post.excerpt; // Uncaught TypeError
Uncaught TypeError: la propiedad "excerpt" no es configurable y no se puede eliminar en Firefox
Uncaught TypeError: la propiedad «excerpt» no es configurable y no se puede eliminar en Firefox

Template Literals

Cuando necesitas combinar cadenas con la salida de expresiones en JavaScript, sueles utilizar el operador de suma +. Sin embargo, también puedes utilizar una función de JavaScript que te permite incluir expresiones dentro de cadenas sin utilizar el operador de adición: Template Literals.

Template Literals son un tipo especial de cadenas delimitadas por caracteres de acento grave (`) o backtick.

En los literales de plantilla puedes incluir marcadores de posición, que son expresiones incrustadas delimitadas por un carácter del dólar y entre llaves.

Aquí tienes un ejemplo:

const align = 'left';
console.log(`This string is ${ align }-aligned`);

Las cadenas y los marcadores de posición se pasan a una función predeterminada que realiza una interpolación de cadenas para sustituir los marcadores de posición y concatenar las partes en una sola cadena. También puedes sustituir la función por defecto por una función personalizada.

Puedes utilizar Template Literals para:

Cadenas de varias líneas: los caracteres de nueva línea forman parte del literal de plantilla.

console.log(`Twinkle, twinkle, little bat!
How I wonder what you’re at!`);

Interpolación de cadenas: Sin Template Literals, sólo puedes utilizar el operador de suma para combinar la salida de expresiones con cadenas. Mira el siguiente ejemplo:

const a = 3;
const b = 7;
console.log("The result of " + a + " + " + b + " is " + (a + b));

Es un poco confuso, ¿verdad? Pero puedes escribir ese código de forma más legible y fácil de mantener utilizando Template Literals:

const a = 3;
const b = 7;
console.log(`The result of ${ a } + ${ b } is ${ a + b }`);

Pero ten en cuenta que hay una diferencia entre ambas sintaxis:

Los Template Literals se prestan a varios usos. En el siguiente ejemplo, utilizamos un operador ternario para asignar un valor a un atributo class.

const page = 'archive';
console.log(`class=${ page === 'archive' ? 'archive' : 'single' }`);

A continuación, realizamos un cálculo sencillo:

const price = 100;
const VAT = 0.22;

console.log(`Total price: ${ (price * (1 + VAT)).toFixed(2) }`);

También es posible anidar Template Literals incluyéndolos dentro de un marcador de posición ${expression} (pero utiliza las plantillas anidadas con precaución porque las estructuras de cadena complejas pueden ser difíciles de leer y mantener).

Tagged templates: Como hemos mencionado antes, también es posible definir una función personalizada para realizar la concatenación de cadenas. Este tipo de Template Literal se denomina Tagged Template.

Las etiquetas te permiten analizar template literals con una función. El primer argumento de una función de etiqueta contiene un array de valores de cadena. Los argumentos restantes están relacionados con las expresiones.

Las etiquetas te permiten analizar template literals con una función personalizada. El primer argumento de esta función es un array de las cadenas incluidas en el Template Literal, los demás argumentos son las expresiones.

Puedes crear una función personalizada para realizar cualquier tipo de operación sobre los argumentos de la plantilla y devolver la cadena manipulada. Aquí tienes un ejemplo muy básico de tagged template:

const name = "Carlo";
const role = "student";
const organization = "North Pole University";
const age = 25;

function customFunc(strings, ...tags) {
	console.log(strings); // ['My name is ', ", I'm ", ', and I am ', ' at ', '', raw: Array(5)]
	console.log(tags); // ['Carlo', 25, 'student', 'North Pole University']
	let string = '';
	for ( let i = 0; i < strings.length - 1; i++ ){
		console.log(i + "" + strings[i] + "" + tags[i]);
		string += strings[i] + tags[i];
	}
	return string.toUpperCase();
}

const output = customFunc`My name is ${name}, I'm ${age}, and I am ${role} at ${organization}`;
console.log(output);

El código anterior imprime los elementos del array strings y tags y luego pone en mayúsculas los caracteres de la cadena antes de imprimir la salida en la consola del navegador.

Arrow Functions

Las Arrow functions son una alternativa a las funciones anónimas (funciones sin nombre) en JavaScript, pero con algunas diferencias y limitaciones.

Las siguientes declaraciones son ejemplos válidos de Arrow Functions:

// Arrow function without parameters
const myFunction = () => expression;

// Arrow function with one parameter
const myFunction = param => expression;

// Arrow function with one parameter
const myFunction = (param) => expression;

// Arrow function with more parameters
const myFunction = (param1, param2) => expression;

// Arrow function without parameters
const myFunction = () => {
	statements
}

// Arrow function with one parameter
const myFunction = param => {
	statements
}

// Arrow function with more parameters
const myFunction = (param1, param2) => {
	statements
}

Puedes omitir las llaves si sólo pasas un parámetro a la función. Si pasas dos o más parámetros, debes encerrarlos entre llaves. Aquí tienes un ejemplo:

const render = ( id, title, category ) => `${id}: ${title} - ${category}`;
console.log( render ( 5, 'Hello World!', "JavaScript" ) );

Las Arrow Functions de una línea devuelven un valor por defecto. Si utilizas la sintaxis de varias líneas, tendrás que devolver manualmente un valor:

const render = ( id, title, category ) => {
	console.log( `Post title: ${ title }` );
	return `${ id }: ${ title } - ${ category }`;
}
console.log( `Post details: ${ render ( 5, 'Hello World!', "JavaScript" ) }` );

Una diferencia clave a tener en cuenta entre las funciones normales y las Arrow Functions es que las Arrow Functions no tienen sus propios enlaces a la palabra clave this. Si intentas utilizar this en una Arrow Functions, se saldrá del ámbito de la función.

Para una descripción más detallada de las Arrow Functions y ejemplos de uso, lee también mdn web docs.

Clases

Las clases en JavaScript son un tipo especial de función para crear objetos que utilizan el mecanismo de herencia prototípica.

Según la documentación web de mdn

En lo que a herencia se refiere, JavaScript sólo tiene una estructura: objetos. Cada objeto tiene una propiedad privada (referida como su [[Prototype]]) que mantiene un enlace a otro objeto llamado su prototipo. Ese objeto prototipo tiene su propio prototipo, y así sucesivamente hasta que se alcanza un objeto cuyo prototipo es null

Al igual que con las funciones, tienes dos formas de definir una clase:

  • Una expresión de clase
  • Una declaración de clase

Puedes utilizar la palabra clave class para definir una clase dentro de una expresión, como se muestra en el siguiente ejemplo:

const Circle = class {
	constructor(radius) {
		this.radius = Number(radius);
	}
	area() {
		return Math.PI * Math.pow(this.radius, 2);
	}
	circumference() {
		return Math.PI * this.radius * 2;
	}
}
console.log('Circumference: ' + new Circle(10).circumference()); // 62.83185307179586
console.log('Area: ' + new Circle(10).area()); // 314.1592653589793

Una clase tiene un cuerpo, que es el código incluido entre llaves. Aquí definirás el constructor y los métodos, que también se denominan miembros de la clase. El cuerpo de la clase se ejecuta en modo estricto incluso sin utilizar la directiva 'strict mode'.

El método constructor se utiliza para crear e inicializar un objeto creado con una clase y se ejecuta automáticamente al instanciar la clase. Si no defines un método constructor en tu clase, JavaScript utilizará automáticamente un constructor por defecto.

Una clase puede ampliarse utilizando la palabra clave extends.

class Book {
	constructor(title, author) {
		this.booktitle = title;
		this.authorname = author;
	}
	present() {
		return this.booktitle + ' is a great book from ' + this.authorname;
	}
}

class BookDetails extends Book {
	constructor(title, author, cat) {
		super(title, author);
		this.category = cat;
	}
	show() {
		return this.present() + ', it is a ' + this.category + ' book';
	}
}

const bookInfo = new BookDetails("The Fellowship of the Ring", "J. R. R. Tolkien", "Fantasy");
console.log(bookInfo.show());

Un constructor puede utilizar la palabra clave super para llamar al constructor padre. Si pasas un argumento al método super(), este argumento también estará disponible en la clase del constructor padre.

Para profundizar en las clases de JavaScript y ver varios ejemplos de uso, consulta también los documentos web de mdn.

Las clases se utilizan a menudo para crear componentes React. Normalmente, no crearás tus propias clases, sino que ampliarás las clases React incorporadas.

Todas las clases de React tienen un método render() que devuelve un elemento React:

class Animal extends React.Component {
	render() {
		return <h2>Hey, I am a {this.props.name}!</h2>;
	}
}

En el ejemplo anterior, Animal es un componente de clase. Ten en cuenta que

  • El nombre del componente debe empezar por mayúscula
  • El componente debe incluir la expresión extends React.Component. Esto da acceso a los métodos de React.Component.
  • El método render() devuelve el HTML y es obligatorio.

Una vez que hayas creado tu componente de clase, puedes renderizar el HTML en la página:

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Animal name="Rabbit" />;
root.render(element);

La imagen de abajo muestra el resultado en la página (Puedes verlo en acción en CodePen).

Un simple componente de clase React
Un simple componente de clase React

Ten en cuenta, sin embargo, que no se recomienda utilizar componentes de clase en React y que es preferible definir los componentes como funciones.

La Palabra Clave ‘this’

En JavaScript, la palabra clave this es un marcador de posición genérico que suele utilizarse dentro de objetos, clases y funciones, y hace referencia a distintos elementos en función del contexto o ámbito.

this puede utilizarse en el ámbito global. Si escribes this en la consola de tu navegador, obtendrás:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Puedes acceder a cualquiera de los métodos y propiedades del objeto Window. Por tanto, si ejecutas this.location en la consola de tu navegador, obtendrás la siguiente salida:

Location {ancestorOrigins: DOMStringList, href: 'https://kinsta.com/', origin: 'https://kinsta.com', protocol: 'https:', host: 'kinsta.com', ...}

Cuando utilizas this en un objeto, se refiere al propio objeto. De este modo, puedes hacer referencia a los valores de un objeto en los métodos del propio objeto:

const post = { 
	id: 5,
	getSlug: function(){
		return `post-${this.id}`;
	},
	title: 'Awesome post', 
	category: 'JavaScript' 
};
console.log( post.getSlug );

Ahora vamos a intentar utilizar this en una función:

const useThis = function () {
	return this;
}
console.log( useThis() );

Si no estás en modo estricto, obtendrás

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Pero si invocas el modo estricto, obtendrás un resultado diferente:

const doSomething = function () {
	'use strict';
	return this;
}
console.log( doSomething() );

En este caso, la función devuelve undefined. Eso es porque this en una función se refiere a su valor explícito.

Entonces, ¿cómo establecer explícitamente this en una función?

En primer lugar, puedes asignar manualmente propiedades y métodos a la función:

function doSomething( post ) {
	this.id = post.id;
	this.title = post.title;
	console.log( `${this.id} - ${this.title}` );
}
new doSomething( { id: 5, title: 'Awesome post' } );

Pero también puedes utilizar los métodos call, apply y bind, así como las funciones de flecha.

const doSomething = function() {
	console.log( `${this.id} - ${this.title}` );
}
doSomething.call( { id: 5, title: 'Awesome post' } );

El método call() puede utilizarse en cualquier función y hace exactamente lo que dice: llama a la función.

Además, call() acepta cualquier otro parámetro definido en la función:

const doSomething = function( cat ) {
	console.log( `${this.id} - ${this.title} - Category: ${cat}` );
}
doSomething.call( { id: 5, title: 'Awesome post' }, 'JavaScript' );
const doSomething = function( cat1, cat2 ) {
	console.log( `${this.id} - ${this.title} - Categories: ${cat1}, ${cat2}` );
}
doSomething.apply( { id: 5, title: 'Awesome post' }, ['JavaScript', 'React'] );
const post = { id: 5, title: 'Awesome post', category: 'JavaScript' };
const doSomething = function() {
	return `${this.id} - ${this.title} - ${this.category}`;
}
const bindRender = doSomething.bind( post );
console.log( bindRender() );

Una alternativa a las opciones comentadas anteriormente es utilizar arrow functions.

Las expresiones arrow function sólo deben utilizarse para funciones que no sean métodos, porque no tienen su propio this.

Esto hace que las funciones flecha sean especialmente útiles con los manejadores de eventos.

Esto se debe a que «cuando se llama al código desde un atributo de controlador de eventos inline, su this se establece en el elemento DOM en el que está colocado el oyente» (consulta la documentación web de mdn).

Pero las cosas cambian con las arrow functions porque…

… las arrow functions establecen this en función del ámbito en el que se define la arrow function, y el valor de this no cambia en función de cómo se invoque la función.

Vinculación de ‘this’ a Manejadores de Eventos en React

Cuando se trata de React, tienes varias formas de asegurarte de que el manejador de eventos no pierde su contexto:

1. Utilizando bind() dentro del método render:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage.bind( this ) }>Show message from state!</button> );
	}
}
export default MyComponent;

2. Vinculando el contexto al manejador de eventos en el constructor:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	constructor(props) {
		super(props);
		this.showMessage = this.showMessage.bind( this );
	}

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage }>Show message from state!</button> );
	}
}
export default MyComponent;

3. Definiendo el manejador de eventos usando arrow functions:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage = () => {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={this.showMessage}>Show message from state!</button> );
	}
}
export default MyComponent;

4. Utilizando arrow functions en el método de representación:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage() {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={()=>{this.showMessage()}}>Show message from state!</button> );
	}
}
export default MyComponent;

Sea cual sea el método que elijas, cuando pulses el botón, la consola del navegador mostrará la siguiente salida:

This refers to:  MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
The message is:  Hello World!

Operador Ternario

El operador condicional (u operador ternario) te permite escribir expresiones condicionales sencillas en JavaScript. Toma tres operandos:

  • una condición seguida de un signo de interrogación (?),
  • una expresión a ejecutar si la condición es verdadera seguida de un punto y coma (:),
  • una segunda expresión a ejecutar si la condición es falsa.
const drink = personAge >= 18 ? "Wine" : "Juice";

También es posible encadenar varias expresiones:

const drink = personAge >= 18 ? "Wine" : personAge >= 6 ? "Juice" : "Milk";

Pero ten cuidado, porque encadenar varias expresiones puede dar lugar a un código desordenado y difícil de mantener.

El operador ternario es especialmente útil en React, sobre todo en tu código JSX, que sólo acepta expresiones entre llaves.

Por ejemplo, puedes utilizar el operador ternario para establecer el valor de un atributo en función de una condición específica:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/...',
		description: 'Content Writer',
		theme: 'light'
	}

	return (
		<div
			className='card' 
			style={
				person.theme === 'dark' ? 
				{ background: 'black', color: 'white' } : 
				{ background: 'white', color: 'black'} 
			}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

En el código anterior, comprobamos la condición person.theme === 'dark' para establecer el valor del atributo style del contenedor div.

Short Circuit Evaluation (Evaluación por Cortocircuito)

El operador lógico AND (&&) evalúa los operandos de izquierda a derecha y devuelve true si y sólo si todos los operandos son true.

El AND lógico es un short-circuit operator. Cada operando se convierte en un booleano y, si el resultado de la conversión es false, el operador AND se detiene y devuelve el valor original del operando falso. Si todos los valores son true, devuelve el valor original del último operando.



Short circuit evaluation es una función de JavaScript que se utiliza habitualmente en React, ya que te permite devolver bloques de código en función de condiciones específicas. Aquí hay un ejemplo:

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

En el código anterior, si displayExcerpt Y post.excerpt.rendered se evalúan como true, React devuelve el bloque final de JSX.

Para recapitular, «si la condición es true, el elemento situado justo después de && aparecerá en la salida. Si es false, React lo ignorará y omitirá».

Spread Syntax (Sintaxis de Propagación)

En JavaScript, spread syntax te permite expandir un elemento iterable, como un array o un objeto, en argumentos de función, literales de array o literales de objeto.

En el siguiente ejemplo, estamos descomprimiendo un array en una llamada a función:

function doSomething( x, y, z ){
	return `First: ${x} - Second: ${y} - Third: ${z} - Sum: ${x+y+z}`;
}
const numbers = [3, 4, 7];
console.log( doSomething( ...numbers ) );

Puedes utilizar la spread syntax para duplicar un array (incluso arrays multidimensionales) o para concatenar arrays. En los siguientes ejemplos, concatenamos dos arrays de dos formas distintas:

const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray.push( ...secondArray );
console.log( firstArray );

Alternativamente:

let firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray = [ ...firstArray, ...secondArray];
console.log( firstArray );

También puedes utilizar spread syntax para clonar o fusionar dos objetos:

const firstObj = { id: '1', title: 'JS is awesome' };
const secondObj = { cat: 'React', description: 'React is easy' };

// clone object
const thirdObj = { ...firstObj };

// merge objects
const fourthObj = { ...firstObj, ...secondObj }

console.log( { ...thirdObj } );
console.log( { ...fourthObj } );

Destructuring Assignment (Asignación por Desestructuración)

Otra estructura sintáctica que encontrarás utilizada a menudo en React es la sintaxis destructuring assignment

En el siguiente ejemplo, desempaquetamos valores de un array:

const user = ['Carlo', 'Content writer', 'Kinsta'];
const [name, description, company] = user;
console.log( `${name} is ${description} at ${company}` );

Y aquí tienes un ejemplo sencillo de destructuring assignment con un objeto:

const user = {
	name: 'Carlo',
	description: 'Content writer',
	company: 'Kinsta'
}
const { name, description, company } = user;
console.log( `${name} is ${description} at ${company}` );

Pero podemos hacer aún más. En el siguiente ejemplo, desestructuramos algunas propiedades de un objeto y asignamos las restantes a otro objeto utilizando spread syntax

const user = {
	name: 'Carlo',
	family: 'Daniele',
	description: 'Content writer',
	company: 'Kinsta',
	power: 'swimming'
}
const { name, description, company, ...rest } = user;
console.log( rest ); // {family: 'Daniele', power: 'swimming'}

También puedes asignar valores a un array:

const user = [];
const object = { name: 'Carlo', company: 'Kinsta' };
( { name: user[0], company: user[1] } = object );
console.log( user ); // (2) ['Carlo', 'Kinsta']

Ten en cuenta que los paréntesis alrededor de la sentencia de asignación son necesarios cuando se utiliza la asignación desestructurada literal de objeto sin declaración.

Para un análisis más profundo de la asignación desestructurante, con varios ejemplos de uso, consulta la documentación web de mdn.

filter(), map() y reduce()

JavaScript proporciona varios métodos útiles que utilizarás a menudo en React.

filter()

En el siguiente ejemplo, aplicamos el filtro al array numbers para obtener un array cuyos elementos sean números mayores que 5:

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.filter( number => number > 5);
console.log(result); // (4) [6, 8, 9, 23]

En el siguiente ejemplo, obtenemos un array de entradas con la palabra «JavaScript» incluida en el título:

const posts = [
	{id: 0, title: 'JavaScript is awesome', content: 'your content'},
	{id: 1, title: 'WordPress is easy', content: 'your content'},
	{id: 2, title: 'React is cool', content: 'your content'},
	{id: 3, title: 'With JavaScript to the moon', content: 'your content'},
];

const jsPosts = posts.filter( post => post.title.includes( 'JavaScript' ) );

console.log( jsPosts );
Una array de entradas cuyo título incluye "JavaScript"
Una array de entradas cuyo título incluye «JavaScript»

map()

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.map( number => number * 5 );
console.log(result); // (7) [10, 30, 40, 10, 25, 45, 115]

En un componente React, a menudo encontrarás el método map() utilizado para construir listas. En el siguiente ejemplo, estamos mapeando el objeto posts de WordPress para construir una lista de entradas:

<ul>
	{ posts && posts.map( ( post ) => {
		return (
			<li key={ post.id }>
				<h5>
					<a href={ post.link }>
						{ 
							post.title.rendered ? 
							post.title.rendered :
							__( 'Default title', 'author-plugin' )
						}
					</a>
				</h5>
			</li>
		)
	})}
</ul>

reduce()

reduce() acepta dos parámetros:

  • Una función callback a ejecutar para cada elemento del array. Devuelve un valor que se convierte en el valor del parámetro acumulador en la siguiente llamada. En la última llamada, la función devuelve el valor que será el valor de retorno de reduce().
  • Un valor inicial que es el primer valor del acumulador pasado a la función callback.

La función callback toma unos cuantos parámetros:

  • Un acumulador: El valor devuelto por la llamada anterior a la función de callback. En la primera llamada, se establece en un valor inicial si se especifica. En caso contrario, toma el valor del primer elemento del array.
  • El valor del elemento actual: El valor se establece en el primer elemento del array (array[0]) si se ha establecido un valor inicial, de lo contrario, toma el valor del segundo elemento (array[1]).
  • El índice actual es la posición del índice del elemento actual.

Un ejemplo lo aclarará todo.

const numbers = [1, 2, 3, 4, 5];
const initialValue = 0;
const sumElements = numbers.reduce(
	( accumulator, currentValue ) => accumulator + currentValue,
	initialValue
);
console.log( numbers ); // (5) [1, 2, 3, 4, 5]
console.log( sumElements ); // 15

Veamos en detalle qué ocurre en cada iteración. Vuelve al ejemplo anterior y cambia el initialValue:

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log('Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index);
		return accumulator + currentValue;
	},
	initialValue
);
console.log( sumElements );

La siguiente imagen muestra la salida en la consola del navegador:

utilizando reduce() con el valor inicial fijado en 5
utilizando reduce() con el valor inicial fijado en 5

Ahora veamos qué ocurre sin el parámetro initialValue:

const numbers = [1, 2, 3, 4, 5];
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log( 'Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index );
		return accumulator + currentValue;
	}
);
console.log( sumElements );
Utilizando reduce() sin valor inicial
Utilizando reduce() sin valor inicial

Puedes encontrar más ejemplos y casos de uso en el sitio web mdn web docs.

Exportaciones e Importaciones

A partir de ECMAScript 2015 (ES6), es posible exportar valores de un módulo JavaScript e importarlos a otro script. Utilizarás mucho las importaciones y exportaciones en tus aplicaciones React y, por tanto, es importante que entiendas bien cómo funcionan.

El siguiente código crea un componente funcional. La primera línea importa la biblioteca React:

import React from 'react';

function MyComponent() {
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: 'dark'
	}
 
	return (
		<div
			className = 'card'
			style = {
				person.theme === 'dark' ?
				{ background: 'black', color: 'white' } :
				{ background: 'white', color: 'black'}
			}>
			<img
				src = { person.avatar }
				alt = { person.name }
				style = { { width: '100%' } }
			/>
			<div
				style = { { padding: '2px 16px' } }
			>
				<h3>{ person.name }</h3>
				<p>{ person.description }.</p>
			</div>
		</div>
	);
}
export default MyComponent;

Utilizamos la palabra clave import seguida del nombre que queremos asignar a lo que estamos importando, seguido del nombre del paquete que queremos instalar tal y como se menciona en el archivo package.json.

Observa que en la función MyComponent() anterior, hemos utilizado algunas de las características de JavaScript comentadas en las secciones anteriores. Incluimos los valores de las propiedades entre llaves y asignamos el valor de la propiedad style utilizando la sintaxis del operador condicional.

El script finaliza con la exportación de nuestro componente personalizado.

Ahora que sabemos un poco más sobre importaciones y exportaciones, veamos más detenidamente cómo funcionan.

Exportar

Cada módulo React puede tener dos tipos diferentes de exportación: exportación con nombre y exportación por defecto.

Por ejemplo, puedes exportar varias funciones a la vez con una sola sentencia export:

export { MyComponent, MyVariable };

También puedes exportar características individuales (function, class, const, let):

export function MyComponent() { ... };
export let myVariable = x + y;

Pero sólo puedes tener una única exportación por defecto:

export default MyComponent;

También puedes utilizar la exportación por defecto para funciones individuales:

export default function() { ... }
export default class { ... }

Importar

Una vez exportado el componente, puedes importarlo a otro archivo, por ejemplo un archivo index.js, junto con otros módulos:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyComponent from './MyComponent';

const root = ReactDOM.createRoot( document.getElementById( 'root' ) );
root.render(
	<React.StrictMode>
		<MyComponent />
	</React.StrictMode>
);

En el código anterior, hemos utilizado la declaración de importación de varias formas.

En las dos primeras líneas, asignamos un nombre a los recursos importados, en la tercera línea no asignamos un nombre sino que simplemente importamos el archivo ./index.css. La última sentencia import importa el archivo ./MiComponente y le asigna un nombre.

Veamos las diferencias entre esas importaciones.

En total, hay cuatro tipos de importaciones:

Importación con nombre

import { MyFunction, MyVariable } from "./my-module";

Importación por defecto

import MyComponent from "./MyComponent";

Importación namespace

import * as name from "my-module";

Importación side effect

import "module-name";

Una vez que hayas añadido unos cuantos estilos en tu index.css, tu tarjeta debería tener el aspecto de la imagen siguiente, donde también puedes ver el código HTML correspondiente:

Un componente React sencillo
Un componente React sencillo

Ten en cuenta que las declaraciones import sólo pueden utilizarse en módulos en el nivel superior (no dentro de funciones, clases, etc.).

Para una visión más completa de las declaraciones import y export, también puedes consultar los siguientes recursos:

Resumen

React es una de las bibliotecas JavaScript más populares hoy en día y es una de las habilidades más solicitadas en el mundo del desarrollo web.

Con React es posible crear aplicaciones web dinámicas e interfaces avanzadas. Crear aplicaciones grandes, dinámicas e interactivas puede ser fácil gracias a sus componentes reutilizables.

Pero React es una biblioteca de JavaScript, y una buena comprensión de las principales características de JavaScript es esencial para comenzar tu viaje con React. Por eso hemos reunido en un solo lugar algunas de las características de JavaScript que encontrarás más a menudo en React. Dominar esas funciones te dará una ventaja en tu viaje de aprendizaje de React.

Y cuando se trata de desarrollo web, pasar de JS/React a WordPress requiere muy poco esfuerzo.

Ahora es tu turno, ¿qué funciones de JavaScript crees que son más útiles en el desarrollo con React? ¿Nos hemos dejado algo importante que te hubiera gustado ver en nuestra lista? Comparte tu opinión con nosotros en los comentarios.

Carlo Daniele Kinsta

Carlo es un diseñador y desarrollador de front-end freelance. Cuando escribe artículos y tutoriales, Carlo se ocupa principalmente de los estándares web, pero cuando juega con sitios web, su mejor compañero de trabajo es WordPress.