Aller au contenu

a

Rendu de collections, modules

Avant de commencer une nouvelle partie, récapitulons quelques-uns des sujets qui se sont avérés difficiles l'année dernière.

console.log

Quelle est la différence entre un programmeur JavaScript expérimenté et un débutant ? L'expérimenté utilise console.log 10 à 100 fois plus.

Paradoxalement, cela semble être vrai même si un programmeur débutant aurait besoin de console.log (ou de toute méthode de débogage) plus qu'un programmeur expérimenté.

Lorsque quelque chose ne fonctionne pas, ne vous contentez pas de deviner ce qui ne va pas. Au lieu de cela, logger ou utilisez un autre moyen de débogage.

NB Comme expliqué dans la partie 1, lorsque vous utilisez la commande console.log pour le débogage, ne concaténez pas les choses "à la manière Java" avec un plus. Au lieu d'écrire :

console.log('props value is ' + props)

séparez les éléments à afficher par une virgule :

console.log('props value is', props)

Si vous concaténez un objet avec une chaîne et que vous le loggez dans la console (comme dans notre premier exemple), le résultat sera plutôt inutile :

props value is [Object object]

Au contraire, lorsque vous transmettez des objets en tant qu'arguments distincts séparés par des virgules à console.log, comme dans notre deuxième exemple ci-dessus, le contenu de l'objet est affiché sur la console du développeur sous forme de chaînes perspicaces. Si nécessaire, en savoir plus sur le débogage des applications React.

Conseil de pro : snippets de code Visual Studio

Avec Visual Studio Code, il est facile de créer des "snippets", c'est-à-dire des raccourcis pour générer rapidement des portions de code couramment réutilisées, un peu comme le fonctionnement de "sout" dans Netbeans.

Les instructions pour créer des snippets sont disponibles ici.

Des extraits de code utiles et prêts à l'emploi peuvent également être trouvés sous forme de plugins VS Code, sur le marketplace.

Le snippet le plus important est celui de la commande console.log(), par exemple, clog. Cela peut être créé comme ceci:

{
  "console.log": {
    "prefix": "clog",
    "body": [
      "console.log('$1')",
    ],
    "description": "Log output to console"
  }
}

Le débogage du code à l'aide de console.log() est si courant que Visual Studio Code intègre ce snippet. Pour l'utiliser, tapez log et appuyez sur l'onglet pour compléter automatiquement. Des extensions d'extraits de code console.log() plus complètes sont disponibles sur le marketplace.

Tableaux JavaScript

À partir de maintenant, nous utiliserons les méthodes de programmation fonctionnelle des tableaux JavaScript , comme find , filter et map - tout le temps. Ils fonctionnent sur les mêmes principes généraux que les flux de Java 8, qui ont été utilisés ces dernières années dans les cours 'Ohjelmoinnin perusteet' et 'Ohjelmoinnin jatkokurssi' du département d'informatique de l'université, ainsi que dans le MOOC de programmation.

Si la programmation fonctionnelle avec des tableaux vous semble étrangère, cela vaut la peine de regarder au moins les trois premières parties de la série de vidéos YouTube Programmation fonctionnelle en JavaScript :

Gestionnaires d'événements revisités

Sur la base du cours de l'année dernière, la gestion des événements s'est avérée difficile.

Cela vaut la peine de lire le chapitre de révision à la fin de la partie précédente gestion d'événements revisitée, si vous pensez que vos propres connaissances sur le sujet ont besoin d'être affinées. Passer des gestionnaires d'événements aux composants enfants du composant App a soulevé quelques questions. Une petite révision sur le sujet peut être trouvée ici.

Rendu de Collections

Nous allons maintenant faire le "frontend", ou la logique d'application côté navigateur, dans React pour une application similaire à l'exemple d'application de la partie 0

Commençons par ce qui suit (le fichier App.js) :

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

Le fichier index.js ressemble à :

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

import App from './App'

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

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

Chaque note contient son contenu textuel et un horodatage, ainsi qu'une valeur booléenne pour marquer si la note a été classée comme importante ou non, ainsi qu'un id unique.

L'exemple ci-dessus fonctionne car il y a exactement trois notes dans le tableau.

Une seule note est rendue en accédant aux objets du tableau en se référant à un numéro d'index codé en dur :

<li>{notes[1].content}</li>

Ce n'est bien sûr pas pratique. Nous pouvons améliorer cela en générant des éléments React à partir des objets du tableau à l'aide de la fonction map.

notes.map(note => <li>{note.content}</li>)

Le résultat est un tableau d'éléments 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>,
]

Qui peut ensuite être placé à l'intérieur de balises ul :

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>        {notes.map(note => <li>{note.content}</li>)}      </ul>    </div>
  )
}

Étant donné que le code générant les balises li est JavaScript, il doit être entouré d'accolades dans un modèle JSX, comme tout autre code JavaScript.

Nous rendrons également le code plus lisible en séparant la déclaration de la fonction fléchée sur plusieurs lignes :

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li>            {note.content}          </li>        )}
      </ul>
    </div>
  )
}

Attribut clé

Même si l'application semble fonctionner, il y a un méchant avertissement dans la console :

fullstack content

Comme le suggère la page React dans le message d'erreur; les éléments de la liste, c'est-à-dire les éléments générés par la méthode map, doivent chacun avoir une valeur clé unique : un attribut appelé clé.

Ajoutons les clés :

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li key={note.id}>            {note.content}
          </li>
        )}
      </ul>
    </div>
  )
}

Et le message d'erreur disparaît.

React utilise les attributs clés des objets dans un tableau pour déterminer comment mettre à jour la vue générée par un composant lorsque le composant est rendu à nouveau. Plus d'informations à ce sujet dans la documentation React.

Map

Comprendre comment la méthode map fonctionne est crucial pour le reste du cours.

L'application contient un tableau appelé 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
  }
]

Arrêtons-nous un instant et examinons comment fonctionne map.

Si le code suivant est ajouté, disons, à la fin du fichier :

const result = notes.map(note => note.id)
console.log(result)

[1, 2, 3] sera imprimé sur la console. map crée toujours un nouveau tableau dont les éléments ont été créés à partir des éléments du tableau d'origine par mappage : en utilisant la fonction donnée en paramètre à la méthode map.

La fonction est

note => note.id

Qui est une fonction fléchée écrite sous forme compacte. La forme complete serait :

(note) => {
  return note.id
}

La fonction a comme paramètre un objet note et renvoie la valeur de son champ id.

Changer la commande en :

const result = notes.map(note => note.content)

donne un tableau contenant le contenu des notes.

C'est déjà assez proche du code React que nous avons utilisé :

notes.map(note =>
  <li key={note.id}>{note.content}</li>
)

qui génère une balise li contenant le contenu de la note de chaque objet note.

Parce que le paramètre de fonction passé à la méthode map -

note => <li key={note.id}>{note.content}</li>

 - est utilisé pour créer des éléments de vue, la valeur de la variable doit être rendue entre accolades. Essayez de voir ce qui se passe si les accolades sont retirées.

L'utilisation d'accolades vous causera quelques douleurs au début, mais vous vous y habituerez assez tôt. Le retour visuel de React est immédiat.

Anti-pattern: index de tableau en tant que clés

Nous aurions pu faire disparaître le message d'erreur sur notre console en utilisant les index du tableau comme clés. Les index peuvent être récupérés en passant un second paramètre à la fonction callback de la map method:

notes.map((note, i) => ...)

Lorsqu'il est appelé ainsi, i reçoit la valeur de l'index de la position dans le tableau où réside la note.

En tant que tel, une façon de définir la génération de lignes sans obtenir d'erreurs est :

<ul>
  {notes.map((note, i) => 
    <li key={i}>
      {note.content}
    </li>
  )}
</ul>

Ceci est cependant, non recommandé et peut créer des problèmes indésirables même s'il semble fonctionner correctement.

En savoir plus à ce sujet dans cet article.

Refactoring de Modules

Changeons un peu le code. Nous ne sommes intéressés que par le champ notes des props, alors récupérons cela directement en utilisant la déstructuration :

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

Si vous avez oublié ce que signifie la déstructuration et comment cela fonctionne, veuillez consulter la section associée.

Nous séparerons l'affichage d'une seule note dans son propre composant 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>
  )
}

Notez que l'attribut key doit maintenant être défini pour les composants Note, et non pour les balises li comme auparavant.

Une application React entière peut être écrite dans un seul fichier. Même si ce n'est bien sûr pas très pratique. La pratique courante consiste à déclarer chaque composant dans son propre fichier en tant que module ES6.

Nous avons utilisé des modules tout le temps. Les premières lignes du fichier index.js :

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

import App from './App'

importent trois modules, nous permettant de les utiliser dans ce fichier. Le module React est placé dans la variable React, le module react-dom dans la variable ReactDOM, et le module qui définit le composant principal de l'application est placé dans la variable Application

Déplaçons notre composant Note dans son propre module.

Dans les petites applications, les composants sont généralement placés dans un répertoire appelé components, qui est à son tour placé dans le répertoire src. La convention est de nommer le fichier d'après le composant.

Maintenant, nous allons créer un répertoire appelé components pour notre application et y placer un fichier nommé Note.js. Le contenu du fichier Note.js est le suivant :

import React from 'react'

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

export default Note

La dernière ligne du module exporte le module déclaré, la variable Note .

Maintenant, le fichier qui utilise le composant - App.js - peut importer le module :

import Note from './components/Note'
const App = ({ notes }) => {
  // ...
}

Le composant exporté par le module est maintenant disponible pour être utilisé dans la variable Note, comme il l'était auparavant.

Notez que lors de l'importation de nos propres composants, leur emplacement doit être indiqué.

'./components/Note'

Le point - . - au début fait référence au répertoire courant, donc l'emplacement du module est un fichier appelé Note.js dans le sous répertoire components du répertoire courant. L'extension du fichier .js peut être omise.

Les modules ont de nombreuses autres utilisations que de permettre aux déclarations de composants d'être séparées dans leurs propres fichiers. Nous y reviendrons plus tard dans ce cours.

Le code actuel de l'application peut être trouvé sur GitHub.

Notez que la branche main du référentiel contient le code d'une version ultérieure de l'application. Le code actuel se trouve sur la branche part2-1 :

fullstack content

Si vous clonez le projet, exécutez la commande npm install avant de démarrer l'application avec npm run dev.

Lorsque l'application crashe

Au début de votre carrière de programmeur (et même après 30 ans de codage comme le vôtre vraiment), ce qui arrive souvent, c'est que l'application tombe complètement en panne. C'est encore plus le cas avec les langages à typage dynamique, comme JavaScript, où le compilateur ne vérifie pas le type de données. Par exemple, des variables de fonction ou des valeurs de retour.

Une "explosion React" peut, par exemple, ressembler à ceci :

fullstack content

Dans ces situations, votre meilleure solution reste la méthode console.log.

Le morceau de code à l'origine de l'explosion est celui-ci :

const Course = ({ course }) => (
  <div>
    <Header course={course} />
  </div>
)

const App = () => {
  const course = {
    // ...
  }

  return (
    <div>
      <Course course={course} />
    </div>
  )
}

Nous allons rechercher la raison de la panne en ajoutant des commandes console.log au code. Étant donné que la première chose à rendre est le composant App, cela vaut la peine d'y mettre le premier console.log :

const App = () => {
  const course = {
    // ...
  }

  console.log('App works...')
  return (
    // ..
  )
}

Pour voir l'impression dans la console, il faut faire défiler vers le haut le long fil rouge des erreurs.

fullstack content

Lorsqu'une chose fonctionne, il faut aller chercher le problème en profondeur. Si le composant a été déclaré en tant qu'instruction unique ou en tant que fonction sans retour, cela rend l'impression sur la console plus difficile.

const Course = ({ course }) => (
  <div>
    <Header course={course} />
  </div>
)

Le composant doit être remplacé par sa forme plus longue afin que nous puissions ajouter le retour sur console:

const Course = ({ course }) => { 
  console.log(course)  return (
    <div>
      <Header course={course} />
    </div>
  )
}

Très souvent, la racine du problème est que les props sont censés être d'un type différent, ou appelés avec un nom différent de ce qu'ils sont réellement, et la déstructuration échoue en conséquences. Le problème commence souvent à se résoudre de lui-même lorsque la déstructuration est supprimée et que nous voyons ce que les props contiennent réellement.

const Course = (props) => {  console.log(props)  const { course } = props
  return (
    <div>
      <Header course={course} />
    </div>
  )
}

Si le problème n'a toujours pas été résolu, il n'y a vraiment pas grand-chose à faire à part continuer à chasser les bogues en saupoudrant plus d'instructions console.log autour de votre code.

J'ai ajouté ce chapitre au cours après que le modèle de réponse à la question suivante ait complètement explosé (car les props étaient du mauvais type), et j'ai dû le déboguer en utilisant console.log.