Un exemple d'utilisation pratique des modules





Bonne journée, mes amis!



Les modules ES6 utilisant la syntaxe "import / export" sont des outils assez puissants et un concurrent digne des composants des frameworks populaires.



Laissez-moi vous le démontrer en dessinant différentes formes sur toile.



Inspiré de cette section du guide JavaScript de MDN.



Voici quelles fonctionnalités seront implémentées dans notre petite application:



  • création automatique d'un canevas de dimensions spécifiées et son rendu sur la page
  • la possibilité de dessiner des carrés, des cercles et des triangles d'une taille et d'une couleur données sur la toile
  • diviser le code en modules contenant des parties logiques de l'application


Dans le processus de création de l'application, nous porterons une attention particulière à l'export / import par défaut et nommé, ainsi qu'aux importations statiques et dynamiques. La plupart des applications seront écrites en utilisant la syntaxe de classe.



Il est souhaitable que vous ayez au moins une compréhension de base du travail avec les classes et le canevas.



Le code du projet est ici .



Une démo de l'application peut être consultée ici .



Commençons par le soutien.











Dans l'ensemble assez bon. En moyenne, environ 93%.



La structure du projet sera la suivante (vous pouvez créer tous les fichiers à la fois ou les créer selon vos besoins):



modules
  helpers
    convert.js
  shapes
    circle.js
    square.js
    triangle.js
  canvas.js
index.html
main.js
main.css


Le balisage ressemble à ceci:



<div>
  <section>
    <h3>Circle</h3>
    <label>
      X:
      <input type="number" value="75" data-prop="x" />
    </label>
    <label>
      Y:
      <input type="number" value="75" data-prop="y" />
    </label>
    <label>
      Radius:
      <input type="number" value="50" data-prop="radius" />
    </label>
    <label>
      Color:
      <input type="color" value="#ff0000" data-prop="color" />
    </label>
    <button data-btn="circle" class="draw_btn">Draw</button>
  </section>

  <section>
    <h3>Square</h3>
    <label>
      X:
      <input type="number" value="275" data-prop="x" />
    </label>
    <label>
      Y:
      <input type="number" value="175" data-prop="y" />
    </label>
    <label>
      Length:
      <input type="number" value="100" data-prop="length" />
    </label>
    <label>
      Color:
      <input type="color" value="#00ff00" data-prop="color" />
    </label>
    <button data-btn="square" class="draw_btn">Draw</button>
  </section>

  <section>
    <h3>Triangle</h3>
    <label>
      X:
      <input type="number" value="150" data-prop="x" />
    </label>
    <label>
      Y:
      <input type="number" value="100" data-prop="y" />
    </label>
    <label>
      Length:
      <input type="number" value="125" data-prop="length" />
    </label>
    <label>
      Color:
      <input type="color" value="#0000ff" data-prop="color" />
    </label>
    <button data-btn="triangle" class="draw_btn">Draw</button>
  </section>
</div>
<button>Clear Canvas</button>

<script src="main.js" type="module"></script>


À quoi devez-vous faire attention ici?



Pour chaque forme, une section distincte est créée avec des champs pour saisir les données nécessaires et un bouton pour démarrer le processus de dessin de la forme sur le canevas. Pour l'exemple d'une section pour un cercle, ces données sont: les coordonnées de départ, le rayon et la couleur. Nous définissons les champs d'entrée sur les valeurs initiales pour permettre un test rapide de l'intégrité de l'application. Les attributs "data-prop" sont conçus pour obtenir les valeurs des champs à saisir dans le script. Les attributs "data-btn" sont utilisés pour déterminer quel bouton a été enfoncé. Le dernier bouton est utilisé pour effacer le canevas.



Faites attention à la façon dont le script est connecté. L'attribut "type" avec la valeur "module" est obligatoire. L'attribut "defer" n'est pas nécessaire dans ce cas, car le chargement des modules est différé (c'est-à-dire après que la page est complètement chargée) par défaut. Notez également que nous n'incluons que le fichier "main.js" dans la page. D'autres fichiers sont utilisés dans "main.js" comme modules.



L'une des principales caractéristiques des modules est que chaque module a sa propre portée (contexte), y compris "main.js". D'une part, c'est bien car cela évite la pollution de l'espace de noms global et, par conséquent, évite les conflits entre les variables et les fonctions du même nom. Par contre, si différents modules doivent accéder aux mêmes éléments DOM, par exemple, vous devez soit créer un script séparé avec des variables globales et le connecter à la page avant le module principal, soit créer explicitement des variables globales (window.variable = value), ou créer les mêmes variables dans chaque module, ou échanger des variables entre les modules (ce que nous ferons en fait).



Il existe également une quatrième approche: accéder directement aux éléments DOM par ID. Connaissiez-vous cette possibilité? Par exemple, si nous avons un élément avec l'identifiant "main" dans notre balisage, nous pouvons le désigner simplement comme main (main.innerHTML = "<p> Some Awesome Content <p />") sans d'abord définir (rechercher) l'élément avec en utilisant "document.getElementById ()" ou des méthodes similaires. Cependant, cette approche n'est pas standard et son utilisation n'est pas recommandée, car on ne sait pas si elle sera prise en charge à l'avenir, bien que personnellement je trouve cette opportunité très pratique.



Une autre caractéristique des modules statiques est qu'ils ne peuvent être importés qu'une seule fois. La réimportation sera ignorée.



Enfin, la troisième caractéristique des modules est que le code du module ne peut pas être modifié après l'importation. En d'autres termes, les variables et fonctions déclarées dans un module ne peuvent être modifiées que dans ce module, où elles sont importées, cela ne peut pas être fait. Ceci est quelque peu similaire au modèle de conception Module, implémenté à l'aide d'un objet contenant des variables et des fonctions privées, ou à l'aide d'une classe avec des champs et des méthodes privés.



Passer à autre chose. Ajoutons quelques styles minimaux:



body {
  max-width: 768px;
  margin: 0 auto;
  color: #222;
  text-align: center;
}
canvas {
  display: block;
  margin: 1rem auto;
  border: 1px dashed #222;
  border-radius: 4px;
}
div {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
section {
  padding: 1rem;
}
label {
  display: block;
}
input {
  margin: 0.25rem 0;
}
input:not([type="color"]) {
  width: 50px;
}
button {
  margin: 0.25rem auto;
  cursor: pointer;
}
ul {
  list-style: none;
}
li {
  margin: 0.5rem auto;
  width: 320px;
  border-bottom: 1px dotted #222;
}
p {
  margin: 0.25rem 0;
}


Rien de spécial ici. Vous pouvez ajouter de la beauté à votre goût.



Passons aux modules.



Le fichier "canvas.js" contient le code de classe pour la création et le rendu d'un canevas, ainsi qu'une liste de messages affichés lors de la création d'une forme particulière (ces messages représentent des informations sur la zone et le périmètre (conventionnellement) de la forme):



// export default      
//           ,  IIFE (   ,    )
//   , ..  class ClassName...,  export default ClassName
export default class Canvas {
  //     :  ,   
  constructor(parent, width, height) {
    this.parent = parent;
    this.width = width;
    this.height = height;
    //   
    this.ctx = null;
    //   
    this.listEl = null;
    //        ,          
    this.clearCanvas = this.clearCanvas.bind(this);
  }

  //     
  createCanvas() {
    //      
    //  ,       
    //        
    if (this.ctx !== null) {
      console.log("Canvas already created!");
      return;
    } else {
      //   "canvas"
      const canvasEl = document.createElement("canvas");

      //         
      //        
      canvasEl.setAttribute("width", this.width);
      canvasEl.setAttribute("height", this.height);

      //     
      this.parent.append(canvasEl);

      //     
      this.ctx = canvasEl.getContext("2d");
    }

    //          
    return this;
  }

  //     
  //        
  createReportList() {
    if (this.listEl !== null) {
      console.log("Report list already created!");
      return;
    } else {
      const listEl = document.createElement("ul");
      this.parent.append(listEl);

      this.listEl = listEl;
    }

    return this;
  }

  //       
  clearCanvas() {
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.listEl.innerHTML = "";
  }
}


Le fichier "convert.js" contient une fonction de conversion des degrés en radians:



//  
export const convert = (degrees) => (degrees * Math.PI) / 180;


Chaque fichier du répertoire des formes est un module pour une forme particulière. En général, le code de ces modules est identique, à l'exception des méthodes de dessin, ainsi que des formules de calcul de l'aire et du périmètre d'une forme. Considérons un module contenant le code pour dessiner un cercle (circle.js):



//    
//     
//       -      "Module"
import { convert } from "../helpers/convert.js";

//  
//         
export class Circle {
  //     "" 
  //     
  //    ,    ""  ctx  listEl
  constructor({ ctx, listEl, radius, x, y, color }) {
    this.ctx = ctx;
    this.listEl = listEl;
    this.radius = radius;
    this.x = x;
    this.y = y;
    this.color = color;
    //  
    this.name = "Circle";
    //   
    this.listItemEl = document.createElement("li");
  }

  //    
  draw() {
    //   
    this.ctx.fillStyle = this.color;
    //  
    this.ctx.beginPath();
    //  arc  6 :
    //     "x",     "y", ,  ,  
    // (      "0, 2 * Math.PI")
    //   ,    :     
    this.ctx.arc(this.x, this.y, this.radius, convert(0), convert(360));
    //  
    this.ctx.fill();
  }

  //        
  report() {
    // 
    this.listItemEl.innerHTML = `<p>${this.name} area is ${Math.round(Math.PI * (this.radius * this.radius))}px squared.</p>`;
    // 
    this.listItemEl.innerHTML += `<p>${this.name} circumference is ${Math.round(2 * Math.PI * this.radius)}px.</p>`;

    this.listEl.append(this.listItemEl);
  }
}


Enfin, dans le fichier "main.js", une importation statique par défaut du module de classe "Canvas" est effectuée, une instance de cette classe est créée et les pressions sur les boutons sont gérées, ce qui consiste à importer dynamiquement le module de classe de figure correspondant et à appeler ses méthodes:



//   
//      
import Canvas from "./modules/canvas.js";

//   ,         
//     :
//   ,       
const { ctx, listEl, clearCanvas } = new Canvas(document.body, 400, 300).createCanvas().createReportList();

//    "" 
//    ,      
//      "async"
document.addEventListener("click", async (e) => {
  //     
  if (e.target.tagName !== "BUTTON") return;

  //     
  if (e.target.className === "draw_btn") {
    //   
    //  ,     
    //     
    const { btn: btnName } = e.target.dataset;

    //      
    //      -      
    const shapeName = `${btnName[0].toUpperCase()}${btnName.slice(1)}`;

    //     
    const shapeParams = {};

    //     
    const inputsEl = e.target.parentElement.querySelectorAll("input");

    //  
    inputsEl.forEach((input) => {
      //   
      //   
      const { prop } = input.dataset;
      //   
      //  ,   ,   
      const value = !isNaN(input.value) ? input.valueAsNumber : input.value;
      //      
      shapeParams[prop] = value;
    });
    //          
    shapeParams.ctx = ctx;
    shapeParams.listEl = listEl;

    console.log(shapeParams);

    //  
    //   "Module"
    const ShapeModule = await import(`./modules/shapes/${btnName}.js`);
    //      -  
    //    
    //             "Module" (  )       
    const shape = new ShapeModule[shapeName](shapeParams);

    //   
    shape.draw();
    //         
    shape.report();
  } else {
    //     
    //     "Canvas"
    clearCanvas();
  }
});


Vous pouvez jouer avec le code ici .



Comme vous pouvez le voir, les modules ES6 offrent des possibilités assez intéressantes liées à la division du code en blocs relativement autonomes contenant des parties logiques de l'application qui peuvent être chargées immédiatement ou à la demande. En tandem avec les littéraux de template, ils sont une bonne alternative aux composants des frameworks populaires. Je veux dire, tout d'abord, le rendu des pages côté client. De plus, cette approche vous permet de restituer uniquement les éléments DOM qui ont subi des modifications, ce qui, à son tour, élimine le besoin d'un DOM virtuel. Mais plus à ce sujet dans l'un des articles suivants.



J'espère que vous avez trouvé quelque chose d'intéressant pour vous-même. Merci de votre attention.



All Articles