
Bom dia amigos!
Módulos ES6 que usam a sintaxe "importar / exportar" são ferramentas bastante poderosas e competem com os componentes de estruturas populares.
Deixe-me demonstrar isso desenhando várias formas na tela.
Inspirado nesta seção do Guia de JavaScript do MDN.
Esta é a funcionalidade que será implementada em nosso pequeno aplicativo:
- criação automática de uma tela de dimensões especificadas e sua renderização na página
- a capacidade de desenhar quadrados, círculos e triângulos de um determinado tamanho e cor na tela
- dividir o código em módulos contendo partes lógicas do aplicativo
No processo de criação da aplicação, prestaremos atenção especial ao padrão e denominado exportação / importação, bem como às importações estáticas e dinâmicas. A maior parte do aplicativo será escrita usando a sintaxe de classe.
É desejável que você tenha pelo menos um conhecimento básico de como trabalhar com classes e o canvas.
O código do projeto está aqui .
Uma demonstração do aplicativo pode ser vista aqui .
Vamos começar com o suporte.


Geral muito bom. Em média, cerca de 93%.
A estrutura do projeto será a seguinte (você pode criar todos os arquivos de uma vez ou criá-los conforme necessário):
modules
helpers
convert.js
shapes
circle.js
square.js
triangle.js
canvas.js
index.html
main.js
main.css
A marcação é semelhante a esta:
<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>
O que você deve prestar atenção aqui?
Para cada forma, uma seção separada é criada com campos para inserir os dados necessários e um botão para iniciar o processo de desenho da forma na tela. No exemplo de uma seção para um círculo, esses dados são: coordenadas iniciais, raio e cor. Definimos os campos de entrada com os valores iniciais para permitir um teste rápido da integridade do aplicativo. Os atributos "data-prop" são projetados para obter os valores dos campos para entrada no script. Os atributos "data-btn" destinam-se a determinar qual botão foi pressionado. O último botão é usado para limpar a tela.
Preste atenção em como o script está conectado. O atributo "type" com o valor "module" é obrigatório. O atributo "adiar" não é necessário neste caso, uma vez que o carregamento dos módulos é adiado (ou seja, após a página ter sido totalmente carregada) por padrão. Observe também que incluímos apenas o arquivo "main.js" na página. Outros arquivos são usados dentro de "main.js" como módulos.
Uma das principais características dos módulos é que cada módulo tem seu próprio escopo (contexto), incluindo "main.js". Por um lado, isso é bom porque evita a poluição do namespace global e, portanto, evita conflitos entre variáveis e funções de mesmo nome. Por outro lado, se diferentes módulos precisam acessar os mesmos elementos DOM, por exemplo, você deve criar um script separado com variáveis globais e conectá-lo à página antes do módulo principal ou criar explicitamente variáveis globais (window.variable = value), ou crie as mesmas variáveis dentro de cada módulo ou troque variáveis entre os módulos (o que nós, de fato, faremos).
Há também uma quarta abordagem: acessar elementos DOM por ID diretamente. Você sabia dessa possibilidade? Por exemplo, se tivermos um elemento com o identificador "main" em nossa marcação, podemos nos referir a ele simplesmente como main (main.innerHTML = "<p> Algum conteúdo incrível <p />") sem primeiro definir (pesquisar) o elemento com usando "document.getElementById ()" ou métodos semelhantes. No entanto, esta abordagem não é padronizada e não é recomendada para uso, uma vez que não se sabe se ela será suportada no futuro, embora eu pessoalmente ache esta oportunidade muito conveniente.
Outro recurso dos módulos estáticos é que eles só podem ser importados uma vez. A reimportação será ignorada.
Finalmente, o terceiro recurso dos módulos é que o código do módulo não pode ser alterado após a importação. Ou seja, as variáveis e funções declaradas em um módulo só podem ser alteradas neste módulo, para onde são importadas, isso não pode ser feito. Isso lembra um pouco o padrão de design do Módulo, implementado usando um objeto contendo variáveis e funções privadas ou usando uma classe com campos e métodos privados.
Se movendo. Vamos adicionar alguns estilos mínimos:
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;
}
Nada de especial aqui. Você pode adicionar beleza ao seu gosto.
Vamos passar para os módulos.
O arquivo "canvas.js" contém o código da classe para criar e renderizar a tela, bem como uma lista de mensagens exibidas ao criar uma forma específica (essas mensagens representam informações sobre a área e o perímetro da forma (convencionalmente)):
// 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 = "";
}
}
O arquivo convert.js contém uma função para converter graus em radianos:
//
export const convert = (degrees) => (degrees * Math.PI) / 180;
Cada arquivo no diretório de formas é um módulo para uma forma específica. Em geral, o código desses módulos é idêntico, com exceção dos métodos de desenho, bem como das fórmulas de cálculo da área e do perímetro de uma forma. Considere um módulo que contém o código para desenhar um círculo (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);
}
}
Por fim, no arquivo "main.js", é realizada uma importação estática padrão da classe do módulo "Canvas", uma instância desta classe é criada e os botões pressionados, que consistem em importar dinamicamente o módulo da classe da figura correspondente e chamar seus métodos:
//
//
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();
}
});
Você pode brincar com o código aqui .
Como você pode ver, os módulos ES6 fornecem alguns recursos bastante interessantes relacionados à divisão do código em blocos relativamente autocontidos contendo partes lógicas do aplicativo que podem ser carregadas imediatamente ou sob demanda. Quando usados em conjunto com literais de modelo, eles são uma boa alternativa para componentes de estruturas populares. Quero dizer, em primeiro lugar, renderizar páginas no lado do cliente. Além disso, essa abordagem permite que você renderize novamente apenas os elementos DOM que sofreram alterações, o que, por sua vez, elimina a necessidade de um DOM virtual. Mais sobre isso em um dos artigos seguintes.
Espero que você tenha encontrado algo interessante para você. Obrigado pela atenção.