All posts in FrontEnd

La gestion des états dans React

React est une librairie JavaScript dédiée à la constructions d’ interfaces pour des applications web.
L’un des plus gros challenges dans une application React est d’afficher des données de manière efficace et efficiente. Plus l’application grandit, plus il devient difficile d’orchestrer correctement tous les composants tout en évitant les re-rendus inutiles.Nous allons voir, au cours de cet article, les différents moyens de partager de l’information entre les composants d’une application React. Nous verrons aussi les pièges à éviter pour faciliter la gestion des états dans React.

UseState et prop-drilling

Le flux de données dans React est unidirectionnel. Cela signifie que les données circulent dans un seul sens : des composants parents vers les composants enfants. L’objectif est de favoriser une architecture de données propre et prévisible.

Les props permettent de transmettre des données des composants parents aux composants enfants. Elles sont en lecture seule dans le composant enfant, ce qui garantit que ce dernier ne peut pas modifier directement ces données.

La gestion des états avec useState et prop-drilling

Le « prop-drilling » désigne le processus de transmission des props à travers plusieurs couches de composants. Certains de ces composants n’utilisent pas directement ces données et ne font que les relayer à leurs propres composants enfants.

La gestions des états dans React et l'enfer du prop-drilling

Cela peut entraîner des difficultés de débogage, des comportements inattendus, ainsi que des composants étroitement couplés et difficiles à réutiliser.

React Context

Les contextes React permettent à un composant parent de mettre certaines informations à disposition de n’importe quel composant de l’arborescence, quelle que soit sa profondeur. La donnée devient ainsi accessible sans avoir à la transmettre explicitement via des props, ce qui permet de résoudre le problème du prop-drilling.

Un composant « abonné » à un contexte (via le hook useContext) est automatiquement re-rendu lorsqu’une modification intervient dans ce dernier. Cependant, toute modification d’une partie du contexte entraîne le re-rendu de tous les composants abonnés.

La gestions des états dans React avec les contextes

On peut alors envisager de créer un contexte par type de donnée afin de les partager dans notre arbre de composants. Mais que se passe-t-il si l’on se retrouve avec 50 contextes différents ? On plonge alors dans l’enfer des contextes.

La gestions des états dans React et l'enfer des contextes

L’enfer des contextes React survient lorsque l’API Context est utilisée de manière excessive ou inappropriée. C’est comme si votre application devenait un puzzle à trop de couches, rendant l’ensemble difficile à suivre et à maintenir.

Imaginez une situation avec de nombreuses couches, chacune contenant des éléments différents, comme des états ou des fonctions. Cela devient un véritable fouillis, difficile à comprendre, à tester et à maintenir.

Stores Redux

Un store Redux est un emplacement qui contient toutes les données. A l’inverse des contextes React, nous n’avons besoin que d’un seul store, lequel peut être découpé en « slices » (morceaux). La modification d’une partie du store entraîne des re-rendus des composants abonnés à cette partie seulement.

Une mise à jour d’une partie du store est déclenchée depuis n’importe quelle partie de l’arbre des composants. Une action utilisateur est le plus souvent à l’origine du déclenchement. Dans la terminologie Redux, cela s’appelle « dispatch » d’actions.

La gestions des états dans React avec Redux

L’action est envoyée aux « reducers ». Ce sont des fonctions pures qui permettent de modifier l’état. Par exemple, une action « increment » déclenchera la fonction qui incrémente un compteur. Une autre action « addOrder » déclenchera la fonction qui ajoute une commande dans notre liste de commandes, etc.

La fonction reducer est exécutée, puis la partie du store concernée est mise à jour. Finalement, les composants abonnées déclenchent un re-rendu.

Conclusion

Nous avons vu au cours de cet article différentes façons de maintenir des données pour optimiser la gestions des états dans React. Ils est possible de combiner ces trois concepts précédemment étudiés, mais il est nécessaire de savoir quand et comment les utiliser :

  • Utilisez le prop-drilling lorsque vous souhaitez passer des données à des dumb-components. Ceux-ci se contenteront d’afficher les données passées en paramètres, sans les transmettre à nouveau à d’autres composants.
  • Utilisez les contextes pour des données de haut niveau qui nécessitent un re-rendu global : gestion du thème, de la langue, des préférences utilisateur, de l’authentification, etc.
  • Utilisez un store Redux pour gérer les données métier : liste d’utilisateurs, liste de factures, etc.

Vos composants React sont-ils purs ?

Quand on développe des applications web avec React, il faut s’assurer que les composants se comportent de manière prévisible et performante. L’un des concepts clés pour y parvenir est celui des composants purs. Que signifie « composant pur », et pourquoi cela est si important ? Avec cet article, nous allons expliquer ce concept, comparer des exemples de composants purs et non purs, et finalement s’intéresser aux tests unitaires qui garantissent la pureté des composants.

Qu’est-ce qu’un composant pur ?

composants React purs

Un composant pur est un composant qui génère toujours le même résultat pour des entrées et un état donné. Le rendu d’un composant dépend seulement de ses entrées et est totalement déterminé par celles-ci. Si les entrées et l’état ne changent pas, le composant rendra toujours le même résultat, peu importe le nombre d’appels.

Les composants purs sont plus faciles à tester, plus performants et ont moins d’effets secondaires indésirables. Avec React, il est possible de créer un composant pur en utilisant la classe React.PureComponent ou en écrivant un composant fonctionnel qui n’introduit pas d’effets secondaires.

Composants non purs

Un composant non pur peut avoir des effets secondaires ou un comportement qui dépend de facteurs autres que ses entrées et son état.: Il peut s’agir par exemple de variables globales, d’états internes non contrôlés, ou d’appels à des API qui modifient le rendu de manière imprévisible. Ces composants sont difficiles voir impossible à tester et à debuguer, car leur sortie ne dépend pas toujours de leurs entrées et état.

Voici un exemple simple de composant non pur :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
export default function VisitCounter({ count }) {
if (count <= 10 ) {
document.getElementById('counter').className = 'red';
} else {
document.getElementById('counter').className = 'green';
}
return (
<h1 id="counter">
Number of visits: {count}
</h1>
);
}
import React from 'react'; export default function VisitCounter({ count }) { if (count <= 10 ) { document.getElementById('counter').className = 'red'; } else { document.getElementById('counter').className = 'green'; } return ( <h1 id="counter"> Number of visits: {count} </h1> ); }
import React from 'react';

export default function VisitCounter({ count }) {
  
  if (count <= 10 ) {
    document.getElementById('counter').className = 'red';
  } else {
    document.getElementById('counter').className = 'green';
  }

  return (
    

Number of visits: {count}

); }

Dans cet exemple, le changement de la classe introduit un effet secondaire qui peut rendre le comportement du composant imprévisible, ce qui complique les tests.

Composants purs : exemple

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
export default function VisitCounter({ count }) {
let className;
if (count <= 10) {
className = 'red';
} else {
className = 'green';
}
return (
<h1 id="counter" classname="{className}">
Number of visits: {count}
</h1>
);
}
export default React.memo(VisitCounter);
import React from 'react'; export default function VisitCounter({ count }) { let className; if (count <= 10) { className = 'red'; } else { className = 'green'; } return ( <h1 id="counter" classname="{className}"> Number of visits: {count} </h1> ); } export default React.memo(VisitCounter);
import React from 'react';

export default function VisitCounter({ count }) {
 
  let className;

  if (count <= 10) {
    className = 'red';
  } else {
    className = 'green';
  }

  return (
    

Number of visits: {count}

); } export default React.memo(VisitCounter);

Ici, le composant VisitCounter dépend de l’entrée « count » et l’affiche dans une balise « h1 ». On utilise React.memo pour optimiser ce composant et éviter des re-rendus inutiles si la propriété en entrée ne change pas.

Pourquoi utiliser des composants purs ?

Utiliser des composants purs présente plusieurs avantages :

  • Prévisibilité : un composant pur est facile à comprendre et à tester. Son comportement ne dépend pas d’effets secondaires ou de dépendances externes.
  • Optimisation des performances : React peut optimiser le rendu des composants purs en évitant les re-rendus inutiles grâce à la comparaison des propriétés en entrée. Il faut utiliser React.PureComponent ou React.memo.
  • Facilité des tests unitaires : tester des composants purs est plus simple, car leur sortie dépend uniquement de leurs entrées. Cela permet de rédiger des tests plus ciblés et fiables.

Exemple de test unitaire

Voici un exemple de test unitaire pour vérifier le comportement des composants purs.

Tester un composant pur comme VisitCounter est simple. Vérifions que l’entrée est bien rendue dans le retour du composant.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
import { render, screen } from '@testing-library/react';
import { VisitCounter } from './VisitCounter';
describe('<visitcounter></visitcounter> test', () => {
test('renders correctly', () => {
render(<visitcounter count="5"></visitcounter>);
expect(screen.getByText('Number of visits: 0')).toBeInTheDocument();
const counter = screen.getByTestId('counter');
expect(counter).toHaveClass('red');
});
});
import React from 'react'; import { render, screen } from '@testing-library/react'; import { VisitCounter } from './VisitCounter'; describe('<visitcounter></visitcounter> test', () => { test('renders correctly', () => { render(<visitcounter count="5"></visitcounter>); expect(screen.getByText('Number of visits: 0')).toBeInTheDocument(); const counter = screen.getByTestId('counter'); expect(counter).toHaveClass('red'); }); });
import React from 'react';
import { render, screen } from '@testing-library/react';
import { VisitCounter } from './VisitCounter';

describe(' test', () => {
  test('renders correctly', () => {
    render();
    expect(screen.getByText('Number of visits: 0')).toBeInTheDocument();
    const counter = screen.getByTestId('counter'); 
    expect(counter).toHaveClass('red');
  });
});

Conclusion

Les composants purs offrent de nombreux avantages, qui sont principalement la prévisibilité et la performance. En évitant les effets secondaires, nous créons des composants plus robustes, plus simples et plus faciles à tester. En suivant les bonnes pratiques et en utilisant les classes et méthodes adaptées (React.memo ou React.PureComponent), nous améliorons la qualité de notre code React et améliorons l’expérience utilisateur.

Et vous, vos composants React sont-ils purs ?

Décorateurs TypeScript et Web Components

Chez DocDoku nous animons des formations sur les outils et frameworks pour le développement front-end. Les trois principaux acteurs du marché sont aujourd’hui React, Vue et Angular. Ils offrent tous les trois la possibilité d’écrire du code de manière déclarative pour créer nos interfaces.Néanmoins, si nous devions réaliser une petite application, choisir un de ces framework pourrait paraître surdimensionné. Alors pourquoi ne pas réaliser un petit framework maison ? Il y a en effet plusieurs technologies à notre disposition pour ce faire. Nous vous proposons d’en étudier quelques-unes dans cet article.

Les deux technologies que nous allons aborder sont les Web Components (suite de technologies HTML5) et les décorateurs TypeScript qui vont nous permettre d’effleurer la surface de la réalisation d’un framework.

Les Web Components

Les Web Components sont un regroupement de trois technologies HTML5 :

  • Les custom elements
  • Les shadow root
  • Les templates

La principale caractéristique des Web Components est la possibilité de créer des éléments personnalisés, par exemple enrichir des éléments existants comme un champ de texte, un bouton, etc. C’est aussi la possibilité de créer de nouveaux éléments HTML (balises) avec un apport sémantique dans la construction de nos applicatifs, par exemple une nouvelle balise HTML my-todo-list qui construirait elle même ses sous-éléments. Nous parlons alors de custom elements.

Voici le code le plus simple pour définir une nouvelle balise :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MyTodoList extends HTMLElement {
constructor() {
super();
}
}
customElements.define("my-todo-list", MyTodoList);
class MyTodoList extends HTMLElement { constructor() { super(); } } customElements.define("my-todo-list", MyTodoList);
class MyTodoList extends HTMLElement {
  constructor() {
    super();
  }
}
customElements.define("my-todo-list", MyTodoList);

Une autre caractéristique notable est l’isolement (encapsulation) de l’HTML sous-jacent. Notre nouvelle balise et son contenu enfant peuvent être totalement « détachés » du reste de la page et ainsi ne subir aucun impact du reste du DOM, avoir sa propre feuille de style locale, et ne pas impacter les autres éléments voisins. C’est le rôle du shadow DOM.

Finalement nous pouvons nous passer de l’écriture fastidieuse en JavaScript de la construction des éléments enfants en utilisant la balise template. Cette balise nous permet d’écrire le contenu de nos éléments personnalisés directement en HTML. Aussi nous pouvons combiner les templates avec l’utilisation de la balise slot afin de rendre nos templates paramétrables.

Utilisation de la balise template :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="my-todo-list-template">
<h1>Hello todo list</h1>
<ul>
<li>Do this</li>
<li>Do that</li>
</ul>
</template>
<template id="my-todo-list-template"> <h1>Hello todo list</h1> <ul> <li>Do this</li> <li>Do that</li> </ul> </template>

 

Instanciation d’un élément depuis ce template :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let template = document.getElementById("my-todo-list-template");
let templateContent = template.content;
document.body.appendChild(templateContent);
let template = document.getElementById("my-todo-list-template"); let templateContent = template.content; document.body.appendChild(templateContent);
let template = document.getElementById("my-todo-list-template");
let templateContent = template.content;
document.body.appendChild(templateContent);

Exemple complet avec ces trois technologies :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="my-todo-list-template">
<h1><slot name="my-title">Default title</slot></h1>
<ul>
<li>Do this</li>
<li>Do that</li>
</ul>
</template>
<my-todo-list>
<span slot="my-title">Hello todo list 1</span>
</my-todo-list>
<my-todo-list>
</my-todo-list>
<template id="my-todo-list-template"> <h1><slot name="my-title">Default title</slot></h1> <ul> <li>Do this</li> <li>Do that</li> </ul> </template> <my-todo-list> <span slot="my-title">Hello todo list 1</span> </my-todo-list> <my-todo-list> </my-todo-list>


   Hello todo list 1



Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MyTodoList extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-todo-list-template');
const templateContent = template.content;
this.attachShadow({ mode: 'open' }).appendChild(
templateContent.cloneNode(true)
);
}
}
customElements.define('my-todo-list',MyTodoList)
class MyTodoList extends HTMLElement { constructor() { super(); const template = document.getElementById('my-todo-list-template'); const templateContent = template.content; this.attachShadow({ mode: 'open' }).appendChild( templateContent.cloneNode(true) ); } } customElements.define('my-todo-list',MyTodoList)
class MyTodoList extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById('my-todo-list-template');
    const templateContent = template.content;

    this.attachShadow({ mode: 'open' }).appendChild(
      templateContent.cloneNode(true)
    );
  }
}
customElements.define('my-todo-list',MyTodoList)

Les décorateurs TypeScript

TypeScript est une extension du langage JavaScript qui nous apporte le confort des contraintes. Etudions ce code JavaScript :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var i = 2
i = "hello"
var i = 2 i = "hello"
var i = 2
i = "hello"

Nous ne savons pas lors de l’écriture du code si la variable i est un nombre ou une chaine de caractères et cela peut être fatal si nous ne faisons pas l’effort de vérifier son type lors de son utilisation.

Avec TypeScript nous sommes contraints à garder le type défini lors de l’initialisation.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let i : number = 2
i = "hello" // Interdit
let i : number = 2 i = "hello" // Interdit
let i : number = 2
i = "hello" // Interdit

Cet exemple nous montre seulement un des aspects de TypeScript. Ce qui nous intéresse ici est l’apport des décorateurs.

Imaginons que nous souhaitions automatiser l’exécution de code pour deux classes différentes. Par exemple une classe Voiture et une classe Animal qui auraient toutes les deux leur table associée dans la base de données.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Animal {
static {
// code pour initialiser la table ANIMAL
}
}
class Voiture {
static {
// code pour initialiser la table VOITURE
}
}
class Animal { static { // code pour initialiser la table ANIMAL } } class Voiture { static { // code pour initialiser la table VOITURE } }
class Animal {
  static {
    // code pour initialiser la table ANIMAL
  }
}

class Voiture {
  static {
    // code pour initialiser la table VOITURE
  }
}

Dans cet exemple, difficile de mutualiser du code, nos classes ont un bloc d’initialisation static qui peut contenir de plus en plus de choses, difficile de maintenir, etc.

Avec les décorateurs TypeScript nous pouvons imaginer écrire nos classes différemment :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@CreateTable("ANIMAL")
class Animal {
}
@CreateTable("VOITURE")
class Voiture {
}
@CreateTable("ANIMAL") class Animal { } @CreateTable("VOITURE") class Voiture { }
@CreateTable("ANIMAL")
class Animal {
}

@CreateTable("VOITURE")
class Voiture {
}

Et pourquoi pas leur ajouter d’autres fonctionnalités, par exemple l’utilisation d’un utilitaire de log différent :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@CreateTable("ANIMAL")
@Logger(SomeLogger)
class Animal {
}
@CreateTable("VOITURE")
@Logger(AnOtherLogger)
class Voiture {
}
@CreateTable("ANIMAL") @Logger(SomeLogger) class Animal { } @CreateTable("VOITURE") @Logger(AnOtherLogger) class Voiture { }
@CreateTable("ANIMAL")
@Logger(SomeLogger)
class Animal {
}

@CreateTable("VOITURE")
@Logger(AnOtherLogger)
class Voiture {
}

Derrière ces décorateurs se cachent en fait de simples fonctions. Celles-ci ne sont exécutées qu’une fois au chargement de la classe, et non lors de nouvelles instances de celles-ci.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const CreateTable = (name: string) => {
return (target: Function) => {
console.log(name) // Le nom passé en paramètre, ici "ANIMAL"
console.log(target) // L'objet de type class, ici class Animal
console.log(Object.getOwnPropertyDescriptors(target.prototype)) // Liste des méthodes
};
};
@CreateTable("ANIMAL")
class Animal {
_age: number
get age(){ return this._age }
manger(){
//...
}
}
/*
ANIMAL
[class Animal]
{
constructor: {
value: [class Animal],
writable: true,
enumerable: false,
configurable: true
},
age: {
get: [Function: get age],
set: undefined,
enumerable: false,
configurable: true
},
manger: {
value: [Function: manger],
writable: true,
enumerable: false,
configurable: true
}
}
*/
const CreateTable = (name: string) => { return (target: Function) => { console.log(name) // Le nom passé en paramètre, ici "ANIMAL" console.log(target) // L'objet de type class, ici class Animal console.log(Object.getOwnPropertyDescriptors(target.prototype)) // Liste des méthodes }; }; @CreateTable("ANIMAL") class Animal { _age: number get age(){ return this._age } manger(){ //... } } /* ANIMAL [class Animal] { constructor: { value: [class Animal], writable: true, enumerable: false, configurable: true }, age: { get: [Function: get age], set: undefined, enumerable: false, configurable: true }, manger: { value: [Function: manger], writable: true, enumerable: false, configurable: true } } */
const CreateTable = (name: string) => {
  return (target: Function) => {
    console.log(name)    // Le nom passé en paramètre, ici "ANIMAL"
    console.log(target)  // L'objet de type class, ici class Animal
    console.log(Object.getOwnPropertyDescriptors(target.prototype)) // Liste des méthodes
  };
};


@CreateTable("ANIMAL")
class Animal {
    _age: number
    get age(){ return this._age }

    manger(){
        //...
    }
}

/*
ANIMAL
[class Animal]
{
  constructor: {
    value: [class Animal],
    writable: true,
    enumerable: false,
    configurable: true
  },
  age: {
    get: [Function: get age],
    set: undefined,
    enumerable: false,
    configurable: true
  },
  manger: {
    value: [Function: manger],
    writable: true,
    enumerable: false,
    configurable: true
  }
}
*/

Début d’un framework basé sur ces deux technologies

En combinant les Web Components et les décorateurs TypeScript, nous pouvons alors imaginer réaliser un début de framework maison inspiré d’Angular.

Avec Angular, la création d’un composant est déclarative, et son utilisation très simple depuis un fichier HTML :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Component({
selector: 'app-my-component',
template: '<h1>{{ title }}</h1>',
styles: ['h1 { font-weight: normal; }']
})
class MyComponent {
title = "Hello world"
}
@Component({ selector: 'app-my-component', template: '<h1>{{ title }}</h1>', styles: ['h1 { font-weight: normal; }'] }) class MyComponent { title = "Hello world" }
@Component({
  selector: 'app-my-component',
  template: '

{{ title }}

', styles: ['h1 { font-weight: normal; }'] }) class MyComponent { title = "Hello world" }
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<app-my-component></app-my-component>
<app-my-component></app-my-component>

Nous allons nous inspirer de cela pour écrire un début de framework. Nous avons besoin d’un décorateur qui va créer automatiquement son shadow DOM depuis le template passé dans les arguments du décorateur « Component ».

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Paramètres du décorateur
interface ComponentOptions {
selector: string; // Nom de la nouvelle balise
template: string; // Le template associé
style: string; // Un peu de css
}
// Fonction décorateur
function Component(options: ComponentOptions) {
return function < T extends { new (..._args: any[]): HTMLElement } >(
target: T
) {
const cls = class extends target {
constructor(..._args: any[]) {
super();
// Creation du shadow DOM
let root = this.attachShadow({ mode: "open" });
// Création des balises enfants avec EJS (moteur de template)
root.innerHTML = options.template;
// Ajout de style local
let style = document.createElement("style");
style.textContent = options.style;
root.appendChild(style);
}
};
// Enregistrement automatique en tant que custom element
customElements.define(options.selector, cls);
return cls;
};
}
@Component({
selector: "my-todo-list",
template: `
<h2><%= title %></h2>
<ul>
<li>Do this</li>
<li>Do that</li>
</ul>
`,
style: `h2 {color: blue}`,
})
export default class HelloWorld extends HTMLElement {
title = "Hello world !";
}
// Paramètres du décorateur interface ComponentOptions { selector: string; // Nom de la nouvelle balise template: string; // Le template associé style: string; // Un peu de css } // Fonction décorateur function Component(options: ComponentOptions) { return function < T extends { new (..._args: any[]): HTMLElement } >( target: T ) { const cls = class extends target { constructor(..._args: any[]) { super(); // Creation du shadow DOM let root = this.attachShadow({ mode: "open" }); // Création des balises enfants avec EJS (moteur de template) root.innerHTML = options.template; // Ajout de style local let style = document.createElement("style"); style.textContent = options.style; root.appendChild(style); } }; // Enregistrement automatique en tant que custom element customElements.define(options.selector, cls); return cls; }; } @Component({ selector: "my-todo-list", template: ` <h2><%= title %></h2> <ul> <li>Do this</li> <li>Do that</li> </ul> `, style: `h2 {color: blue}`, }) export default class HelloWorld extends HTMLElement { title = "Hello world !"; }
// Paramètres du décorateur
interface ComponentOptions {
  selector: string; // Nom de la nouvelle balise
  template: string; // Le template associé
  style: string; // Un peu de css
}

// Fonction décorateur
function Component(options: ComponentOptions) {
  return function < T extends { new (..._args: any[]): HTMLElement } >(
    target: T
  ) {
    const cls = class extends target {
      constructor(..._args: any[]) {
        super();

        // Creation du shadow DOM
        let root = this.attachShadow({ mode: "open" });

        // Création des balises enfants avec EJS (moteur de template)
        root.innerHTML = options.template;

        // Ajout de style local
        let style = document.createElement("style");
        style.textContent = options.style;
        root.appendChild(style);
      }
    };

    // Enregistrement automatique en tant que custom element
    customElements.define(options.selector, cls);

    return cls;
  };
}

@Component({
  selector: "my-todo-list",
  template: `
   

<%= title %>

  • Do this
  • Do that
`, style: `h2 {color: blue}`, }) export default class HelloWorld extends HTMLElement { title = "Hello world !"; }
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<my-todo-list></my-todo-list>
<my-todo-list></my-todo-list>

Bien entendu, notre framework maison est encore loin d’être complet. Nous aurions besoin d’y ajouter une détection des changements, du routing, une couche HTTP, etc. Seule la partie de création de composants et d’affichage a été abordée dans cet article.

8 thématiques incontournables à connaître pour maîtriser HTML5

L’HTML va bientôt fêter ses 30 ans. Ce langage de balisage a beaucoup évolué depuis sa création, avec l’intégration de JavaScript et CSS (Cascading Style Sheets) qui sont venus grossir les rangs des technologies web.
Et côté utilisateurs, dynamiser dans son ensemble la navigation web.

Développer des applications web modernes comme le permet HTML5 et CSS3 ne s’improvise pas.
Voici le top 8 des sujets incontournables à connaître pour maîtriser ce langage :

  • Une brique essentielle : JavaScript
  • Les API de communication
  • Le stockage côté client
  • Graphisme et multimédia
  • Les nouveaux tags HTML5
  • La présentation avec CSS3
  • Architecture et conception
  • L’outillage et l’environnement de développement

Vous êtes Architectes, développeurs ou webmasters et certains de ces sujets vous échappent encore ?

Nous avons une piste pour vous permettre de mettre à jour rapidement vos compétences : rejoignez notre prochaine session de formation Développer des applications HTML 5.

Prochaines sessions HTML5 à Toulouse :
Du 26 au 28 février 2020 (dernières places disponibles!)
Du 18 au 20 mai 2020

Prochaines sessions HTML5 à Paris :
Du 22 au 24 janvier 2020
Du 18 au 20 mars 2020