Le développement d'UI basé sur les composants

Programmation web - Client riche

Objectifs

  • Comprendre le concept de composant
  • Comprendre la relation entre la notion d'état et l'interface utilisateur
  • Comprendre la différence entre code impératif et code déclaratif
  • Appliquer ces notions à une librairie basée sur les composants : React

La notion de composant

App

MessageForm

MessagesList

Message

Message

Un composant d'Interface Utilisateur :

 

  • Décrit un "morceau" d'interface
  • Est composé de 3 parties :
    • Markup (HTML)
    • Style (CSS)
    • Comportement (JS)
  • Doit se concentrer sur une seule responsabilité (équivalent du Single Responsibility Principle en Programmation Orientée Objet)

Deux problèmes :

  • Code impératif
  • Structure (HTML) séparée de la donnée dont elle dépend (JS)

Code impératif vs code déclaratif

<div>
  <p class="text-big">
    Hello world.
    <a href="about.html">Click here.</a>
  </p>
</div>
const div = document.createElement("div")

const p = document.createElement("p")
p.classList.add("text-big")

const a = document.createElement("a")
a.href = "about.html"
a.textContent = "Click here."

p.append("Hello world.", a)

div.append(p)

document.body.append(div)

Déclaratif

Impératif

Décrit le résultat souhaité

Détaille chaque étape pour arriver au résultat souhaité

createElement(
  "div",
  null,
  createElement(
    "p",
    { class: "text-big" },
    "Hello world",
    createElement(
      "a",
      { href: "about.html" },
      "Click here.")
  )
)
const div = document.createElement("div")

const p = document.createElement("p")
p.classList.add("text-big")

const a = document.createElement("a")
a.href = "about.html"
a.textContent = "Click here."

p.append("Hello world.", a)

div.append(p)

document.body.append(div)

Déclaratif

Impératif

// messagesList.js

import * as message from "./message.js"

const messageElement = message.make("Hello world")
// message.js

export const make = (message) => {
  /**
   * Plein de code impératif pour
   * créer des éléments
   */
}

La fonction make contient du code impératif. Lorsqu'on utilise cette fonction, on est déclaratifs : "je veux un élément qui contient tel message"

L'UI en tant que fonction appliquée à une donnée

UI = f(donnée)

donnée = tableau de messages
 

f = fonction qui prend en entrée un tableau de messages et renvoie un ensemble d'élément du DOM décrivant la structure de l'UI pour ce tableau de messages
 

UI = l'interface résultant de ces éléments

✅ L'UI reflète toujours l'état courant de la donnée

 

✅ La représentation de l'UI et la donnée sont au même endroit : f

 

❌ L'ensemble de l'UI doit être recréée à chaque fois que la donnée change

 

➡ Problème de performance, perte de focus, mauvaise accessibilité...

Solution : un algo qui compare l'état courant du DOM avec l'état "suivant" souhaité, et détermine les changements minimaux à appliquer pour faire la transition entre les deux état

React

Librairie JS destinée à construire des UI dynamiques qui :

 

  • est basée sur le concept de composants
  • propose une API déclarative
  • implémente système de DOM virtuel couplé à un algorithme de comparaison de deux DOM virtuels permettant de n'appliquer que les changements minimaux entre deux états de l'UI
npx create-react-app my-app

=> Installe toutes les dépendances et l'outillage préconfiguré pour développer une app avec React

cd my-app
npm run start

=> Lance un serveur de développement local (avec rafraîchissement automatique dans le navigateur)

npm run build

=> Crée un build de production optimisé (ensemble de fichiers HTML / CSS / JS prêt à être mis en ligne)

<!-- index.html -->
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <title>Ma première app avec React</title>
  </head>
  
  <body>
    <div id="app"></div>
    <script src="./index.js"></script>
  </body>
</html>
// index.js

import * as React from "react"
import ReactDOM from "react-dom"

const appElement = document.querySelector("#app")

ReactDOM.render(
  React.createElement("div", null, "Hello world"),
  appElement
)
React.createElement(
  type,
  attributs,
  ...enfants
)
React.createElement("div", null, "Hello world")
// => <div>Hello world</div>

React.createElement("div", { className: "text-big", id: "text" }, "Hello world")
// => <div class="text-big" id="text">Hello world</div>

React.createElement("div", null, React.createElement("p", null, "Hello world"))
// => <div><p>Hello world</p></div>

React.createElement(
  "div",
  null,
  React.createElement("p", null, "Hello"),
  React.createElement("p", null, "world")
)
// => <div><p>Hello</p><p>world</p></div>

Créer un composant avec React

import * as React from "react"
import ReactDOM from "react-dom"

const Hello = () => {
  return React.createElement("div", null, "Hello world")
}

const appElement = document.querySelector("#app")

ReactDOM.render(React.createElement(Hello), appElement)
import * as React from "react"
import ReactDOM from "react-dom"

const Hello = (props) => {
  return React.createElement(
    "div",
    { className: "hello" },
    `Hello ${props.who}`
  )
}

const appElement = document.querySelector("#app")

ReactDOM.render(
  React.createElement(Hello, { who: "world" }),
  appElement
)

JSX

React.createElement(
  "div",
  { className: "title" },
  "Hello world"
)
<div className="title">
  Hello world
</div>
const Hello = (props) => {
  return (
    <div className="hello">
      Hello {props.who}
    </div>
  )
}
SyntaxError: expected expression, got '<'
React.createElement(
  "div",
  { className: "title" },
  "Hello world"
)
<div className="title">
  Hello world
</div>
import * as React from "react"
import ReactDOM from "react-dom"

const Hello = (props) => {
  return (
    <div className="hello">
      Hello {props.who}
    </div>
  )
}

const appElement = document.querySelector("#app")

ReactDOM.render(<Hello who="world" />, appElement)

Les événements

const MyComponent = () => {
  const handleClick = (e) => {
    console.log(e)
  }

  return (
    <button type="button" onClick={handleClick}>Click me!</button>
  )
}

onEventType (camelCase) : onSubmit, onKeyUp, onMouseHover...

Des composants avec un état dynamique

const [state, setState] = React.useState(initialValue)

Valeur courante

Fonction permettant de mettre à jour la valeur + réexécuter le composant

Valeur initiale

Gérer les effets de bord

Un composant React ne s'occupe que de retourner des éléments en fonction de ses props et de son state.

Tout le reste est un "effet de bord". Les effets de bord doivent être gérés avec la fonction React.useEffect

React.useEffect(didUpdate)

Fonction qui exécute des effets de bord

const ChatBox = (props) => {
  const [messages, setMessage] = React.useState([])
  
  React.useEffect(() => {
    const socket = websocket.connect(props.user.id)
    
    socket.on("message", (message) => setMessages([...messages, message]))  
    
    return () => {
      socket.close()
    }
  }, [props.user.id])
  
  return (
    <div>...</div>
  )
}

L'effet de bord sera réexécuté seulement si props.user.id a changé lors de la mise à jour

const Feed = (props) => {
  const [items, setItems] = React.useState([])
  
  React.useEffect(() => {
    fetchInitialItems()
      .then((items) => setItems(items))
  }, [])
  
  return (
    <div>...</div>
  )
}

L'effet de bord sera exécuté lorsque le composant est monté, mais pas lors des mises à jour

Récap !

  • En développement d'UI, l'unité est le composant
  • Plusieurs composants dynamiques qui communiquent => besoin d'une bonne organisation
  • Cette organisation est atteignable avec du JS pur, mais on se heurte à devoir manipuler le DOM de manière impérative
  • Les frameworks basés sur les composants permettent de construire des applications très dynamiques de manière déclarative
  • Les principaux frameworks sont : React, VueJS, Svelte, Angular

Programmation web - client riche - Introduction à React

By Cyrille Perois

Programmation web - client riche - Introduction à React

  • 1,140