Capítulo 4. Cómo funciona React

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Hasta ahora en tu viaje, has repasado la sintaxis más reciente. Has repasado los patrones de programación funcional que guiaron la creación de React. Estos pasos te han preparado para dar el siguiente paso, para hacer lo que has venido a hacer aquí: aprender cómo funciona React. Empecemos a escribir código React de verdad.

Cuando trabajas con React, es más que probable que crees tus aplicaciones con JSX. JSX es una sintaxis de JavaScript basada en etiquetas que se parece mucho a HTML. Es una sintaxis en la que profundizaremos en el próximo capítulo y que seguiremos utilizando durante el resto del libro. Sin embargo, para entender realmente React, necesitamos comprender sus unidades más atómicas: los elementos React. A partir de ahí, nos adentraremos en los elementos React. A partir de ahí, nos adentraremos en los componentes React viendo cómo podemos crear componentes personalizados que componen otros componentes y elementos.

Configurar página

En para trabajar con React en el navegador, necesitamos incluir dos bibliotecas: React y ReactDOM. React es la biblioteca para crear vistas. ReactDOM es la biblioteca que se utiliza para renderizar realmente la interfaz de usuario en el navegador. Ambas bibliotecas están disponibles como scripts en el CDN de unpkg (los enlaces se incluyen en el código siguiente). Vamos a crear un documento HTML:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Samples</title>
  </head>
  <body>
    <!-- Target container -->
    <div id="root"></div>

    <!-- React library & ReactDOM (Development Version)-->
    <script
  src="https://unpkg.com/react@16/umd/react.development.js">
  </script>
    <script
  src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">
  </script>

    <script>
      // Pure React and JavaScript code
    </script>
  </body>
</html>

Estos son los requisitos mínimos para trabajar con React en el navegador. Puedes colocar tu JavaScript en un archivo aparte, pero debe cargarse en algún lugar de la página después de que se haya cargado React. En vamos a utilizar la versión de desarrollo de React para ver todos los mensajes de error y advertencias en la consola del navegador. Puedes optar por utilizar la versión de producción minificada utilizando react.production.min.js yreact-dom.production.min.js, que eliminará esas advertencias.

Elementos React

HTML es simplemente un conjunto de instrucciones que sigue un navegador al construir el DOM. Los elementos que componen un documento HTML se convierten en elementos DOM cuando el navegador carga el HTML y representa la interfaz de usuario.

Supongamos que tienes que construir una jerarquía HTML para una receta. Una posible solución para esa tarea podría ser algo así

<section id="baked-salmon">
  <h1>Baked Salmon</h1>
  <ul class="ingredients">
    <li>2 lb salmon</li>
    <li>5 sprigs fresh rosemary</li>
    <li>2 tablespoons olive oil</li>
    <li>2 small lemons</li>
    <li>1 teaspoon kosher salt</li>
    <li>4 cloves of chopped garlic</li>
  </ul>
  <section class="instructions">
    <h2>Cooking Instructions</h2>
    <p>Preheat the oven to 375 degrees.</p>
    <p>Lightly coat aluminum foil with oil.</p>
    <p>Place salmon on foil</p>
    <p>Cover with rosemary, sliced lemons, chopped garlic.</p>
    <p>Bake for 15-20 minutes until cooked through.</p>
    <p>Remove from oven.</p>
  </section>
</section>

En HTML, los elementos se relacionan entre sí en una jerarquía que se asemeja a un árbol genealógico. Podríamos decir que el elemento raíz (en este caso, una sección) tiene tres hijos: un encabezamiento, una lista desordenada de ingredientes y una sección para las instrucciones.

En el pasado, los sitios web consistían en páginas HTML independientes. Cuando el usuario navegaba por estas páginas, el navegador solicitaba y cargaba distintos documentos HTML. La invención de AJAX (JavaScript asíncrono y XML) nos trajo la aplicación de una sola página, oSPA. Como los navegadores podían solicitar y cargar pequeños fragmentos de datos mediante AJAX, ahora podían ejecutarse aplicaciones web enteras desde una sola página y depender de JavaScript para actualizar la interfaz de usuario.

En una SPA, el navegador carga inicialmente un documento HTML. A medida que los usuarios navegan por el sitio, en realidad permanecen en la misma página. JavaScript destruye y crea una nueva interfaz de usuario a medida que el usuario interactúa con la aplicación. Puede parecer que estás saltando de una página a otra, pero en realidad sigues en la misma página HTML, y JavaScript está haciendo el trabajo pesado.

La API DOM es una colección de objetos que JavaScript puede utilizar para interactuar con el navegador y modificar el DOM. Si has utilizado document.createElement o document.appendChild, habrás trabajado con la API DOM.

React es una biblioteca diseñada para actualizar el DOM del navegador por nosotros. Ya no tenemos que preocuparnos de las complejidades asociadas a la construcción de SPA de alto rendimiento, porque React puede hacerlo por nosotros. Con React, no interactuamos directamente con la API del DOM. En su lugar, proporcionamos instrucciones sobre lo que queremos que React construya, y React se encargará de renderizar y reconciliar los elementos que le hemos ordenado crear.

El DOM del navegador está formado por elementos DOM. Del mismo modo, el DOM de React está formado por elementos React. Los elementos DOM y los elementos React pueden parecer iguales, pero en realidad son bastante diferentes. Un elemento React es una descripción del aspecto que debería tener el elemento DOM real. En otras palabras, los elementos React son las instrucciones de cómo debe crearse el DOM del navegador.

Podemos crear un elemento React para representar un h1 utilizandoReact.createElement:

React.createElement("h1", { id: "recipe-0" }, "Baked Salmon");

El primer argumento define el tipo de elemento que queremos crear. En este caso, queremos crear un elemento h1. El segundo argumento representa las propiedades del elemento. Este h1 tiene actualmente un id derecipe-0. El tercer argumento representa los hijos del elemento: cualquier nodo que se inserte entre la etiqueta de apertura y la de cierre (en este caso, sólo algo de texto).

Durante la renderización, React convertirá este elemento en un elemento DOM real:

<h1 id="recipe-0">Baked Salmon</h1>

Las propiedades se aplican de forma similar al nuevo elemento DOM: las propiedades se añaden a la etiqueta como atributos, y el texto hijo se añade como texto dentro del elemento. Un elemento React no es más que un literal de JavaScript que indica a React cómo construir el elemento DOM.

Si registraras este elemento, tendría el siguiente aspecto:

{
  $$typeof: Symbol(React.element),
  "type": "h1",
  "key": null,
  "ref": null,
  "props": {id: "recipe-0", children: "Baked Salmon"},
  "_owner": null,
  "_store": {}
}

Esta es la estructura de un elemento React. Hay campos que son utilizados por React: _owner, _store, y $$typeof. Los campos key y ref son importantes para los elementos React, pero los presentaremos más adelante. Por ahora, echemos un vistazo más de cerca a los campos type y props.

La propiedad type del elemento React indica a React qué tipo de elemento HTML o SVG debe crear. La propiedad props representa los datos y elementos hijos necesarios para construir un elemento DOM. La propiedad childrensirve para mostrar otros elementos anidados como texto.

Crear elementos

Vamos a echar un vistazo al objeto que devuelve React.createElement. En realidad, no crearás estos elementos a mano, sino que utilizarás la funciónReact.createElement.

ReactDOM

Una vez que hemos creado un elemento React, querremos verlo en el navegador. ReactDOM contiene las herramientas necesarias para renderizar elementos React en el navegador. ReactDOM es donde encontraremos el método render.

Podemos renderizar un elemento React, incluidos sus hijos, en el DOM conReactDOM.render. El elemento que queremos renderizar se pasa como primer argumento, y el segundo argumento es el nodo de destino, donde debemos renderizar el elemento:

const dish = React.createElement("h1", null, "Baked Salmon");

ReactDOM.render(dish, document.getElementById("root"));

Renderizando el elemento título al DOM añadiríamos un elemento h1 aldiv con el id de root, que ya tendríamos definido en nuestro HTML. Construimos este div dentro de la etiqueta body:

<body>
  <div id="root">
    <h1>Baked Salmon</h1>
  </div>
</body>

Todo lo que relaciona con la representación de elementos en el DOM se encuentra en el paqueteReactDOM. En versiones de React anteriores a React 16, sólo podías renderizar un elemento en el DOM. Hoy en día, también es posible renderizar matrices. Cuando se anunció la posibilidad de hacer esto en la ReactConf 2017, todo el mundo aplaudió y gritó. Era algo grande. Esto es lo que parece:

const dish = React.createElement("h1", null, "Baked Salmon");
const dessert = React.createElement("h2", null, "Coconut Cream Pie");

ReactDOM.render([dish, dessert], document.getElementById("root"));

Esto hará que ambos elementos aparezcan como hermanos dentro del contenedor root. ¡Esperamos que hayas aplaudido y gritado!

En la siguiente sección, comprenderemos cómo utilizarprops.children.

Niños

React renderiza elementos hijos utilizando props.children. En la sección anterior, renderizamos un elemento de texto como hijo del elemento h1, y así props.children se convirtió en Baked Salmon. También podríamos renderizar otros elementos React como hijos, creando un árbol de elementos. Por eso utilizamos el término árbol de elementos: el árbol tiene un elemento raíz del que crecen muchas ramas.

Consideremos la lista desordenada que contiene ingredientes:

<ul>
  <li>2 lb salmon</li>
  <li>5 sprigs fresh rosemary</li>
  <li>2 tablespoons olive oil</li>
  <li>2 small lemons</li>
  <li>1 teaspoon kosher salt</li>
  <li>4 cloves of chopped garlic</li>
</ul>

En este ejemplo, la lista desordenada es el elemento raíz y tiene seis hijos. Podemos representar este ul y sus hijos conReact.createElement:

React.createElement(
  "ul",
  null,
  React.createElement("li", null, "2 lb salmon"),
  React.createElement("li", null, "5 sprigs fresh rosemary"),
  React.createElement("li", null, "2 tablespoons olive oil"),
  React.createElement("li", null, "2 small lemons"),
  React.createElement("li", null, "1 teaspoon kosher salt"),
  React.createElement("li", null, "4 cloves of chopped garlic")
);

Cada argumento adicional enviado a la función createElement es otro elemento hijo. React crea una matriz de estos elementos hijo y establece el valor de props.children en esa matriz.

Si inspeccionáramos el elemento React resultante, veríamos cada elemento de la lista representado por un elemento React y añadido a una matriz llamadaprops.children. Si registras por consola este elemento

const list = React.createElement(
  "ul",
  null,
  React.createElement("li", null, "2 lb salmon"),
  React.createElement("li", null, "5 sprigs fresh rosemary"),
  React.createElement("li", null, "2 tablespoons olive oil"),
  React.createElement("li", null, "2 small lemons"),
  React.createElement("li", null, "1 teaspoon kosher salt"),
  React.createElement("li", null, "4 cloves of chopped garlic")
);

console.log(list);

El resultado será así:

{
    "type": "ul",
    "props": {
    "children": [
    { "type": "li", "props": { "children": "2 lb salmon" }  },
    { "type": "li", "props": { "children": "5 sprigs fresh rosemary"}  },
    { "type": "li", "props": { "children": "2 tablespoons olive oil" }  },
    { "type": "li", "props": { "children": "2 small lemons"}  },
    { "type": "li", "props": { "children": "1 teaspoon kosher salt"}  },
    { "type": "li", "props": { "children": "4 cloves of chopped garlic"}  }
    ]
    ...
    }
}

Ahora podemos ver que cada elemento de la lista es un hijo. Anteriormente en este capítulo, introdujimos el HTML para una receta completa enraizada en un elemento section. Para crear esto utilizando React, utilizaremos una serie de llamadas a createElement:

React.createElement(
  "section",
  { id: "baked-salmon" },
  React.createElement("h1", null, "Baked Salmon"),
  React.createElement(
    "ul",
    { className: "ingredients" },
    React.createElement("li", null, "2 lb salmon"),
    React.createElement("li", null, "5 sprigs fresh rosemary"),
    React.createElement("li", null, "2 tablespoons olive oil"),
    React.createElement("li", null, "2 small lemons"),
    React.createElement("li", null, "1 teaspoon kosher salt"),
    React.createElement("li", null, "4 cloves of chopped garlic")
  ),
  React.createElement(
    "section",
    { className: "instructions" },
    React.createElement("h2", null, "Cooking Instructions"),
    React.createElement("p", null, "Preheat the oven to 375 degrees."),
    React.createElement("p", null, "Lightly coat aluminum foil with oil."),
    React.createElement("p", null, "Place salmon on foil."),
    React.createElement(
      "p",
      null,
      "Cover with rosemary, sliced lemons, chopped garlic."
    ),
    React.createElement(
      "p",
      null,
      "Bake for 15-20 minutes until cooked through."
    ),
    React.createElement("p", null, "Remove from oven.")
  )
);

className en React

Cualquier elemento que tenga un atributo HTML class está utilizando className para esa propiedad en lugar de class. Como class es una palabra reservada en JavaScript, tenemos que utilizar className para definir el atributo class de un elemento HTML. Este ejemplo es el aspecto de React puro. React puro es, en última instancia, lo que se ejecuta en el navegador. Una aplicación React es un árbol de elementos React, todos ellos derivados de un único elemento raíz. Los elementos React son las instrucciones que React utilizará para construir una interfaz de usuario en el navegador.

Construir elementos con datos

La principal ventaja de utilizar React es su capacidad para separar los datos de los elementos de la interfaz de usuario. Como React es sólo JavaScript, podemos añadir lógica JavaScript para ayudarnos a construir el árbol de componentes React. Por ejemplo, los ingredientes pueden almacenarse en una matriz, y podemos asignar esa matriz a los elementos React.

Volvamos atrás y pensemos un momento en la lista desordenada:

React.createElement(
  "ul",
  null,
  React.createElement("li", null, "2 lb salmon"),
  React.createElement("li", null, "5 sprigs fresh rosemary"),
  React.createElement("li", null, "2 tablespoons olive oil"),
  React.createElement("li", null, "2 small lemons"),
  React.createElement("li", null, "1 teaspoon kosher salt"),
  React.createElement("li", null, "4 cloves of chopped garlic")
);

Los datos utilizados en esta lista de ingredientes pueden representarse fácilmente utilizando una matriz de JavaScript:

const items = [
  "2 lb salmon",
  "5 sprigs fresh rosemary",
  "2 tablespoons olive oil",
  "2 small lemons",
  "1 teaspoon kosher salt",
  "4 cloves of chopped garlic"
];

Queremos utilizar estos datos para generar el número correcto de elementos de la lista sin tener que codificar cada uno de ellos. Podemos mapear la matriz y crear elementos de lista para tantos ingredientes como haya:

React.createElement(
  "ul",
  { className: "ingredients" },
  items.map(ingredient => React.createElement("li", null, ingredient))
);

Esta sintaxis crea un elemento React para cada ingrediente de la matriz. Cada cadena se muestra en los elementos hijos de la lista como texto. El valor de cada ingrediente se muestra como elemento de la lista.

Al ejecutar este código, verás un aviso en la consola como el que se muestra en la Figura 4-1.

image
Figura 4-1. Advertencia de la consola

Cuando construimos una lista de elementos hijos iterando a través de una matriz, a React le gusta que cada uno de esos elementos tenga una propiedad key. React utiliza la propiedad keypara actualizar el DOM de forma eficiente. Puedes hacer que desaparezca esta advertencia añadiendo una propiedad key única a cada uno de los elementos de la lista. Puedes utilizar el índice del array de cada ingrediente como ese valor único:

React.createElement(
  "ul",
  { className: "ingredients" },
  items.map((ingredient, i) =>
    React.createElement("li", { key: i }, ingredient)
  )
);

Trataremos las claves con más detalle cuando hablemos de JSX, pero añadir esto a cada elemento de la lista borrará la advertencia de la consola.

Componentes React

No importa su tamaño, su contenido o qué tecnologías se utilicen para crearla, una interfaz de usuario se compone de partes. Botones. Listas. Encabezados. Todas estas partes, cuando se juntan, forman una interfaz de usuario. Considera una aplicación de recetas con tres recetas diferentes. Los datos son distintos en cada recuadro, pero las partes necesarias para crear una receta son las mismas (ver Figura 4-2).

image
Figura 4-2. Aplicación Recetas

En React, describimos cada una de estas partes como un componente. Los componentes nos permiten reutilizar la misma estructura, y podemos rellenar esas estructuras con diferentes conjuntos de datos.

Cuando consideres una interfaz de usuario que quieras construir con React, busca oportunidades para descomponer los elementos en piezas reutilizables. Por ejemplo, las recetas de la Figura 4-3 tienen un título, una lista de ingredientes y unas instrucciones. Todos forman parte de una receta más grande o de un componente de la app. Podríamos crear un componente para cada una de las partes resaltadas: ingredientes, instrucciones, etc.

image
Figura 4-3. Cada componente está esquematizado: App, IngredientsList, Instructions

Piensa en lo escalable que es esto. Si queremos mostrar una receta, nuestra estructura de componentes lo soportará. Si queremos mostrar 10.000 recetas, sólo tendremos que crear 10.000 nuevas instancias de ese componente.

Crearemos un componente escribiendo una función. Esa función devolverá una parte reutilizable de una interfaz de usuario. Vamos a crear una función que devuelva una lista desordenada de ingredientes. Esta vez, haremos el postre con una función llamada IngredientsList:

function IngredientsList() {
  return React.createElement(
    "ul",
    { className: "ingredients" },
    React.createElement("li", null, "1 cup unsalted butter"),
    React.createElement("li", null, "1 cup crunchy peanut butter"),
    React.createElement("li", null, "1 cup brown sugar"),
    React.createElement("li", null, "1 cup white sugar"),
    React.createElement("li", null, "2 eggs"),
    React.createElement("li", null, "2.5 cups all purpose flour"),
    React.createElement("li", null, "1 teaspoon baking powder"),
    React.createElement("li", null, "0.5 teaspoon salt")
  );
}

ReactDOM.render(
  React.createElement(IngredientsList, null, null),
  document.getElementById("root")
);

El nombre del componente es IngredientsList, y la función produce elementos con este aspecto:

<IngredientsList>
  <ul className="ingredients">
    <li>1 cup unsalted butter</li>
    <li>1 cup crunchy peanut butter</li>
    <li>1 cup brown sugar</li>
    <li>1 cup white sugar</li>
    <li>2 eggs</li>
    <li>2.5 cups all purpose flour</li>
    <li>1 teaspoon baking powder</li>
    <li>0.5 teaspoon salt</li>
  </ul>
</IngredientsList>

Esto está muy bien, pero hemos codificado estos datos en el componente. ¿Y si pudiéramos construir un componente y luego pasar los datos a ese componente como propiedades? ¿Y si ese componente pudiera mostrar los datos dinámicamente? ¡Quizá algún día ocurra!

Es broma, ese día es ahora. Aquí tienes una selección de secretIngredients necesarios para preparar una receta:

const secretIngredients = [
  "1 cup unsalted butter",
  "1 cup crunchy peanut butter",
  "1 cup brown sugar",
  "1 cup white sugar",
  "2 eggs",
  "2.5 cups all purpose flour",
  "1 teaspoon baking powder",
  "0.5 teaspoon salt"
];

Luego ajustaremos el componente IngredientsList para que mapee sobre estositems, construyendo un li para el número de elementos que haya en la matriz items:

function IngredientsList() {
  return React.createElement(
    "ul",
    { className: "ingredients" },
    items.map((ingredient, i) =>
      React.createElement("li", { key: i }, ingredient)
    )
  );
}

Entonces pasaremos esos secretIngredients como una propiedad llamada items, que es el segundo argumento utilizado en createElement:

ReactDOM.render(
  React.createElement(IngredientsList, { items: secretIngredients }, null),
  document.getElementById("root")
);

Ahora, echemos un vistazo al DOM. La propiedad de datos items es una matriz con ocho ingredientes. Como hicimos las etiquetas li utilizando un bucle, pudimos añadir una clave única utilizando el índice del bucle:

<IngredientsList items="[...]">
  <ul className="ingredients">
    <li key="0">1 cup unsalted butter</li>
    <li key="1">1 cup crunchy peanut butter</li>
    <li key="2">1 cup brown sugar</li>
    <li key="3">1 cup white sugar</li>
    <li key="4">2 eggs</li>
    <li key="5">2.5 cups all purpose flour</li>
    <li key="6">1 teaspoon baking powder</li>
    <li key="7">0.5 teaspoon salt</li>
  </ul>
</IngredientsList>

Si creamos nuestro componente de esta forma, será más flexible. Tanto si la matriz items tiene un elemento como si tiene cien, el componente mostrará cada uno de ellos como un elemento de la lista.

Otro ajuste que podemos hacer aquí es referenciar la matriz items desde React props. En lugar de mapear sobre el global items, haremos queitems esté disponible en el objeto props. Empieza pasando props a la función, y luego mapea sobre props.items:

function IngredientsList(props) {
  return React.createElement(
    "ul",
    { className: "ingredients" },
    props.items.map((ingredient, i) =>
      React.createElement("li", { key: i }, ingredient)
    )
  );
}

También podríamos limpiar un poco el código desestructurando items deprops:

function IngredientsList({ items }) {
  return React.createElement(
    "ul",
    { className: "ingredients" },
    items.map((ingredient, i) =>
      React.createElement("li", { key: i }, ingredient)
    )
  );
}

Todo lo relacionado con la interfaz de usuario de IngredientsList está encapsulado en un componente. Todo lo que necesitamos está ahí.

Componentes React: Un recorrido histórico

Antes de que existieran los componentes de función, había otras formas de crear componentes. Aunque no vamos a dedicar mucho tiempo a estos enfoques, es importante comprender la historia de los componentes React, sobre todo al tratar con estas API en bases de código heredadas. Hagamos un pequeño recorrido histórico por las API de React de tiempos pasados.

Parada 1: createClass

Cuando React se convirtió en código abierto por primera vez en 2013, había una forma de crear un componente: createClass. El uso de React.createClass para crear un componente tiene este aspecto:

const IngredientsList = React.createClass({
  displayName: "IngredientsList",
  render() {
    return React.createElement(
      "ul",
      { className: "ingredients" },
      this.props.items.map((ingredient, i) =>
        React.createElement("li", { key: i }, ingredient)
      )
    );
  }
});

Los componentes que utilizaban createClass tenían un método render() que describía el elemento o elementos React que debían devolverse y renderizarse. La idea del componente era la misma: describir un trozo de interfaz de usuario reutilizable para su representación.

En React 15.5 (abril de 2017), React empezó a lanzar advertencias si se utilizabacreateClass. En React 16 (septiembre de 2017),React.createClass quedó oficialmente obsoleto y se trasladó a su propio paquete, create-react-class.

Parada 2: componentes de la clase

Cuando se añadió la sintaxis de clases a JavaScript con ES 2015, React introdujo un nuevo método para crear componentes React. La APIReact.Component te permitía utilizar la sintaxis de clases para crear una nueva instancia de componente:

class IngredientsList extends React.Component {
  render() {
    return React.createElement(
      "ul",
      { className: "ingredients" },
      this.props.items.map((ingredient, i) =>
        React.createElement("li", { key: i }, ingredient)
      )
    );
  }
}

Todavía es posible crear un componente React utilizando la sintaxis de clases, pero ten en cuenta que React.Component también está en vías de desaparición. Aunque todavía se admite, puedes esperar que siga el camino deReact.createClass, otro viejo amigo que te formó pero al que ya no verás tan a menudo porque se fue y tú seguiste adelante. A partir de ahora, utilizaremos funciones para crear componentes en este libro y sólo señalaremos brevemente patrones antiguos como referencia.

Get Aprendiendo React, 2ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.