Interfaces de Usuario - UI/UX


Índice

  1. Introducción
  2. Interfaces de usuario
  3. Aplicaciones de una página
  4. Scroll
  5. Animación
  6. React

Introducción

Hasta ahora, hemos discutido cómo construir páginas web simples utilizando HTML y CSS, y cómo utilizar Git y GitHub para realizar un seguimiento de los cambios en nuestro código y colaborar con otros. También nos familiarizamos con el lenguaje de programación Python, comenzamos a utilizar Django para crear aplicaciones web y aprendimos a utilizar los modelos de Django para almacenar información en nuestros sitios. Luego, introdujimos JavaScript y aprendimos cómo utilizarlo para hacer que las páginas web sean más interactivas.

Hoy, discutiremos paradigmas comunes en el diseño de interfaces de usuario, utilizando JavaScript y CSS para hacer que nuestros sitios sean aún más amigables para el usuario.

Interfaces de Usuario

Una interfaz de usuario es la forma en que los visitantes interactúan con una página web. Nuestro objetivo como desarrolladores web es hacer que estas interacciones sean lo más agradables posible para el usuario, y hay muchos métodos que podemos usar para lograrlo.

Aplicaciones de una página

Anteriormente, si queríamos un sitio web con múltiples páginas, lo lograríamos utilizando diferentes rutas en nuestra aplicación Django. Ahora, tenemos la capacidad de cargar solo una página y luego utilizar JavaScript para manipular el DOM. Una gran ventaja de hacer esto es que solo necesitamos modificar la parte de la página que realmente está cambiando. Por ejemplo, si tenemos una barra de navegación que no cambia según la página actual, no querríamos tener que volver a renderizar esa barra de navegación cada vez que cambiamos a una nueva parte de la página.

Veamos un ejemplo de cómo podríamos simular el cambio de página en JavaScript:

html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Página Simple</title>
5 <style>
6 div {
7 display: none;
8 }
9 </style>
10 <script src="singlepage.js"></script>
11 </head>
12 <body>
13 <button data-page="page1">Page 1</button>
14 <button data-page="page2">Page 2</button>
15 <button data-page="page3">Page 3</button>
16 <div id="page1">
17 <h1>Esta es la página 1</h1>
18 </div>
19 <div id="page2">
20 <h1>Esta es la página 2</h1>
21 </div>
22 <div id="page3">
23 <h1>Esta es la página 3</h1>
24 </div>
25 </body>
26</html>

Observa en el HTML anterior que tenemos tres botones y tres divisionesdivs. En este momento, las divisiones contienen solo un poco de texto e imágenes, pero podríamos imaginar que cada div contiene el contenido de una página en nuestro sitio. Ahora, agregaremos algo de JavaScript que nos permita utilizar los botones para cambiar entre páginas.

javascript
1// Muestra una página y oculta las otras dos
2function showPage(page) {
3 // Oculta todas las divisiones (divs):
4 document.querySelectorAll('div').forEach(div => {
5 div.style.display = 'none';
6 });
7 // Muestra la div proporcionada en el argumento
8 document.querySelector(`#${page}`).style.display = 'block';
9}
10
11// Espera a que la página se cargue:
12document.addEventListener('DOMContentLoaded', function() {
13 // Selecciona todos los botones
14 document.querySelectorAll('button').forEach(button => {
15 // Cuando se hace clic en un botón, cambia a esa página
16 button.onclick = function() {
17 showPage(this.dataset.page);
18 }
19 })
20});

En muchos casos, sería ineficiente cargar todo el contenido de cada página cuando visitamos un sitio por primera vez, por lo que necesitaremos utilizar un servidor para acceder a nuevos datos. Por ejemplo, al visitar un sitio de noticias, llevaría demasiado tiempo cargar todas las noticias disponibles cuando se visita la página por primera vez. Podemos evitar este problema utilizando una estrategia similar a la que utilizamos al cargar tasas de cambio de divisas en curso de javascript anterior. En esta ocasión, echaremos un vistazo a cómo utilizar Django para enviar y recibir información desde nuestra aplicación de una sola página. Para mostrar cómo funciona esto, echemos un vistazo a una aplicación Django simple. Tiene dos patrones de URL enurls.py:

python
1urlpatterns = [
2 path("", views.index, name="index"),
3 path("sections/<int:num>", views.section, name="section")
4]

Y dos rutas correspondientes en views.py. Observa que la ruta "section" toma un entero y luego devuelve una cadena de texto basada en ese entero como una respuesta HTTP.

python
1from django.http import Http404, HttpResponse
2from django.shortcuts import render
3
4# Crea tus vistas aquí.
5def index(request):
6 return render(request, "singlepage/index.html")
7
8# El texto en realidad es más largo, pero tiene
9# que ser corto aquí para ahorrar espacio
10texts = ["Text 1", "Text 2", "Text 3"]
11
12def section(request, num):
13 if 1 <= num <= 3:
14 return HttpResponse(texts[num - 1])
15 else:
16 raise Http404("No such section")

Ahora, dentro de nuestro archivo index.html, aprovecharemos AJAX, que aprendimos en el curso anterior deDjango, para realizar una solicitud al servidor y obtener el texto de una sección específica para mostrarlo en la pantalla:

jsx
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Página Simple</title>
7 </head>
8 <body>
9 <button data-page="page1">Página 1</button>
10 <button data-page="page2">Página 2</button>
11 <button data-page="page3">Página 3</button>
12 <div id="page1">
13 <h1>Esta es la página 1</h1>
14 </div>
15 <div id="page2">
16 <h1>Esta es la página 2</h1>
17 <p>Y la podemos llenar de contenido como un lorem ipsum dolor...</p>
18 <p>O archivos multimedia..</p>
19 <img
20 src="https://github.com/solidsnk86/GerArt/blob/master/assets/img/portfolio/Varios-HellBoy.jpg?raw=true"
21 alt="Tremendo dibujo!"
22 width="400"
23 height="300"
24 />
25 </div>
26 <div id="page3">
27 <h1>Esta es la página 3</h1>
28 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
29 incididunt ut labore et dolore magna aliqua.
30 </p>
31 <ol>
32 <li>
33 Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
34 ut aliquip ex ea commodo consequat.
35 </li>
36 <li>
37 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
38 eu fugiat nulla pariatur.
39 </li>
40 </ol>
41 </div>
42 <script>
43 function showPage(page) {
44 document.querySelectorAll("div").forEach((div) => {
45 div.style.display = "none";
46 });
47 document.querySelector(`#${page}`).style.display = "block";
48 }
49 document.addEventListener("DOMContentLoaded", function () {
50 document.querySelectorAll("button").forEach((button) => {
51 button.onclick = function () {
52 showPage(this.dataset.page)
53 }

Ahora, hemos creado un sitio donde podemos cargar nuevos datos desde un servidor sin recargar toda nuestra página HTML. Sin embargo, una desventaja de nuestro sitio es que la URL es menos informativa. Te darás cuenta en el video anterior que la URL sigue siendo la misma incluso cuando cambiamos de sección a sección. Podemos resolver este problema utilizando laAPI de Historial de JavaScript. Esta API nos permite agregar información a nuestro historial de navegación y actualizar manualmente la URL. Veamos cómo podemos usar esta API. Imagina que tenemos un proyecto Django idéntico al anterior, pero esta vez queremos modificar nuestro script para emplear la API de historial:

javascript
1// cuando hacemos click en el botón atrás, muestra la sección previa
2window.onpopstate = function(event) {
3 console.log(event.state.section);
4 showSection(event.state.section);
5}
6
7function showSection(section) {
8 fetch(`/sections/${section}`)
9 .then(response => response.text())
10 .then(text => {
11 console.log(text);
12 document.querySelector('#content').innerHTML = text;
13 });
14
15}
16
17document.addEventListener('DOMContentLoaded', function() {
18 document.querySelectorAll('button').forEach(button => {
19 button.onclick = function() {
20 const section = this.dataset.section;
21
22 // Añade el estado actual al historial
23 history.pushState({section: section}, "", `section${section}`);
24 showSection(section);
25 };
26 });
27});

En la funciónshowSectionanterior, utilizamos la funciónhistory.pushState. Esta función agrega un nuevo elemento a nuestro historial de navegación basado en tres argumentos:

  1. Cualquier dato asociado con el estado.
  2. Un parámetro de título ignorado por la mayoría de los navegadores web.
  3. Lo que debería mostrarse en la URL.

El otro cambio que realizamos en el JavaScript anterior es establecer el parámetro onpopstate, que especifica qué deberíamos hacer cuando el usuario hace clic en la flecha hacia atrás. En este caso, queremos mostrar la sección anterior cuando se presiona el botón de retroceso. Ahora, el sitio parece un poco más amigable para el usuario:

Demostración del código, url de la imagen: /images/singlepage3.gif

Scroll

Para actualizar y acceder al historial del navegador, utilizamos un objeto JavaScript importante conocido como window. Hay algunas otras propiedades de window que podemos utilizar para mejorar la apariencia de nuestros sitios:

  • window.innerWidth: Ancho de la ventana en píxeles.
  • window.innerHeight: Altura de la ventana en píxeles.
Demostración del código, url de la imagen: /images/innerMeasures.png

Mientras que window representa lo que actualmente es visible para el usuario, document se refiere a toda la página web, que a menudo es mucho más grande que la ventana, obligando al usuario a desplazarse hacia arriba y hacia abajo para ver el contenido de la página. Para trabajar con el desplazamiento, tenemos acceso a otras variables:

  • window.scrollY: Cuántos píxeles hemos desplazado desde la parte superior de la página.
  • document.body.offsetHeight: La altura en píxeles de todo el documento.
Dscripción del código para scroll, url de la imagen: /images/scroll.png

Podemos utilizar estas medidas para determinar si el usuario ha llegado al final de una página mediante la comparaciónwindow.scrollY + window.innerHeight >=document.body.offsetHeight. La siguiente página, por ejemplo, cambiará el color de fondo a verde cuando lleguemos al final de la página:

javascript
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Scroll</title>
5 <script>
6 // Evento escuchador para el deslizamiento (scrolling)
7 window.onscroll = () => {
8
9 // Chequeamos si estamos en el fondo
10 if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
11
12 // Cambiar color a verde
13 document.querySelector('body').style.background = 'green';
14 } else {
15
16 // Cambiar color a blanco
17 document.querySelector('body').style.background = 'white';
18 }
19 };
20 </script>
21 </head>
22 <body>
23 <p>1</p>
24 <p>2</p>
25 <!-- Más párrafos que dejarán guardar el espacio -->
26 <p>99</p>
27 <p>100</p>
28 </body>
29</html

Scroll Infinito

Cambiar el color de fondo al final de la página probablemente no sea muy útil, pero puede que queramos detectar que hemos llegado al final de la página si queremos implementar un desplazamiento infinito. Por ejemplo, si estás en un sitio de redes sociales, no querrás cargar todas las publicaciones de una vez; podrías querer cargar las primeras diez y, luego, cuando el usuario llegue al final, cargar las siguientes diez. Echemos un vistazo a una aplicación de Django que podría hacer esto. Esta aplicación tiene dos rutas enurls.py.

python
1urlpatterns = [
2 path("", views.index, name="index"),
3 path("posts", views.posts, name="posts")
4]

Y dos vistas correspondientes aviews.py:

python
1import time
2
3from django.http import JsonResponse
4from django.shortcuts import render
5
6# Crea tus vistas aquí.
7def index(request):
8 return render(request, "posts/index.html")
9
10def posts(request):
11
12 # Obtiene un punto de incio y un punto final
13 start = int(request.GET.get("start") or 0)
14 end = int(request.GET.get("end") or (start + 9))
15
16 # Generar una lista de posts
17 data = []
18 for i in range(start, end + 1):
19 data.append(f"Post #{i}")
20
21 # Se retrasa artificialmente el tiempo de respuesta del post
22 time.sleep(1)
23
24 # Devuelve una lista de posts
25 return JsonResponse({
26 "posts": data
27 })

Ten en cuenta que la vista depublicacionesrequiere dos argumentos: un punto deinicioy un puntofinal. En esta vista, hemos creado nuestra propia API, que podemos probar visitando la URLlocalhost:8000/posts?start=10&end=15, la cual devuelve el siguiente JSON:

json
1{
2 "posts": [
3 "Post #10",
4 "Post #11",
5 "Post #12",
6 "Post #13",
7 "Post #14",
8 "Post #15"
9 ]
10}

Ahora, en la plantillaindex.htmlque carga el sitio, comenzamos con solo undivvacío en el cuerpo y algún estilo. Observa que cargamos nuestros archivos estáticos al principio y luego hacemos referencia a un archivo JavaScript dentro de nuestra carpetaestática.

html
1{% load static %}
2<!DOCTYPE html>
3<html>
4 <head>
5 <title>My Webpage</title>
6 <style>
7 .post {
8 background-color: #77dd11;
9 padding: 20px;
10 margin: 10px;
11 }
12
13 body {
14 padding-bottom: 50px;
15 }
16 </style>
17 <script scr="{% static 'posts/script.js' %}"></script>
18 </head>
19 <body>
20 <div id="posts">
21 </div>
22 </body>
23</html>

Ahora, con JavaScript, esperaremos hasta que un usuario haga scroll hasta el final de la página y luego cargaremos más publicaciones utilizando nuestra API:

javascript
1// Comenzar con la primera publicación
2let counter = 1;
3
4// Cargar 20 publicaciones a la vez
5const quantity = 20;
6
7// Cuando el DOM se carga, renderizar las primeras 20 publicaciones
8document.addEventListener('DOMContentLoaded', load);
9
10// Si se hace scroll hasta el final, cargar las siguientes 20 publicaciones
11window.onscroll = () => {
12 if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
13 load();
14 }
15};
16
17// Cargar el próximo conjunto de publicaciones
18function load() {
19
20 // Establecer números de inicio y fin de las publicaciones, y actualizar el contador
21 const start = counter;
22 const end = start + quantity - 1;
23 counter = end + 1;
24
25 // Obtener nuevas publicaciones y agregarlas
26 fetch(`/posts?start=${start}&end=${end}`)
27 .then(response => response.json())
28 .then(data => {
29 data.posts.forEach(add_post);
30 })
31};
32
33// Agregar una nueva publicación con el contenido dado al DOM
34function add_post(contents) {
35
36 // Crear nueva publicación
37 const post = document.createElement('div');
38 post.className = 'post';
39 post.innerHTML = contents;
40
41 // Agregar la publicación al DOM
42 document.querySelector('#posts').append(post);
43};
Descripción del código, url de la imagen: /images/infscroll.gif

Animación

Otra forma de hacer que nuestros sitios sean un poco más interesantes es añadiendo alguna animación a ellos. Resulta que, además de proporcionar estilos, CSS nos facilita la tarea de animar elementos HTML.

Para crear una animación en CSS, utilizamos el formato que se muestra a continuación, donde los detalles de la animación pueden incluir estilos de inicio y fin (from y to), o estilos en diferentes etapas durante la duración (desde 0% hasta 100%). Por ejemplo:

css
1@keyframes animation_name {
2 from {
3 /* Algún estilo del inicio */
4 }
5
6 to {
7 /* Algún estilo del final */
8 }
9}

o también podemos usar un porcentaje:

css
1@keyframes animation_name {
2 0% {
3 /* Algún estilo del inicio */
4 }
5
6 75% {
7 /* Algún estilo de 3/4 de la animación */
8 }
9
10 100% {
11 /* Algún estilo del final */
12 }
13}

Luego, para aplicar una animación a un elemento, incluimos el nombre de la animación (animation-name), la duración de la animación (en segundos) y el modo de llenado de la animación (por lo general, forwards). Por ejemplo, aquí hay una página en la que un título crece cuando entramos por primera vez en la página:

html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Animate</title>
5 <style>
6 @keyframes grow {
7 from {
8 font-size: 20px;
9 }
10 to {
11 font-size: 100px;
12 }
13 }
14 h1 {
15 animation-name: grow;
16 animation-duration: 2s;
17 animation-fill-mode: forwards;
18 }
19 </style>
20 </head>
21 <body>
22 <h1>Bienvenidos a Neotecs!</h1>
23 </body>
24</html>

Podemos hacer más que solo manipular el tamaño: el siguiente ejemplo muestra cómo podemos cambiar la posición de un encabezado simplemente modificando algunas líneas:

css
1@keyframes move {
2 0% {
3 left: 0%;
4 }
5 50% {
6 left: 50%;
7 }
8 100% {
9 left: 0%;
10 }
11}

Ahora, veamos cómo configurar algunas propiedades intermedias de CSS. Podemos especificar el estilo en cualquier porcentaje del recorrido de una animación. En el siguiente ejemplo, moveremos el título de izquierda a derecha y luego de nuevo a la izquierda, alterando solo la animación mencionada anteriormente.

html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Document</title>
7 </head>
8 <body>
9 <style>
10 @keyframes move {
11 0% {
12 left: 0%;
13 }
14 50% {
15 left: 50%;
16 }
17 100% {
18 left: 0%;
19 }
20 }
21 h1 {
22 position: relative;
23 animation-name: move;
24 animation-duration: 2s;
25 animation-fill-mode: forwards;
26 }
27 </style>
28 <h1>Welcome!</h1>
29 </body>
30</html>

Si queremos repetir una animación varias veces, podemos cambiar la propiedadanimation-iteration-counta un número mayor que uno (o inclusoinfinitepara una animación sin fin). Hay muchas propiedades de animación que podemos establecer para cambiar diferentes aspectos de nuestra animación.

Además de CSS, podemos utilizar JavaScript para controlar aún más nuestras animaciones. Utilicemos nuestro ejemplo de encabezado en movimiento(con repetición infinita)para mostrar cómo podemos crear un botón que inicia y detiene la animación. Suponiendo que ya tenemos una animación, un botón y un encabezado, podemos agregar el siguiente script para iniciar y pausar la animación:

javascript
1document.addEventListener("DOMContentLoaded", function () {
2 // Buscar el encabezdo
3 const h1 = document.querySelector("h1");
4
5 // Pausamos la anomación por defecto
6 h1.style.animationPlayState = "paused";
7
8 // Seleccionamos el botón para que espere el click
9 document.querySelector("button").onclick = () => {
10 // Si la animación está pausada, comenzar la animación
11 if (h1.style.animationPlayState == "paused") {
12 h1.style.animationPlayState = "running";
13 }
14 // De otra manera, pausar la animación
15 else {
16 h1.style.animationPlayState = "paused";
17 }
18 };
19});

Ahora, veamos cómo podemos aplicar nuestro nuevo conocimiento de animaciones a la página de publicaciones que creamos anteriormente. Específicamente, supongamos que queremos la capacidad de ocultar las publicaciones una vez que hayamos terminado de leerlas. Imaginemos un proyecto de Django idéntico al que acabamos de crear, pero con HTML y JavaScript ligeramente diferentes. El primer cambio que haremos será en la funciónadd_post, agregando esta vez también un botón al lado derecho de la publicación:

javascript
1// Añadir un nuevo post con los contenidos dados al DOM
2function add_post(contents) {
3
4 // Crear un nuevo post
5 const post = document.createElement('div');
6 post.className = 'post';
7 post.innerHTML = `${contents} <button class="hide">Hide</button>`;
8
9 // Añadir post al DOM
10 document.querySelector('#posts').append(post);
11};

Ahora, trabajaremos en ocultar una publicación cuando se hace clic en el botón de ocultar. Para hacer esto, agregaremos un event listener que se activa cada vez que un usuario hace clic en cualquier parte de la página. Luego, escribiremos una función que tome el evento como argumento, lo cual es útil porque podemos usar el atributoevent.targetpara acceder a lo que se hizo clic. También podemos utilizar la clase `parentElement` para encontrar el elemento padre de un elemento dado en el DOM.

javascript
1// Al hacer click en el botón esconder, eliminar el post
2document.addEventListener('click', event => {
3
4 // Encontrar donde fue el click
5 const element = event.target;
6
7 // Chequear si el usuario hizo click en el botón hide
8 if (element.className === 'hide') {
9 element.parentElement.remove()
10 }
11
12});
Imagen demostrativa, url de la imagen: /images/hide0.gif

Ahora podemos ver que hemos implementado el botón de ocultar, pero no se ve tan bien como podría. Tal vez queremos que la publicación se desvanezca y se encoja antes de eliminarla. Para hacer esto, primero crearemos una animación CSS. La siguiente animación dedicará el 75% de su tiempo cambiando laopacidadde 1 a 0, lo que esencialmente hace que la publicación se desvanezca lentamente. Luego, pasa el resto del tiempo moviendo todos sus atributos relacionados con laalturaa 0, reduciendo efectivamente la publicación a nada.

css
1@keyframes hide {
2 0% {
3 opacity: 1;
4 height: 100%;
5 line-height: 100%;
6 padding: 20px;
7 margin-bottom: 10px;
8 }
9 75% {
10 opacity: 0;
11 height: 100%;
12 line-height: 100%;
13 padding: 20px;
14 margin-bottom: 10px;
15 }
16 100% {
17 opacity: 0;
18 height: 0px;
19 line-height: 0px;
20 padding: 0px;
21 margin-bottom: 0px;
22 }
23}

A continuación, agregaríamos esta animación al CSS de nuestra publicación. Observa que inicialmente establecemosanimation-play-stateenpaused, lo que significa que la publicación no estará oculta por defecto.

css
1.post {
2 background-color: #77dd11;
3 padding: 20px;
4 margin-bottom: 10px;
5 animation-name: hide;
6 animation-duration: 2s;
7 animation-fill-mode: forwards;
8 animation-play-state: paused;
9}

Finalmente, queremos poder iniciar la animación una vez que se ha hecho clic en el botón de ocultar y luego eliminar la publicación. Podemos hacer esto editando nuestro JavaScript de arriba:

javascript
1// Si el boton escucha el click, borrar el post
2document.addEventListener('click', event => {
3
4 // Encontar que fue clickeado
5 const element = event.target;
6
7 // Chequea si el usuario hizo click en el botón hide
8 if (element.className === 'hide') {
9 element.parentElement.style.animationPlayState = 'running';
10 element.parentElement.addEventListener('animationend', () => {
11 element.parentElement.remove();
12 });
13 }
14
15});

Estos son los códigos javascript que usé para las cartas y los posts de ejemplo:

javascript
1// Vamos a dar formato a la fecha
2var date = new Date();
3const formatedDate = date.toLocaleDateString('es-ES', {
4 year: 'numeric',
5 month: 'long',
6 day: 'numeric',
7 hour: '2-digit',
8 minute: '2-digit',
9});
10// Una vez que el DOM está cargado inserta la fecha en todos los posts
11document.addEventListener('DOMContentLoaded', () => {
12 // Una manera fácil de crear un selector
13 const $ = (selector) => document.querySelectorAll(selector);
14 const posted = $('.posted');
15 posted.forEach((p) => {
16 p.textContent = formatedDate
17 })
18});

Para hacer esto sin django y python, que sería menos eficiente poque los posts se crean de manera dinámica con la función de python, pero también podrian tener un HTML plano de la siguiente manera:

html
1<div class="posts" id="posts">
2<p class="posted"></p>
3<p>Post #1</p>
4<p>Welcome!</p>
5<button class="hide">Hide</button>
6</div>
7
8<div class="posts">
9<p class="posted"></p>
10<p>Post #2</p>
11<p>Bienvenidos!</p>
12<button class="hide">Hide</button>
13</div>
14
15<div class="posts">
16<p class="posted"></p>
17<p>Post #3</p>
18<p>Welkome!</p>
19<button class="hide">Hide</button>
20</div>

Como pudimos ver en el video de demostración de los post ahora la función de esconderhidese ve más atractiva. Aca les dejo el CSS:

css
1.posts {
2 position: relative;
3 width: 50%;
4 border: 1px solid #999;
5 padding: 10px;
6 border-radius: 15px;
7 box-shadow: 1px 2px 3px #999;
8 margin-block: 10px;
9 margin-inline: auto;
10 background-color: lawngreen;
11 animation-name: hide;
12 animation-duration: 2s;
13 animation-fill-mode: forwards;
14 animation-play-state: paused;
15}
16.posts p {
17 margin-inline: 10px;
18}
19button {
20 position: absolute;
21 top: 40%;
22 right: 10px;
23 padding: 4px 8px;
24 border-radius: 5px;
25 border: none;
26 border: 1px solid #999;
27}
28button:hover {
29 transform: scale(1.1);
30 transition: 0.2s all;
31}
32.posted {
33 color: black;
34 font-weight: 300;
35}
36@keyframes hide {
37 0% {
38 opacity: 1;
39 height: 100%;
40 line-height: 100%;
41 padding: 20px;
42 margin-bottom: 10px;
43 }
44 75% {
45 opacity: 0;
46 height: 100%;
47 line-height: 100%;
48 padding: 20px;
49 margin-bottom: 10px;
50 }
51 100% {
52 opacity: 0;
53 height: 0px;
54 line-height: 0px;
55 padding: 0px;
56 margin-bottom: 0px;
57 }
58}

React

React es una biblioteca de JavaScript que permite construir interfaces de usuario de manera declarativa y eficiente. A diferencia de los enfoques imperativos tradicionales, en React describes cómo debería ser la interfaz de usuario y React se encarga de actualizar y renderizar eficientemente los componentes en respuesta a los cambios de estado.

Hasta este punto, puedes imaginarte cuánto código JavaScript se necesitaría para un sitio web más complicado. Puedes mitigar la cantidad de código que realmente necesitas escribir utilizando un marco de JavaScript, al igual que usamos Bootstrap como un marco de CSS para reducir la cantidad de CSS que realmente teníamos que escribir. Uno de los marcos de JavaScript más populares es una biblioteca llamadaReact.

Hasta ahora, en este curso, hemos estado utilizando métodos de programación imperativa, donde le damos a la computadora un conjunto de declaraciones para ejecutar. Por ejemplo, para actualizar el contador en una página HTML, podríamos tener código que se ve así:

Vista

jsx
1<h1>0</h1>

Lógica

jsx
1let num = parseInt(document.querySelector("h1").innerHTML);
2num += 1;
3document.querySelector("h1").innerHTML = num;

React nos permite utilizar la programación declarativa, lo que nos permite simplemente escribir código que explica qué deseamos mostrar y no preocuparnos por cómo lo estamos mostrando. En React, un contador podría lucir algo así:

Vista:

jsx
1<h1>{num}</h1>

Lógica

jsx
1num += 1

El framework React se basa en la idea de componentes, cada uno de los cuales puede tener un estado subyacente. Un componente podría ser algo que se puede ver en una página web, como una publicación o una barra de navegación, y un estado es un conjunto de variables asociadas con ese componente. La belleza de React radica en que cuando el estado cambia, React cambiará automáticamente el DOM en consecuencia.

Hay varias formas de usar React (incluido el popular comando create-react-app publicado por Facebook), pero hoy nos centraremos en comenzar directamente en un archivo HTML. Para hacer esto, tendremos que importar tres paquetes de JavaScript:

  • React: Define componentes y su comportamiento.
  • ReactDOM: Toma componentes de React e los inserta en el DOM.
  • Babel: Traduce desde JSX, el lenguaje en el que escribiremos en React, a JavaScript plano que nuestros navegadores pueden interpretar. JSX es muy similar a JavaScript, pero con algunas características adicionales, incluida la capacidad de representar HTML dentro de nuestro código.

¡Vamos a sumergirnos y crear nuestra primera aplicación React!

Esta es una manera de crearla, hay otras las cuales les voy a enseñar más adelante..

jsx
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
5 <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
6 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
7 <title>Hola</title>
8 </head>
9 <body>
10 <div id="app"></div>
11
12 <script type="text/babel">
13 function App() {
14 return <h1>Bienvenidos!</h1>
15 }
16
17 ReactDOM.render(<App />, document.querySelector("#app"));
18 </script>
19 </body>
20</html>

Importante

En React, la capacidad de crear componentes y reutilizarlos en otros componentes es una parte fundamental del diseño y la organización del código. Esto sigue el principio de la programación modular, donde puedes dividir tu aplicación en piezas más pequeñas y reutilizables, lo que facilita el mantenimiento y la comprensión del código.

Aquí hay algunos conceptos clave relacionados con la creación y el uso de componentes en React:

  1. Componentes Funcionales y de Clase:
    • Funcionales: Son funciones de JavaScript y se escriben como funciones puras. No tienen estado propio ni métodos de ciclo de vida.
    • De Clase: Son clases de JavaScript que extienden la clase React.Component. Pueden tener estado y métodos de ciclo de vida.
  2. Props (Propiedades):

    Los componentes pueden recibir datos externos llamados "props" (propiedades). Estos son como parámetros de función que puedes pasar a un componente cuando lo utilizas.

  3. Composición de Componentes:

    Puedes construir interfaces de usuario complejas combinando y anidando componentes más pequeños. Esto fomenta la reutilización del código y facilita el mantenimiento.

  4. Estado del Componente:

    Los componentes de clase pueden tener un estado interno que afecta su representación y comportamiento. El estado es mutable y se puede actualizar.

  5. Ciclo de Vida del Componente:

    Los componentes de clase tienen métodos de ciclo de vida, como componentDidMount y componentDidUpdate, que te permiten realizar acciones en diferentes puntos durante la vida útil del componente.

  6. HOC (Higher-Order Components):

    Son funciones que toman un componente y devuelven un nuevo componente con funcionalidades adicionales. Esto se utiliza para reutilizar lógica entre componentes.

  7. Hooks:

    Los hooks son funciones especiales que te permiten usar el estado y otros recursos de React en componentes funcionales. El hook useState permite agregar estado a los componentes funcionales.

  8. Contexto:

    El contexto de React permite pasar datos a través del árbol de componentes sin tener que pasar props manualmente en cada nivel.

La creación y reutilización de componentes en React fomenta un desarrollo más limpio, modular y fácil de mantener. Puedes construir componentes especializados para tareas específicas y combinarlos para construir interfaces complejas de manera estructurada. Esto mejora la legibilidad del código y facilita la colaboración en equipos de desarrollo.

Dado que esta es nuestra primera aplicación React, echemos un vistazo detallado a lo que está haciendo cada parte del código:

  1. En las tres líneas anteriores al título, importamos las versiones más recientes de React, ReactDOM y Babel.
  2. En el cuerpo, incluimos un solo div con un id de app. Casi siempre queremos dejar esto vacío y completarlo en nuestro código de React a continuación.
  3. Incluimos una etiqueta de script donde especificamos que type="text/babel". Esto indica al navegador que el script siguiente debe traducirse utilizando Babe
  4. A continuación, creamos un componente llamado App. Los componentes en React pueden representarse mediante funciones de JavaScript.
  5. Nuestro componente devuelve lo que nos gustaría renderizar en el DOM. En este caso, simplemente devolvemos<div>Hello!</div>.
  6. La última línea de nuestro script utiliza la función ReactDOM.render, que toma dos argumentos:
    • Un componente para renderizar.
    • Un elemento en el DOM dentro del cual se debe renderizar el componente.

Ahora que entendemos qué hace el código, podemos echar un vistazo a la página web resultante:

Imagen demo de react, url de la imagen: /images/react0.png

Una característica útil de React es la capacidad de renderizar componentes dentro de otros componentes. Para demostrar esto, creemos otro componente llamado Hola:

jsx
1function Hola(props) {
2 return (
3 <h1>Bienvenidos a React</h1>
4 <p>¡Hola!</p>
5 );
6}

Y ahora, vamos a renderizar tres componentes Hello dentro de nuestro componente App:

jsx
1function App() {
2 return (
3 <div>
4 <Hola />
5 <Hola />
6 <Hola />
7 </div>
8 );
9}

Esto nos va a dar algo parecido a esto:

React demo, url de la imagen: /images/react1.png

Hasta ahora, los componentes no han sido muy interesantes, ya que son todos exactamente iguales. Podemos hacer que estos componentes sean más flexibles agregándoles propiedades adicionales (props en términos de React). Por ejemplo, digamos que queremos saludar a tres personas diferentes. Podemos proporcionar los nombres de esas personas de una manera similar a los atributos HTML:

jsx
1function App() {
2 return (
3 <div>
4 <Hola name="Neo" />
5 <Hola name="Mario" />
6 <Hola name="Gabriel" />
7 </div>
8 );
9}

Podemos acceder a esas props usando props.NOMBRE_PROP. Luego, podemos insertar esto en nuestro JSX usando llaves:

jsx
1function Hola(props) {
2 return (
3 <h1>Hola, {props.name}!</h1>
4 );
5}

Ahora nuestra página mostrará lo siguiente:

React demo, url de la imagen: /images/react2.png

Ahora, veamos cómo podemos usar React para volver a implementar la página del contador que construimos al trabajar por primera vez con JavaScript. Nuestra estructura general seguirá siendo la misma, pero dentro de nuestro componente App, usaremos el hook useState de React para agregar estado a nuestro componente. El argumento de useState es el valor inicial del estado, que estableceremos en 0. La función devuelve tanto una variable que representa el estado como una función que nos permite actualizar el estado.

jsx
1const [count, setCount] = React.useState(0);

Ahora, podemos trabajar en lo que la función renderizará, donde especificaremos un encabezado y un botón. También agregaremos un event listener para cuando se haga clic en el botón, lo cual React maneja utilizando el atributo onClick:

jsx
1return (
2 <div>
3 <h1>{count}</h1>
4 <button onClick={updateCount}>Count</button>
5 </div>
6);

Finalmente, definamos la función updateCount. Para hacer esto, utilizaremos la función setCount, que puede tomar como argumento un nuevo valor para el estado.

jsx
1function updateCount() {
2 setCount(count + 1);
3}

Ahora tenemos una página con función de conteo:

En esta web tengo varios ejemplos del uso de React con Nextjs para hacer diferentes solicitudes a API's, con API Routes o hacer un fetch usando Reactjs y AJAX con un server Flask y Python para hacer web scraping, incluido ahí mismo hay un ejemplo como hacer un fetch de tuREADME.md(GitHub MarkDown) a tu aplicación web. Si quieren ver algo más avanzado del uso de React, aquí les dejo algunos ejemplos:

  1. Web Scraping con React Js y Python
  2. Base de datos con Google Sheets (Hojas de Cálculos de Excel)
  3. Api Routes con javascript (Cambio de Divisas)

Adición

Ahora que tenemos una comprensión del marco de trabajo React, trabajemos en utilizar lo aprendido para construir un sitio similar a un juego, donde los usuarios resolverán problemas de suma. Comenzaremos creando un nuevo archivo con la misma configuración que nuestras otras páginas de React. Para comenzar a construir esta aplicación, pensemos en lo que podríamos querer llevar un seguimiento en el estado. Deberíamos incluir cualquier cosa que pensemos que podría cambiar mientras un usuario esté en nuestra página. Nuestro estado podría incluir:

  • num1: El primer número a sumar.
  • num2: El segundo número a sumar.
  • response: Lo que el usuario ha escrito.
  • score: Cuántas preguntas ha respondido correctamente el usuario.

Ahora nuestro estado puede ser un objeto de Javascript que incluya toda nuestra informacíón:

jsx
1const [state, setState] = React.useState({
2 num1: 1,
3 num2: 1,
4 response: "",
5 score: 0
6});

Ahora usando los valores del estado podemos renderizar una interfaz básica.

jsx
1return (
2 <div>
3 <div>{state.num1} + {state.num2}</div>
4 <input value={state.response} />
5 <div>Score: {state.score}</div>
6 </div>
7);

Ahora la interfaz sencilla de React se verá así:

Suma con React, url de la imagen: /images/ui-1.png

En este momento, el usuario no puede escribir nada en el cuadro de entrada porque su valor está fijo comostate.response, que actualmente es una cadena vacía. Para solucionar esto, agreguemos un atributoonChangeal elemento de entrada y asignémoslo a una función llamadaupdateResponse.

jsx
1onChange={updateResponse}

Ahora, tendremos que definir la función `updateResponse`, que toma el evento que desencadenó la función y establece la respuesta en el valor actual del input. Esta función permite al usuario escribir y almacena lo que se ha escrito en el estado.

jsx
1function updateResponse(event) {
2 setState({
3 ...state,
4 response: event.target.value
5 });
6 }

Para dar algunos toques finales a la aplicación, agreguemos estilo a la página. Centraremos todo en la aplicación y luego haremos que el problema sea más grande agregando un id de problem al div que contiene el problema. Luego, añadiremos el siguiente CSS a una etiqueta de estilo:

css
1#app {
2 text-align: center;
3 font-family: sans-serif;
4 font-size: 53px;
5}
6
7#problem {
8 font-size: 72px;
9}

Finalmente, vamos a realizar un juego de matemáticas en el cual quien obtenga un puntaje de 10 puntos, gana. Aquí les dejo el código que usé para hacer este mini juego, pueden probar editando las funciones y/o agregar estilos:

javascript
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <script
5 src="https://unpkg.com/react@17/umd/react.production.min.js"
6 crossorigin
7 ></script>
8 <script
9 src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"
10 crossorigin
11 ></script>
12 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
13 <link rel="stylesheet" href="/style.css" />
14 <title>Suma en React</title>
15 </head>
16 <body>
17 <div id="app"></div>
18
19 <script type="text/babel">
20 function App() {
21 const [gameState, setGameState] = React.useState({
22 num1: generateRandomNumber(),
23 num2: generateRandomNumber(),
24 response: '',
25 score: 0,
26 });
27
28 function generateRandomNumber() {
29 return Math.floor(Math.random() * 10) + 1;
30 }
31
32 const handleResponseChange = (event) => {
33 setGameState({
34 ...gameState,
35 response: event.target.value,
36 });
37 };
38
39 const handleSubmit = (event) => {
40 event.preventDefault();
41
42 const userAnswer = parseInt(gameState.response, 10);
43 const correctAnswer = gameState.num1 + gameState.num2;
44
45 if (userAnswer === correctAnswer) {
46 const newScore = gameState.score + 1;
47
48 if (newScore === 10) {
49 alert(
50 `¡Felicidades! Has ganado el juego. Obtuviste ${gameState.score} puntos.`,
51 );
52 setGameState({
53 num1: generateRandomNumber(),
54 num2: generateRandomNumber(),
55 response: '',
56 score: 0,
57 });
58 } else {
59 setGameState({
60 num1: generateRandomNumber(),
61 num2: generateRandomNumber(),
62 response: '',
63 score: newScore,
64 });
65 }
66 } else {
67 setGameState({
68 ...gameState,
69 response: '',
70 });
71 }
72 };
73
74 const styleButton = {
75 border: '1px solid #999',
76 borderRadius: '4px',
77 padding: '4px 8px',
78 marginInline: '10px',
79 backgroundColor: 'transparent',
80 cursor: 'pointer',
81 transition: '.2s',
82 };
83
84 return (
85 <div>
86 <h3>Juego de Matemáticas</h3>
87 <p>Puntuación: {gameState.score}</p>
88 <div id="problem" style={{ fontSize: '24px', margin: '20px 0' }}>
89 {gameState.num1} + {gameState.num2} =
90 </div>
91 <form onSubmit={handleSubmit}>
92 <input
93 type="number"
94 value={gameState.response}
95 onChange={handleResponseChange}
96 />
97 <button type="submit" style={styleButton}>
98 Enviar
99 </button>
100 </form>
101 </div>
102 );
103 }
104
105 ReactDOM.render(<App />, document.querySelector('#app'));
106 </script>
107 </body>
108</html>

Ahora echemos un vistazo a nuestra aplicación:

Muchas gracias por llegar hasta aquí, pueden ir viendo otros ejemplos de desarrollo de aplicaciones con React más complejas.

Aquí les dejo otra práctica para aprender a hacer un fetch de tu archivo MarkDown en React con Axios:

No olviden dejar sus comentarios, si ven errores o tienen alguna consulta para hacer, los escucho en mifeedback.

Compartir

Todos los nombres de productos, logos y marcas son propiedad de sus respectivos creadores.