Ein Beispiel für die praktische Verwendung von Modulen





Guten Tag, Freunde!



ES6-Module, die die "Import / Export" -Syntax verwenden, sind ziemlich leistungsfähige Werkzeuge und ein würdiger Konkurrent zu den Komponenten gängiger Frameworks.



Lassen Sie mich dies demonstrieren, indem Sie verschiedene Formen auf Leinwand zeichnen.



Inspiriert von diesem Abschnitt des JavaScript-Handbuchs von MDN.



Folgende Funktionen werden in unserer kleinen Anwendung implementiert:



  • Automatische Erstellung einer Zeichenfläche mit bestimmten Abmessungen und deren Darstellung auf der Seite
  • die Fähigkeit, Quadrate, Kreise und Dreiecke einer bestimmten Größe und Farbe auf die Leinwand zu zeichnen
  • Aufteilen des Codes in Module, die logische Teile der Anwendung enthalten


Bei der Erstellung der Anwendung wird besonderes Augenmerk auf den Standard- und benannten Export / Import sowie auf statische und dynamische Importe gelegt. Der größte Teil der Anwendung wird mit Klassensyntax geschrieben.



Es ist wünschenswert, dass Sie mindestens ein grundlegendes Verständnis für die Arbeit mit Klassen und der Leinwand haben.



Der Projektcode ist hier .



Eine Demo der Anwendung kann hier angesehen werden .



Beginnen wir mit der Unterstützung.











Insgesamt ziemlich gut. Im Durchschnitt etwa 93%.



Die Projektstruktur sieht wie folgt aus (Sie können alle Dateien gleichzeitig oder nach Bedarf erstellen):



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


Das Markup sieht folgendermaßen aus:



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


Worauf sollten Sie hier achten?



Für jede Form wird ein separater Abschnitt mit Feldern zur Eingabe der erforderlichen Daten und einer Schaltfläche zum Starten des Zeichenvorgangs auf der Leinwand erstellt. Am Beispiel eines Abschnitts für einen Kreis sind diese Daten: Startkoordinaten, Radius und Farbe. Wir setzen die Eingabefelder auf Anfangswerte, um einen schnellen Test des Anwendungszustands zu ermöglichen. Die Attribute "data-prop" dienen dazu, die Werte der Felder für die Eingabe in das Skript abzurufen. Die "data-btn" -Attribute werden verwendet, um zu bestimmen, welche Taste gedrückt wurde. Mit der letzten Schaltfläche wird die Leinwand gelöscht.



Achten Sie darauf, wie das Skript verbunden ist. Das Attribut "Typ" mit dem Wert "Modul" ist erforderlich. Das Attribut "Aufschieben" ist in diesem Fall nicht erforderlich, da das Laden von Modulen standardmäßig aufgeschoben wird (dh nachdem die Seite vollständig geladen wurde). Beachten Sie auch, dass wir nur die Datei "main.js" in die Seite aufnehmen. Andere Dateien werden in "main.js" als Module verwendet.



Eines der Hauptmerkmale von Modulen ist, dass jedes Modul seinen eigenen Bereich (Kontext) hat, einschließlich "main.js". Dies ist zum einen gut, weil es die Verschmutzung des globalen Namespace vermeidet und daher Konflikte zwischen gleichnamigen Variablen und Funktionen verhindert. Wenn andererseits verschiedene Module auf dieselben DOM-Elemente zugreifen müssen, müssen Sie entweder ein separates Skript mit globalen Variablen erstellen und es mit der Seite vor dem Hauptmodul verbinden oder explizit globale Variablen erstellen (window.variable = value) oder Erstellen Sie die gleichen Variablen in jedem Modul oder tauschen Sie Variablen zwischen Modulen aus (was wir tatsächlich tun werden).



Es gibt auch einen vierten Ansatz: Zugriff auf DOM-Elemente über die ID direkt. Wussten Sie über diese Möglichkeit Bescheid? Wenn wir beispielsweise ein Element mit dem Bezeichner "main" in unserem Markup haben, können wir es einfach als main (main.innerHTML = "<p> Some Awesome Content <p />") bezeichnen, ohne das Element zuerst zu definieren (zu suchen) Verwenden von "document.getElementById ()" oder ähnlichen Methoden. Dieser Ansatz ist jedoch nicht standardisiert und wird nicht für die Verwendung empfohlen, da nicht bekannt ist, ob er in Zukunft unterstützt wird, obwohl ich persönlich diese Möglichkeit sehr praktisch finde.



Ein weiteres Merkmal statischer Module ist, dass sie nur einmal importiert werden können. Der erneute Import wird ignoriert.



Das dritte Merkmal von Modulen ist schließlich, dass der Modulcode nach dem Import nicht geändert werden kann. Mit anderen Worten, Variablen und Funktionen, die in einem Modul deklariert sind, können nur in diesem Modul geändert werden, wo sie importiert werden. Dies ist nicht möglich. Dies erinnert ein wenig an das Entwurfsmuster des Moduls, das mithilfe eines Objekts mit privaten Variablen und Funktionen oder mithilfe einer Klasse mit privaten Feldern und Methoden implementiert wurde.



Weitermachen. Fügen wir einige minimale Stile hinzu:



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


Hier ist nichts Besonderes. Sie können Ihrem Geschmack Schönheit hinzufügen.



Kommen wir zu den Modulen.



Die Datei "canvas.js" enthält den Klassencode zum Erstellen und Rendern einer Zeichenfläche sowie eine Liste der Nachrichten, die beim Erstellen einer bestimmten Form angezeigt werden (diese Nachrichten stellen Informationen über den Bereich und den Umfang (herkömmlich) der Form dar):



// 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 = "";
  }
}


Die Datei convert.js enthält eine Funktion zum Konvertieren von Grad in Bogenmaß:



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


Jede Datei im Shapes-Verzeichnis ist ein Modul für eine bestimmte Form. Im Allgemeinen ist der Code dieser Module identisch, mit Ausnahme der Zeichenmethoden sowie der Formeln zur Berechnung der Fläche und des Umfangs einer Form. Stellen Sie sich ein Modul vor, das den Code zum Zeichnen eines Kreises enthält (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);
  }
}


Schließlich wird in der Datei "main.js" ein statischer Standardimport des Klassenmoduls "Canvas" ausgeführt, eine Instanz dieser Klasse erstellt und ein Tastendruck ausgeführt, der darin besteht, das entsprechende Figurenklassenmodul dynamisch zu importieren und seine Methoden aufzurufen:



//   
//      
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();
  }
});


Sie können mit dem Code spielen hier .



Wie Sie sehen, bieten ES6-Module interessante Möglichkeiten für die Aufteilung von Code in relativ eigenständige Blöcke mit logischen Teilen der Anwendung, die sofort oder bei Bedarf geladen werden können. Zusammen mit Vorlagenliteralen sind sie eine gute Alternative zu den Komponenten gängiger Frameworks. Ich meine zunächst einmal das Rendern von Seiten auf der Client-Seite. Darüber hinaus können Sie mit diesem Ansatz nur die DOM-Elemente neu rendern, für die Änderungen vorgenommen wurden, sodass kein virtuelles DOM erforderlich ist. Aber mehr dazu in einem der folgenden Artikel.



Ich hoffe, Sie haben etwas Interessantes für sich gefunden. Vielen Dank für Ihre Aufmerksamkeit.



All Articles