Halo Nama saya Alexander dan saya adalah pengembang Vanilla ES5.1 pada tahun 2018.
Artikel ini adalah jawaban untuk artikel-jawaban "Cara mencari pengguna di GitHub tanpa React + RxJS 6 + Komposisi Ulang " , yang menunjukkan kepada kita bagaimana menggunakan SvelteJS.
Saya sarankan melihat salah satu opsi bagaimana ini dapat diterapkan tanpa menggunakan dependensi apa pun kecuali browser. Selain itu, GitHub sendiri menyatakan bahwa mereka sedang mengembangkan frontend tanpa kerangka kerja .
Kami akan melakukan input yang sama, menampilkan pelat pengguna GitHub:

PenafianArtikel ini benar-benar mengabaikan semua kemungkinan praktik javascript dan pengembangan web modern.
Persiapan
Kami tidak perlu mengonfigurasi dan menulis konfigurasi, membuat index.html dengan semua tata letak yang diperlukan:
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>
Jika ada yang tertarik dengan CSS , Anda bisa melihatnya di repositori .
Kami memiliki gaya yang paling umum, tidak ada modul-css dan lingkup lainnya. Kami cukup menandai komponen dengan kelas yang dimulai dengan x- dan menjamin bahwa tidak akan ada lagi dalam proyek. Kami menulis penyeleksi tentang mereka.
Bidang input
Yang kami inginkan dari bidang input kami adalah peristiwa perubahan yang dilepaskan, serta peristiwa input dimulai, sehingga segera menampilkan indikasi beban. Ternyata seperti ini:
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; }; } });
Di sini kita menggunakan beberapa fungsi utilitarian, kita akan membahasnya:
Karena kami tidak memiliki paket CommonJS
, tidak ada CommonJS
, tidak ada RequireJS
, kami memasukkan semuanya ke dalam objek menggunakan fungsi berikut:
paket.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; } }); };
Fungsi instantiateTemplate()
memberi kami salinan mendalam dari elemen DOM, yang akan diperoleh oleh fungsi consumeTemplates()
dari elemen #templates
di index.html
kami.
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); }); });
Fungsi Dom.binding()
menerima elemen, opsi, mencari atribut data tertentu dan melakukan tindakan yang kita butuhkan dengan elemen. Misalnya, untuk atribut data-element
, itu menambahkan bidang ke hasil dengan tautan ke elemen yang ditandai, untuk atribut data-onedit
, data-onedit
dan penangan change
digantung pada elemen dengan penangan pilihan.
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; }); });
Nah, delay
penawaran dengan jenis debounce yang kita butuhkan:
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); }; }); });
Kartu pengguna
Tidak memiliki logika, hanya templat yang diisi dengan data:
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); }; } });
Tentu saja, melakukan begitu banyak querySelectorAll
kali kita mengubah data tidak terlalu baik, tetapi berfungsi dan kita tahan dengannya. Jika tiba-tiba ternyata semuanya melambat karena ini, kami akan menulis data ke data-element
disimpan. Atau kita akan membuat fungsi pengikatan lain, yang dengan sendirinya menyimpan elemen dan dapat membaca data baru. Atau kami akan mendukung transfer opsi ke objek tidak hanya nilai statis, aliran perubahannya sehingga pengikat dapat mengikuti mereka.
Indikasi kesalahan pemuatan / permintaan
Kami berasumsi bahwa representasi ini juga akan statis, hanya akan digunakan di satu tempat, dan kemungkinan mereka memiliki logikanya sendiri sangat kecil (tidak seperti kartu pengguna), oleh karena itu kami tidak akan membuat komponen terpisah untuk mereka. Mereka hanya akan menjadi templat untuk komponen aplikasi.
Permintaan data
Kami akan membuat kelas dengan metode permintaan pengguna, dalam hal ini kami dapat dengan mudah mengganti instansenya dengan moch / implementasi lainnya:
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; }
Tentu saja, kita membutuhkan pembungkus XMLHttpRequest . Kami tidak menggunakan fetch
karena tidak mendukung gangguan permintaan, juga tidak ingin berkomunikasi dengan janji karena alasan yang sama.
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(){};
Aplikasi akhir
app.js in_package('GitHubUsers', function() { this.provide('App', App); function App(options) { var api = options.api; var element = document.createElement('div');
Kami mendapat cukup banyak kode, setengahnya dalam inisialisasi semua komponen yang kami butuhkan, setengahnya dalam logika mengirim permintaan dan menampilkan beban / kesalahan / hasil. Tetapi semuanya benar-benar transparan, jelas dan kita dapat mengubah logika di mana saja, jika perlu.
Kami menggunakan utilitas DisplayOneOf
, yang menunjukkan salah satu elemen yang diberikan, menyembunyikan sisanya:
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 = ''; }; }); });
Untuk membuatnya berfungsi pada akhirnya, kita perlu menginisialisasi template dan drop instance App
pada halaman:
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()); }
Hasil?
Seperti yang dapat Anda lihat sendiri, kami menulis banyak kode untuk contoh kecil. Tidak ada yang melakukan semua keajaiban untuk kita, kita mencapai semuanya sendiri. Kita sendiri menciptakan keajaiban yang kita butuhkan jika kita menginginkannya.
β Demo β Kode
Tulis kode yang bodoh dan abadi. Tulis tanpa kerangka kerja, yang berarti Anda tidur lebih baik dan jangan takut bahwa besok semua kode Anda akan usang atau tidak cukup modis.
Apa selanjutnya
Contoh ini terlalu kecil untuk ditulis dalam prinsip VanillaJS . Saya percaya bahwa menulis dalam vanilla hanya masuk akal jika proyek Anda berencana untuk hidup lebih lama daripada kerangka kerja mana pun dan Anda tidak memiliki sumber daya untuk menulis ulangnya secara keseluruhan.
Tetapi jika dia lebih besar, inilah yang akan kami lakukan:
Template HTML yang akan kita lakukan mengenai modul / komponen. Mereka akan berada di folder dengan komponen dan instantiateTemplate
akan mengambil nama modul plus nama templat, dan bukan hanya nama global.
Saat ini, semua CSS yang kita miliki ada di index.css
, jelas, kita juga perlu meletakkannya di sebelah komponen.
Tidak ada cukup kumpulan bundel, kami menghubungkan semua file dengan tangan kami di index.html
, ini tidak baik.
Tidak ada masalah menulis skrip yang, melalui daftar modul yang harus dimasukkan dalam bundel, akan mengumpulkan semua js, html, css dari modul ini dan menjadikan kita satu js untuk setiap bundel. Ini akan menjadi jauh lebih sulit dan lebih mudah daripada mengatur webpack , dan setelah satu tahun mengetahui bahwa sudah ada versi yang sama sekali berbeda dan Anda perlu menulis ulang konfigurasi dan menggunakan loader lainnya.
Dianjurkan untuk memiliki semacam flag yang mendukung skema koneksi js / html / css dengan daftar besar di index.html
. Maka tidak akan ada penundaan dalam perakitan, dan di Sumber di chrome Anda akan memiliki setiap file di tab yang terpisah dan tidak ada sourcemaps yang diperlukan.
PS
Ini hanyalah salah satu opsi bagaimana semuanya bisa menggunakan VanillaJS . Dalam komentar, akan menarik untuk mendengar tentang kasus penggunaan lainnya.
Terima kasih atas perhatian anda