a
Renderizando una colección, módulos
Antes de comenzar una nueva parte, recapitulemos algunos de los temas que resultaron difíciles el año pasado.
console.log
¿Cuál es la diferencia entre un programador de JavaScript experimentado y un novato? El experimentado usa console.log de 10 a 100 veces más.
Paradójicamente, esto parece ser cierto aunque un programador novato necesitaría console.log (o cualquier método de depuración) más que uno experimentado.
Cuando algo no funciona, no adivines qué está mal. En su lugar, usa la consola o utiliza alguna otra forma de depuración.
NB Como se explicó en la parte 1, cuando uses el comando console.log para depurar, no concatenes cosas 'al estilo Java' con un signo de sumar. En lugar de escribir:
console.log('props value is' + props)
Separa las cosas que se van a imprimir con una coma:
console.log('props value is', props)
Si concatenas un objeto con una cadena y lo registras en la consola (como en nuestro primer ejemplo), el resultado será bastante inútil:
props value is [Object object]
Por el contrario, cuando pasas objetos como argumentos distintos separados por comas a console.log, como en nuestro segundo ejemplo anterior, el contenido del objeto se imprime en la consola del desarrollador como cadenas que nos aportan información util. Si es necesario, lee más sobre la depuración de aplicaciones React aquí.
Protip: fragmentos de código de Visual Studio
Con Visual Studio Code es fácil crear 'snippets', es decir, accesos directos para generar rápidamente porciones de código que se reutilizan habitualmente, muy parecido a cómo funciona 'sout' en Netbeans.
Las instrucciones para crear snippets se pueden encontrar aquí.
También se pueden encontrar snippets útiles y listos para usar como complementos de VS Code, por ejemplo, aquí.
El snippet más importante es el del comando console.log(), por ejemplo clog. Esto se puede crear así:
{
"console.log": {
"prefix": "clog",
"body": [
"console.log('$1')",
],
"description": "Log output to console"
}
}
Depurar tu código usando console.log() es tan común que Visual Studio Code tiene ese fragmento integrado. Para usarlo, escribe log y presiona tabulador para autocompletar. Extensiones de snippets de console.log() más completos pueden encontrarse en el marketplace.
Matrices JavaScript
De aquí en adelante, usaremos los métodos de programación funcional de JavaScript array -como find , filter y map - todo el tiempo.
Si la programación funcional con matrices te parece ajena, vale la pena ver al menos las tres primeras partes de la serie de videos de YouTube Programación funcional en JavaScript:
Controladores de eventos revisados
Según el curso del año pasado, el manejo de eventos ha demostrado ser difícil.
Vale la pena leer el capítulo de revisión al final de la parte anterior revisión de los controladores de eventos, si cree que su propio conocimiento sobre el tema necesita algo de mejora.
Pasar controladores de eventos a los componentes secundarios del componente App ha planteado algunas preguntas. Se puede encontrar una pequeña revisión sobre el tema aquí.
Renderizando colecciones
Ahora haremos el 'frontend', o la lógica de la aplicación del lado del navegador, en React para una aplicación que es similar a la aplicación de ejemplo de la parte 0
Comencemos con lo siguiente (en el archivo App.jsx):
const App = (props) => {
const { notes } = props
return (
<div>
<h1>Notes</h1>
<ul>
<li>{notes[0].content}</li>
<li>{notes[1].content}</li>
<li>{notes[2].content}</li>
</ul>
</div>
)
}
export default App
El archivo main.jsx se ve de esta forma:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
const notes = [
{
id: 1,
content: 'HTML is easy',
important: true
},
{
id: 2,
content: 'Browser can execute only JavaScript',
important: false
},
{
id: 3,
content: 'GET and POST are the most important methods of HTTP protocol',
important: true
}
]
ReactDOM.createRoot(document.getElementById('root')).render(
<App notes={notes} />
)
Cada nota contiene su contenido textual, y un valor booleano para marcar si la nota ha sido categorizada como importante o no, y también un id único.
El ejemplo anterior funciona debido al hecho de que hay exactamente tres notas en la matriz.
Se renderiza una sola nota al acceder a los objetos de la matriz haciendo referencia a un número de índice codificado:
<li>{notes[1].content}</li>
Esto, por supuesto, no es práctico. Podemos mejorar esto generando elementos React a partir de los objetos de la matriz usando la función map.
notes.map(note => <li>{note.content}</li>)
El resultado es una matriz de elementos li.
[
<li>HTML is easy</li>,
<li>Browser can execute only JavaScript</li>,
<li>GET and POST are the most important methods of HTTP protocol</li>,
]
Que luego se puede colocar dentro de las etiquetas ul:
const App = (props) => {
const { notes } = props
return (
<div>
<h1>Notes</h1>
<ul> {notes.map(note => <li>{note.content}</li>)} </ul> </div>
)
}
Debido a que el código que genera las etiquetas li es JavaScript, debe incluirse entre llaves en una plantilla JSX.
También haremos que el código sea más legible separando la declaración de la función de flecha en varias líneas:
const App = (props) => {
const { notes } = props
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<li> {note.content} </li> )}
</ul>
</div>
)
}
Atributo key
Aunque la aplicación parece estar funcionando, hay una advertencia desagradable en la consola
Como la página vinculada en el mensaje de error instruye, los elementos de la lista, es decir, los elementos generados por el método map, deben tener cada uno un valor de clave única: un atributo llamado key.
Agreguemos las keys (claves):
const App = (props) => {
const { notes } = props
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<li key={note.id}> {note.content}
</li>
)}
</ul>
</div>
)
}
Y el mensaje de error desaparece.
React utiliza los atributos key de los objetos en una matriz para determinar cómo actualizar la vista generada por un componente cuando el componente se vuelve a renderizar. Más sobre esto aquí.
Map
Comprender cómo funciona el método de matriz map es crucial para el resto del curso.
La aplicación contiene una matriz llamada notes:
const notes = [
{
id: 1,
content: 'HTML is easy',
important: true
},
{
id: 2,
content: 'Browser can execute only JavaScript',
important: false
},
{
id: 3,
content: 'GET and POST are the most important methods of HTTP protocol',
important: true
}
]
Hagamos una pausa por un momento y examinemos cómo funciona map.
Si se agrega el siguiente código, digamos, al final del archivo:
const result = notes.map(note => note.id)
console.log(result)
[1, 2, 3] se imprimirá en la consola. map siempre crea una nueva matriz, cuyos elementos se han creado a partir de los elementos de la matriz original mediante mapeo: utilizando la función dada como parámetro al método map.
La función es
note => note.id
Que es una función de flecha escrita en forma compacta. La forma completa sería:
note => {
return note.id
}
La función obtiene un objeto de nota como parámetro, y devuelve el valor de su campo id.
Cambiar el comando a:
const result = notes.map(note => note.content)
resulta en una matriz que contiene el contenido de las notas.
Esto ya está bastante cerca del código de React que usamos:
notes.map(note =>
<li key={note.id}>
{note.content}
</li>
)
Que genera una etiqueta li que contiene el contenido de la nota de cada objeto de nota.
Porque el parámetro de función pasado al método map -
note => <li key={note.id}>{note.content}</li>
- se utiliza para crear elementos de vista, el valor de la variable debe representarse dentro de llaves. Intenta ver qué sucede si se quitan las llaves.
El uso de llaves te causará dolor de cabeza al principio, pero pronto te acostumbrarás. La respuesta visual de React es inmediata.
Anti-patrón: índices de matriz como claves
Podríamos haber hecho desaparecer el mensaje de error en nuestra consola usando los índices de matriz como claves. Los índices se pueden recuperar pasando un segundo parámetro a la función de devolución de llamada del método map:
notes.map((note, i) => ...)
Cuando se llama así, a i se le asigna el valor del índice de la posición en la matriz donde reside la nota.
Como tal, una forma de definir la generación de filas sin obtener errores es:
<ul>
{notes.map((note, i) =>
<li key={i}>
{note.content}
</li>
)}
</ul>
Sin embargo, no se recomienda y puede causar problemas no deseados incluso si parece estar funcionando bien.
Lee más sobre esto en este articulo.
Refactorizando módulos
Ordenemos un poco el código. Solo estamos interesados en el campo notes de los props, así que recuperemos eso directamente usando desestructuración:
const App = ({ notes }) => { return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<li key={note.id}>
{note.content}
</li>
)}
</ul>
</div>
)
}
Si has olvidado lo que significa la desestructuración y cómo funciona, revisa la sección de desestructuración.
Separamos la visualización de una sola nota en su propio componente Note:
const Note = ({ note }) => { return ( <li>{note.content}</li> )}
const App = ({ notes }) => {
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note => <Note key={note.id} note={note} /> )} </ul>
</div>
)
}
Ten en cuenta que el atributo key ahora debe definirse para los componentes Note, y no para las etiquetas li como antes.
Se puede escribir una aplicación React completa en un solo archivo. Aunque eso, por supuesto, no es muy práctico. La práctica común es declarar cada componente en su propio archivo como un módulo ES6.
Hemos estado usando módulos todo el tiempo. Las primeras líneas del archivo main.jsx:
import ReactDOM from "react-dom/client"
import App from "./App"
importan dos módulos, lo que les permite ser utilizados en ese archivo. El módulo react-dom/client se coloca en una variable llamada React-DOM, y el módulo que define el componente principal de la aplicación se coloca en la variable App.
Movamos nuestro componente Note a su propio módulo.
En aplicaciones más pequeñas, los componentes generalmente se colocan en un directorio llamado components, que a su vez se ubica dentro del directorio src. La convención es nombrar el archivo después del componente.
Ahora crearemos un directorio llamado components para nuestra aplicación y colocaremos un archivo llamado Note.jsx dentro. El contenido del archivo es el siguiente:
const Note = ({ note }) => {
return (
<li>{note.content}</li>
)
}
export default Note
La última línea del módulo exporta el módulo declarado, la variable Note.
Ahora el archivo que está usando el componente - App.jsx - puede importar el módulo:
import Note from './components/Note'
const App = ({ notes }) => {
// ...
}
El componente exportado por el módulo ahora está disponible para su uso en la variable Note, al igual que antes.
Ten en cuenta que al importar nuestros propios componentes, se debe dar su ubicación en relación con el archivo de importación:
'./components/Note'
El punto - . - al principio se refiere al directorio actual, por lo que la ubicación del módulo es un archivo llamado Note.jsx en el subdirectorio components del directorio actual. La extensión del nombre de archivo .jsx se puede omitir.
Los módulos tienen muchos otros usos ademas de permitir separar las declaraciones de componentes en sus archivos propios. Volveremos a este tema más adelante en el curso.
El código actual de la aplicación puede encontrarse en GitHub.
Ten en cuenta que la rama main del repositorio contiene el código de una version posterior de la aplicación. El código actual está en la rama part2-1:
Si clonas el proyecto, ejecuta el comando npm install antes de iniciar la aplicación con npm run dev.
Cuando la aplicación se rompe
Al principio de tu carrera como programador (e incluso después de 30 años codeando como es mi caso), lo que sucede a menudo es que la aplicación simplemente se rompe por completo. Esto es aún más común en el caso de los lenguajes tipados dinámicamente, como JavaScript, donde el compilador no verifica el tipo de datos de, por ejemplo, variables de función o valores de retorno.
Una "explosión de React" puede, por ejemplo, verse así:
En estas situaciones, la mejor salida es el método console.log.
El fragmento de código que causa la explosión es este:
const Course = ({ course }) => (
<div>
<Header course={course} />
</div>
)
const App = () => {
const course = {
// ...
}
return (
<div>
<Course course={course} />
</div>
)
}
Investigaremos el motivo de la rotura agregando comandos console.log al código. Como lo primero que se renderiza es el componente App, vale la pena poner el primer console.log allí:
const App = () => {
const course = {
// ...
}
console.log('App works...')
return (
// ..
)
}
Para ver la impresión en la consola, debemos desplazarnos hacia arriba sobre la larga pared roja de errores.
Cuando se descubre que algo funciona, es hora de profundizar más. Si el componente se ha declarado como una sola declaración o una función sin retorno, hace que la impresión en la consola sea más difícil.
const Course = ({ course }) => (
<div>
<Header course={course} />
</div>
)
El componente debe cambiarse a su forma más larga para que agreguemos la impresión:
const Course = ({ course }) => {
console.log(course) return (
<div>
<Header course={course} />
</div>
)
}
Muy a menudo, la raíz del problema es que se espera que los props sean de un tipo diferente, o que se llamen con un nombre diferente al que realmente tienen, y la desestructuración falla como resultado. El problema a menudo comienza a resolverse por sí mismo cuando se elimina la desestructuración y vemos lo que realmente contienen los props.
const Course = (props) => { console.log(props) const { course } = props
return (
<div>
<Header course={course} />
</div>
)
}
Si el problema aún no se ha resuelto, realmente no hay mucho que hacer aparte de continuar la búsqueda de errores esparciendo más declaraciones console.log alrededor de tu código.
Agregué este capítulo al material después de que la respuesta del modelo para la siguiente pregunta explotara por completo (debido a que los props eran del tipo incorrecto), y tuve que depurarlo usando console.log.
Juramento del desarrollador web
Antes de los ejercicios, permíteme recordarte lo que prometiste al final de la parte anterior.
La programación es difícil, por eso utilizaré todos los medios posibles para facilitarla:
- Mantendré abierta la consola de desarrollo de mi navegador todo el tiempo.
- Progresaré con pequeños pasos.
- Escribiré muchas declaraciones de console.log para asegurarme de entender cómo se comporta el código y para ayudar a identificar problemas.
- Si mi código no funciona, no escribiré más código. En cambio, comenzaré a eliminar el código hasta que funcione o simplemente volveré a un estado en el que todo aún funcionaba.
- Cuando pida ayuda en el canal de Discord del curso, o en cualquier otro lugar, formularé mis preguntas adecuadamente; consulta aquí cómo pedir ayuda.