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 adivine qué está mal. En su lugar, use la consola o utilice alguna otra forma de depuración.

NB Como se explicó en la parte 1, cuando use el comando console.log para depurar, no concatene cosas 'al estilo Java' con un plus. 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 pasa 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 son reveladoras. Si es necesario, lea 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.

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. Operan con los mismos principios generales que los streams en Java 8, que se han utilizado durante los últimos años en los cursos 'Ohjelmoinnin perusteet' y 'Ohjelmoinnin jatkokurssi' en el departamento de informática de la universidad, y también en el MOOC de programación.

Si la programación funcional con matrices le 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 ser dificil. 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:

import React from 'react'
import ReactDOM from 'react-dom'

const notes = [
  {
    id: 1,
    content: 'HTML is easy',
    date: '2019-05-30T17:30:31.098Z',
    important: true,
  },
  {
    id: 2,
    content: 'Browser can execute only JavaScript',
    date: '2019-05-30T18:39:34.091Z',
    important: false,
  },
  {
    id: 3,
    content: 'GET and POST are the most important methods of HTTP protocol',
    date: '2019-05-30T19:20:14.298Z',
    important: true,
  },
]

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>
  )
}

ReactDOM.render(<App notes={notes} />, document.getElementById('root'))

Cada nota contiene su contenido textual y una marca de tiempo, así como 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 representa 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 al igual que todos los demás códigos JavaScript.

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

fullstack content

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 una clave única valor: 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',
    date: '2019-05-30T17:30:31.098Z',
    important: true,
  },
  {
    id: 2,
    content: 'Browser can execute only JavaScript',
    date: '2019-05-30T18:39:34.091Z',
    important: false,
  },
  {
    id: 3,
    content: 'GET and POST are the most important methods of HTTP protocol',
    date: '2019-05-30T19:20:14.298Z',
    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)

Da como resultado 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. Trate de 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. Lea más sobre esto aquí.

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 ha olvidado lo que significa la desestructuración y cómo funciona, revise esto.

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>
  )
}

Tenga 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:

import React from 'react'
import ReactDOM from 'react-dom'

importan dos módulos, lo que les permite ser utilizados en ese archivo. El módulo React se coloca en una variable llamada React y React-DOM en la variable ReactDOM.

Muevamos 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.js dentro. El contenido del archivo Note.js es el siguiente:

import React from 'react'

const Note = ({ note }) => {
  return <li>{note.content}</li>
}

export default Note

Importamos React en la primera línea del módulo.

La última línea del módulo exporta el módulo declarado, la variable Note.

Toma en cuenta que en versiones recientes de React ya no es necesario importar React para usar sintaxis JSX, sin embargo sigue siendo importante conocer su uso, ya que hay miles de millones de líneas de código antiguo de React que aún necesitan importar React. Lo mismo se aplica a la documentación y los ejemplos de React con los que puede tropezar en Internet.

Si necesitamos importar React para usar Hooks y otras funciones exportadas que React provee. Lea más sobre esto aquí.

Ahora el archivo que está usando el componente - index.js - puede importar el módulo:

import React from 'react'
import ReactDOM from 'react-dom'
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.

Tenga 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.js en el subdirectorio components del directorio actual. La extensión del nombre de archivo - .js - se puede omitir.

App también es un componente, así que vamos a declararlo también en su propio módulo. Dado que es el componente raíz de la aplicación, lo colocaremos en el directorio src. El contenido del archivo es el siguiente:

import React from 'react'
import Note from './components/Note'

const App = ({ notes }) => {
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => (
          <Note key={note.id} note={note} />
        ))}
      </ul>
    </div>
  )
}

export default App

Lo que queda en el archivo index.js es:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const notes = [
  // ...
]

ReactDOM.render(<App notes={notes} />, document.getElementById('root'))

Los módulos tienen muchos otros usos además de permitir que las declaraciones de componentes se separen en sus propios archivos. Nos pondremos en contacto con ellos más adelante en este curso.

El código actual de la aplicación se puede encontrar en GitHub.

Tenga en cuenta que la rama master del repositorio contiene el código para una versión posterior de la aplicación. El código actual está en la rama part2-1:

fullstack content

Si clona el proyecto, ejecute el comando npm install antes de iniciar la aplicación con npm start.

Cuando la aplicación se rompe

Al principio de tu carrera como programador (e incluso después de 30 años de codificación como la tuya), lo que sucede a menudo es que la aplicación simplemente se descompone por completo. Este es aún más 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í:

fullstack content

En estas situaciones, la mejor salida es 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>
  )
}

Nos centraremos en el motivo del desglose 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.

fullstack content

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 son, 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 su 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.