Bonjour Je m'appelle Alexander et je suis un développeur Vanilla ES5.1 en 2018.
Cet article est une réponse à l'article-réponse "Comment rechercher des utilisateurs sur GitHub sans React + RxJS 6 + Recompose" , qui nous a montré comment utiliser SvelteJS.
Je suggĂšre de regarder l'une des options pour savoir comment cela peut ĂȘtre implĂ©mentĂ© sans utiliser de dĂ©pendances Ă l'exception du navigateur. De plus, GitHub lui-mĂȘme a dĂ©clarĂ© qu'il dĂ©veloppait un frontend sans frameworks .
Nous ferons la mĂȘme entrĂ©e qui affiche la plaque utilisateur GitHub:

Clause de non-responsabilitéCet article ignore absolument toutes les pratiques possibles du javascript moderne et du développement web.
La préparation
Nous n'avons pas besoin de configurer et d'écrire des configs, de créer index.html avec toute la mise en page nécessaire:
index.html<!doctype html> <html> <head> <meta charset='utf-8'> <title>GitHub users</title> <link rel='stylesheet' type='text/css' href='index.css'> </head> <body> <div id='root'></div> <div id='templates' style='display:none;'> <div data-template-id='username_input'> <input type='text' data-onedit='onNameEdit' placeholder='GitHub username'> </div> <div data-template-id='usercard' class='x-user-card'> <div class='background'></div> <div class='avatar-container'> <a class='avatar' data-href='userUrl'> <img data-src='avatarImageUrl'> </a> </div> <div class='name' data-text='userName'></div> <div class='content'> <a class='block' data-href='reposUrl'> <b data-text='reposCount'></b> <span>Repos</span> </a> <a class='block' data-href='gistsUrl'> <b data-text='gistsCount'></b> <span>Gists</span> </a> <a class='block' data-href='followersUrl'> <b data-text='followersCount'></b> <span>Followers</span> </a> </div> </div> <div data-template-id='error'><b data-text='status'></b>: <span data-text='text'></span></div> <div data-template-id='loading'>Loading...</div> </div> </body> </html>
Si quelqu'un s'intéresse au CSS , vous pouvez le voir dans le référentiel .
Nous avons les styles les plus courants, pas de modules css et autres oscilloscopes. Nous marquons simplement les composants avec des classes commençant par x- et garantissons qu'il n'y en aura plus dans le projet. Nous écrivons tous les sélecteurs les concernant.
Champ de saisie
Tout ce que nous voulons de notre champ de saisie, ce sont les événements rebondis de son changement, ainsi que l'événement que la saisie a commencé, afin qu'il affiche immédiatement l'indication de charge. Il se présente comme ceci:
in_package('GitHubUsers', function() { this.provide('UserNameInput', UserNameInput); function UserNameInput(options) { var onNameInput = options.onNameInput, onNameChange = options.onNameChange; var element = GitHubUsers.Dom.instantiateTemplate('username_input'); var debouncedChange = GitHubUsers.Util.delay(1000, function() { onNameChange(this.value); }); GitHubUsers.Dom.binding(element, { onNameEdit: function() { onNameInput(this.value); debouncedChange.apply(this, arguments); } }); this.getElement = function() { return element; }; } });
Ici, nous avons utilisé quelques fonctions utilitaires, nous allons les parcourir:
Comme nous n'avons pas de webpack
, pas de CommonJS
, pas de RequireJS
, nous mettons tout en objets en utilisant la fonction suivante:
packages.js window.in_package = function(path, fun) { path = path.split('.'); var obj = path.reduce(function(acc, p) { var o = acc[p]; if (!o) { o = {}; acc[p] = o; } return o; }, window); fun.call({ provide: function(name, value) { obj[name] = value; } }); };
La fonction consumeTemplates()
nous donne une copie consumeTemplates()
de l'élément DOM, qui sera obtenue par la fonction consumeTemplates()
partir de l'élément #templates
dans notre index.html
.
templates.js in_package('GitHubUsers.Dom', function() { var templatesMap = new Map(); this.provide('consumeTemplates', function(containerEl) { var templates = containerEl.querySelectorAll('[data-template-id]'); for (var i = 0; i < templates.length; i++) { var templateEl = templates[i], templateId = templateEl.getAttribute('data-template-id'); templatesMap.set(templateId, templateEl); templateEl.parentNode.removeChild(templateEl); } if (containerEl.parentNode) containerEl.parentNode.removeChild(containerEl); }); this.provide('instantiateTemplate', function(templateId) { var templateEl = templatesMap.get(templateId); return templateEl.cloneNode(true); }); });
La fonction Dom.binding()
accepte un élément, des options, recherche des attributs de données spécifiques et effectue les actions dont nous avons besoin avec les éléments. Par exemple, pour l'attribut d' data-element
, il ajoute un champ au résultat avec un lien vers l'élément marqué, pour l'attribut data-onedit
, les keyup
et de change
sont suspendus à l'élément avec le gestionnaire d'options.
binding.js in_package('GitHubUsers.Dom', function() { this.provide('binding', function(element, options) { options = options || {}; var binding = {}; handleAttribute('data-element', function(el, name) { binding[name] = el; }); handleAttribute('data-text', function(el, key) { var text = options[key]; if (typeof text !== 'string' && typeof text !== 'number') return; el.innerText = text; }); handleAttribute('data-src', function(el, key) { var src = options[key]; if (typeof src !== 'string') return; el.src = src; }); handleAttribute('data-href', function(el, key) { var href = options[key]; if (typeof href !== 'string') return; el.href = href; }); handleAttribute('data-onedit', function(el, key) { var handler = options[key]; if (typeof handler !== 'function') return; el.addEventListener('keyup', handler); el.addEventListener('change', handler); }); function handleAttribute(attribute, fun) { var elements = element.querySelectorAll('[' + attribute + ']'); for (var i = 0; i < elements.length; i++) { var el = elements[i], attributeValue = el.getAttribute(attribute); fun(el, attributeValue); } } return binding; }); });
Eh bien, le delay
concerne le type de rebond dont nous avons besoin:
debounce.js in_package('GitHubUsers.Util', function() { this.provide('delay', function(timeout, fun) { var timeoutId = 0; return function() { var that = this, args = arguments; if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(function() { timeoutId = 0; fun.apply(that, args); }, timeout); }; }); });
Carte d'utilisateur
Il n'a pas de logique, seulement un modÚle rempli de données:
in_package('GitHubUsers', function() { this.provide('UserCard', UserCard); function UserCard() { var element = GitHubUsers.Dom.instantiateTemplate('usercard'); this.getElement = function() { return element; }; this.setData = function(data) { GitHubUsers.Dom.binding(element, data); }; } });
Bien sûr, faire autant de querySelectorAll
chaque fois que nous modifions les données n'est pas trÚs bon, mais cela fonctionne et nous les acceptons. S'il s'avÚre soudainement que tout ralentit à cause de cela, nous écrirons les données dans l' data-element
stockĂ©. Ou nous allons crĂ©er une autre fonction de liaison, qui enregistre elle-mĂȘme les Ă©lĂ©ments et peut lire les nouvelles donnĂ©es. Ou nous prendrons en charge le transfert d'options Ă l'objet de valeurs non seulement statiques, un flux de leurs modifications afin que les classeurs puissent les suivre.
Indication d'erreurs de chargement / demande
Nous supposons que ces reprĂ©sentations seront Ă©galement statiques, ne seront utilisĂ©es quâen un seul endroit, et les chances quâelles aient leur propre logique sont extrĂȘmement faibles (contrairement Ă la carte de lâutilisateur), par consĂ©quent, nous ne ferons pas de composants sĂ©parĂ©s pour elles. Ce ne seront que des modĂšles pour le composant d'application.
Demande de données
Nous allons crĂ©er une classe avec une mĂ©thode de requĂȘte utilisateur, auquel cas nous pouvons facilement remplacer son instance par une implĂ©mentation moch / other:
in_package('GitHubUsers', function() { this.provide('GitHubApi', GitHubApi); function GitHubApi() { this.getUser = function(options, callback) { var url = 'https://api.github.com/users/' + options.userName; return GitHubUsers.Http.doRequest(url, function(error, data) { if (error) { if (error.type === 'not200') { if (error.status === 404) callback(null, null); else callback({ status: error.status, message: data && data.message }); } else { callback(error); } return; }
Bien sûr, nous avons besoin d'un wrapper sur XMLHttpRequest . Nous n'utilisons pas la fetch
car elle ne prend pas en charge l'interruption des demandes, et nous ne voulons pas non plus communiquer avec des promesses pour la mĂȘme raison.
ajax.js in_package('GitHubUsers.Http', function() { this.provide('doRequest', function(options, callback) { var url; if (typeof options === "string") { url = options; options = {}; } else { if (!options) options = {}; url = options.url; } var method = options.method || "GET", headers = options.headers || [], body = options.body, dataType = options.dataType || "json", timeout = options.timeout || 10000; var old_callback = callback; callback = function() { callback = function(){};
Application finale
app.js in_package('GitHubUsers', function() { this.provide('App', App); function App(options) { var api = options.api; var element = document.createElement('div');
Nous avons obtenu beaucoup de code, dont la moitiĂ© dans l'initialisation de tous les composants dont nous avons besoin, la moitiĂ© dans la logique d'envoi des requĂȘtes et d'affichage de la charge / erreur / rĂ©sultat. Mais tout est complĂštement transparent, Ă©vident et nous pouvons changer la logique n'importe oĂč, si nĂ©cessaire.
Nous avons utilisé l'utilitaire DisplayOneOf
, qui montre l'un des éléments donnés, masque le reste:
dom-util.js in_package('GitHubUsers.DomUtil', function() { this.provide('DisplayOneOf', function(options) { var items = options.items; var obj = {}; items.forEach(function(item) { obj[item.type] = item; }); var lastDisplayed = null; this.showByType = function(type) { if (lastDisplayed) { lastDisplayed.element.style.display = 'none'; } if (!type) { lastDisplayed = null; return; } lastDisplayed = obj[type]; lastDisplayed.element.style.display = ''; }; }); });
Pour le faire fonctionner à la fin, nous devons initialiser les modÚles et déposer l'instance d' App
sur la page:
function onReady() { GitHubUsers.Dom.consumeTemplates(document.getElementById('templates')); var rootEl = document.getElementById('root'); var app = new GitHubUsers.App({ api: new GitHubUsers.GitHubApi() }); rootEl.appendChild(app.getElement()); }
Résultat?
Comme vous pouvez le constater par vous-mĂȘme, nous avons Ă©crit beaucoup de code pour un si petit exemple. Personne ne fait toute la magie pour nous, nous rĂ©alisons tout nous-mĂȘmes. Nous crĂ©ons nous-mĂȘmes la magie dont nous avons besoin si nous le voulons.
â DĂ©mo â Code
Ăcrivez un code stupide et Ă©ternel. Ăcrivez sans framework, ce qui signifie que vous dormez mieux et nâayez pas peur que demain tout votre code soit obsolĂšte ou pas assez Ă la mode.
Et ensuite?
Cet exemple est trop petit pour l'écrire dans VanillaJS en principe. Je pense qu'écrire en vanille n'a de sens que si votre projet prévoit de vivre beaucoup plus longtemps que n'importe lequel des frameworks et que vous n'avez pas les ressources pour le réécrire dans son intégralité.
Mais s'il était plus gros de toute façon, voici ce que nous ferions:
ModĂšles HTML que nous ferions concernant les modules / composants. Ils seraient dans les dossiers avec les composants et instantiateTemplate
prendrait le nom du module plus le nom du modĂšle, et pas seulement le nom global.
Pour le moment, tout le CSS que nous avons est dans index.css
, évidemment, nous devons également le mettre à cÎté des composants.
Il n'y a pas assez d'assemblage de bundles, nous connectons tous les fichiers avec nos mains dans index.html
, ce n'est pas bon.
Il n'y a aucun problĂšme Ă Ă©crire un script qui, Ă travers les listes de modules qui devraient ĂȘtre inclus dans les bundles, collectera tous les js, html, css de ces modules et nous fera un js pour chaque bundle. Ce sera beaucoup plus stupide et plus facile que de configurer un webpack , et aprĂšs un an, dĂ©couvrez qu'il existe dĂ©jĂ une version complĂštement diffĂ©rente et vous devez réécrire la configuration et utiliser d'autres chargeurs.
Il est conseillé d'avoir une sorte d'indicateur qui prend en charge le schéma de connexion js / html / css avec une énorme liste dans index.html
. Ensuite, il n'y aura pas de retards dans l'assemblage, et dans Sources en chrome, vous aurez chaque fichier dans un onglet séparé et aucun sourcemaps n'est nécessaire.
PS
Ceci n'est qu'une des options sur la façon dont tout cela peut ĂȘtre utilisĂ© avec VanillaJS . Dans les commentaires, il serait intĂ©ressant d'entendre parler d'autres cas d'utilisation.
Merci de votre attention.