Guten Tag. Mein Name ist Alexander und ich bin ein Vanilla ES5.1-Entwickler im Jahr 2018.
Dieser Artikel ist eine Antwort auf die Artikelantwort "So suchen Sie auf GitHub nach Benutzern ohne React + RxJS 6 + Recompose" , die uns die Verwendung von SvelteJS zeigte.
Ich schlage vor, eine der Optionen zu prüfen, wie dies implementiert werden kann, ohne Abhängigkeiten außer dem Browser zu verwenden. Darüber hinaus gab GitHub selbst an, ein Frontend ohne Frameworks zu entwickeln .
Wir werden die gleiche Eingabe machen und die GitHub-Benutzerplatte anzeigen:

HaftungsausschlussDieser Artikel ignoriert absolut alle möglichen Praktiken der modernen Javascript- und Webentwicklung.
Vorbereitung
Wir müssen keine Konfigurationen konfigurieren und schreiben, sondern index.html mit dem erforderlichen Layout erstellen:
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>
Wenn sich jemand für CSS interessiert, können Sie es im Repository sehen .
Wir haben die gängigsten Stile, keine CSS-Module und andere Bereiche. Wir markieren die Komponenten einfach mit Klassen, die mit x- beginnen, und garantieren, dass das Projekt nicht mehr vorhanden ist. Wir schreiben alle Selektoren, die sie betreffen.
Eingabefeld
Alles, was wir von unserem Eingabefeld erwarten, sind die entprellten Ereignisse seiner Änderung sowie das Ereignis, mit dem die Eingabe gestartet wurde, so dass sofort die Lastanzeige angezeigt wird. Es stellt sich so heraus:
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; }; } });
Hier haben wir einige nützliche Funktionen verwendet, wir werden sie durchgehen:
Da wir kein webpack
, kein CommonJS
, kein RequireJS
haben, webpack
wir alles mit der folgenden Funktion in Objekte ein:
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; } }); };
Die Funktion instantiateTemplate()
gibt uns eine tiefe Kopie des DOM-Elements, die von der Funktion consumeTemplates()
aus dem Element #templates
in unserer index.html
abgerufen wird.
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); }); });
Die Funktion Dom.binding()
akzeptiert ein Element, Optionen, sucht nach bestimmten Dom.binding()
und führt die Aktionen aus, die wir mit den Elementen benötigen. Beispielsweise fügt es für das data-element
dem Ergebnis ein Feld mit einem Link zum markierten Element hinzu. Für das data-onedit
Attribut werden die keyup
und change
mit dem Optionshandler an das Element gehängt.
bind.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; }); });
Nun, delay
befasst sich mit der Art der Entprellung, die wir brauchen:
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); }; }); });
Benutzerkarte
Es hat keine Logik, nur eine Vorlage, die mit Daten gefüllt ist:
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); }; } });
Natürlich ist es nicht sehr gut, jedes Mal so viele querySelectorAll
wenn wir die Daten ändern, aber es funktioniert und wir lassen es zu. Wenn sich plötzlich herausstellt, dass sich dadurch alles verlangsamt, schreiben wir die Daten in das gespeicherte data-element
. Oder wir machen eine andere Bindungsfunktion, die selbst die Elemente speichert und die neuen Daten lesen kann. Oder wir unterstützen die Übertragung von Optionen auf das Objekt, nicht nur statische Werte, sondern einen Strom ihrer Änderungen, damit die Ordner ihnen folgen können.
Anzeige von Lade- / Anforderungsfehlern
Wir gehen davon aus, dass diese Darstellungen auch statisch sind, nur an einer Stelle verwendet werden und die Wahrscheinlichkeit, dass sie ihre eigene Logik haben, extrem gering ist (im Gegensatz zur Karte des Benutzers), daher werden wir keine separaten Komponenten für sie erstellen. Sie sind nur Vorlagen für die Anwendungskomponente.
Datenanforderung
Wir werden eine Klasse mit einer Benutzeranforderungsmethode erstellen. In diesem Fall können wir ihre Instanz leicht durch eine moch / andere Implementierung ersetzen:
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; }
Natürlich brauchen wir einen Wrapper über XMLHttpRequest . Wir verwenden fetch
nicht, weil es keine Unterbrechung von Anfragen unterstützt, und wir möchten aus demselben Grund auch nicht mit Versprechungen kommunizieren.
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(){};
Endgültige Bewerbung
app.js. in_package('GitHubUsers', function() { this.provide('App', App); function App(options) { var api = options.api; var element = document.createElement('div');
Wir haben ziemlich viel Code, von denen die Hälfte in der Initialisierung aller benötigten Komponenten, die andere Hälfte in der Logik des Sendens von Anforderungen und der Anzeige des Ladevorgangs / Fehlers / Ergebnisses liegt. Aber alles ist völlig transparent, offensichtlich und wir können die Logik bei Bedarf überall ändern.
Wir haben das Dienstprogramm DisplayOneOf
, das eines der angegebenen Elemente DisplayOneOf
und den Rest verbirgt:
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 = ''; }; }); });
Damit es am Ende funktioniert, müssen wir die Vorlagen initialisieren und die App
Instanz auf der Seite ablegen:
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()); }
Ergebnis?
Wie Sie selbst sehen können, haben wir für ein so kleines Beispiel viel Code geschrieben. Niemand macht die ganze Magie für uns, wir erreichen alles selbst. Wir selbst erschaffen die Magie, die wir brauchen, wenn wir es wollen.
→ Demo → Code
Schreiben Sie einen dummen und ewigen Code. Schreiben Sie ohne Frameworks, was bedeutet, dass Sie besser schlafen und keine Angst haben, dass Ihr gesamter Code morgen veraltet oder nicht modisch genug ist.
Was weiter?
Dieses Beispiel ist zu klein, um es im Prinzip in VanillaJS zu schreiben. Ich glaube, dass das Schreiben in Vanille nur dann Sinn macht, wenn Ihr Projekt viel länger als eines der Frameworks leben soll und Sie nicht über die Ressourcen verfügen, um es vollständig neu zu schreiben.
Aber wenn er trotzdem größer wäre, würden wir Folgendes tun:
HTML-Vorlagen, die wir in Bezug auf Module / Komponenten erstellen würden. Sie befinden sich in den Ordnern mit den Komponenten, und instantiateTemplate
übernimmt den Namen des Moduls sowie den Namen der Vorlage und nicht nur den globalen Namen.
Im Moment befindet sich das gesamte CSS in index.css
müssen wir es auch neben die Komponenten stellen.
Es gibt nicht genug Zusammenstellungen von Bundles, wir verbinden alle Dateien mit unseren Händen in index.html
, das ist nicht gut.
Es ist kein Problem, ein Skript zu schreiben, das über die Liste der Module, die in den Bundles enthalten sein sollten, alle js, html und css dieser Module sammelt und uns für jedes Bundle ein js macht. Es ist viel dümmer und einfacher als das Einrichten eines Webpacks . Nach einem Jahr stellen Sie fest, dass es bereits eine völlig andere Version gibt und Sie die Konfiguration neu schreiben und andere Loader verwenden müssen.
Es ist ratsam, eine Art Flag zu haben, das das Verbindungsschema js / html / css mit einer riesigen Liste in index.html
. Dann gibt es keine Verzögerungen bei der Assembly, und in Sources in Chrome befindet sich jede Datei auf einer separaten Registerkarte, und es werden keine Quellenzuordnungen benötigt.
PS
Dies ist nur eine der Optionen, wie VanillaJS verwendet werden kann . In den Kommentaren wäre es interessant, etwas über andere Anwendungsfälle zu erfahren.
Vielen Dank für Ihre Aufmerksamkeit.