Merancang Dan Membangun Aplikasi Web Progresif Tanpa Kerangka Kerja (Bagian 2)
Diterbitkan: 2022-03-10Alasan utama dari petualangan ini adalah untuk mendorong penulis Anda yang rendah hati sedikit dalam disiplin desain visual dan pengkodean JavaScript. Fungsionalitas aplikasi yang saya putuskan untuk dibangun tidak berbeda dengan aplikasi 'yang harus dilakukan'. Penting untuk ditekankan bahwa ini bukanlah latihan untuk berpikir orisinal. Tujuan jauh lebih penting daripada perjalanan.
Ingin tahu bagaimana aplikasi itu berakhir? Arahkan browser ponsel Anda ke https://io.benfrain.com.
Berikut adalah ringkasan dari apa yang akan kita bahas dalam artikel ini:
- Pengaturan proyek dan alasan saya memilih Gulp sebagai alat pembuatan;
- Pola desain aplikasi dan artinya dalam praktik;
- Cara menyimpan dan memvisualisasikan status aplikasi;
- bagaimana CSS dicakup ke komponen;
- keramahan UI/UX apa yang digunakan untuk membuat segalanya lebih 'seperti aplikasi';
- Bagaimana pengiriman berubah melalui iterasi.
Mari kita mulai dengan alat pembuatan.
Bangun Alat
Untuk menjalankan dan menjalankan peralatan dasar TypeScipt dan PostCSS dan menciptakan pengalaman pengembangan yang layak, saya memerlukan sistem pembangunan.
Dalam pekerjaan sehari-hari saya, selama lima tahun terakhir ini, saya telah membangun prototipe antarmuka dalam HTML/CSS dan pada tingkat lebih rendah, JavaScript. Sampai saat ini, saya telah menggunakan Gulp dengan sejumlah plugin hampir secara eksklusif untuk mencapai kebutuhan build saya yang cukup sederhana.
Biasanya saya perlu memproses CSS, mengonversi JavaScript atau TypeScript ke JavaScript yang lebih banyak didukung, dan terkadang, melakukan tugas terkait seperti memperkecil keluaran kode dan mengoptimalkan aset. Menggunakan Gulp selalu memungkinkan saya untuk menyelesaikan masalah tersebut dengan penuh percaya diri.
Bagi mereka yang tidak terbiasa, Gulp memungkinkan Anda menulis JavaScript untuk melakukan 'sesuatu' ke file di sistem file lokal Anda. Untuk menggunakan Gulp, Anda biasanya memiliki satu file (disebut gulpfile.js
) di root proyek Anda. File JavaScript ini memungkinkan Anda untuk mendefinisikan tugas sebagai fungsi. Anda dapat menambahkan 'Plugin' pihak ketiga, yang pada dasarnya adalah fungsi JavaScript lebih lanjut, yang menangani tugas-tugas tertentu.
Contoh Tugas Gulp
Contoh tugas Gulp mungkin menggunakan plugin untuk memanfaatkan PostCSS untuk diproses ke CSS saat Anda mengubah lembar gaya penulisan (gulp-postcss). Atau kompilasi file TypeScript ke vanilla JavaScript (teguk-typescript) saat Anda menyimpannya. Berikut adalah contoh sederhana bagaimana Anda menulis tugas di Gulp. Tugas ini menggunakan plugin 'del' gulp untuk menghapus semua file dalam folder bernama 'build':
var del = require("del"); gulp.task("clean", function() { return del(["build/**/*"]); });
require
menugaskan plugin del
ke variabel. Kemudian metode gulp.task
dipanggil. Kami menamai tugas dengan string sebagai argumen pertama ("bersih") dan kemudian menjalankan fungsi, yang dalam hal ini menggunakan metode 'del' untuk menghapus folder yang diteruskan ke sana sebagai argumen. Simbol asterisk ada pola 'glob' yang pada dasarnya mengatakan 'file apa saja di folder apa pun' dari folder build.
Tugas tegukan bisa menjadi lebih rumit tetapi pada dasarnya, itulah mekanisme bagaimana hal-hal ditangani. Yang benar adalah, dengan Gulp, Anda tidak perlu menjadi ahli JavaScript untuk bertahan; keterampilan salin dan tempel kelas 3 adalah semua yang Anda butuhkan.
Saya terjebak dengan Gulp sebagai alat bawaan/pelari tugas selama bertahun-tahun dengan kebijakan 'jika tidak rusak; jangan mencoba dan memperbaikinya'.
Namun, saya khawatir saya terjebak di jalan saya. Ini adalah jebakan yang mudah untuk jatuh. Pertama, Anda mulai berlibur di tempat yang sama setiap tahun, kemudian menolak untuk mengadopsi tren mode baru sebelum akhirnya dan dengan teguh menolak untuk mencoba alat pembuatan baru.
Saya telah mendengar banyak obrolan di Internet tentang 'Webpack' dan berpikir itu adalah tugas saya untuk mencoba sebuah proyek menggunakan roti panggang model baru dari pengembang front-end anak-anak keren.
paket web
Saya ingat dengan jelas melompat ke situs webpack.js.org dengan penuh minat. Penjelasan pertama tentang apa itu Webpack dan apa yang dilakukan dimulai seperti ini:
import bar from './bar';
Katakan apa? Dalam kata-kata Dr. Evil, "Lemparkan aku tulang sialan di sini, Scott".
Saya tahu ini adalah masalah saya sendiri yang harus dihadapi, tetapi saya telah mengembangkan penolakan terhadap penjelasan pengkodean apa pun yang menyebutkan 'foo', 'bar' atau 'baz'. Itu ditambah kurangnya deskripsi singkat untuk apa sebenarnya Webpack itu membuat saya curiga itu mungkin bukan untuk saya.
Menggali sedikit lebih jauh ke dalam dokumentasi Webpack, penjelasan yang sedikit kurang buram ditawarkan, "Pada intinya, webpack adalah bundler modul statis untuk aplikasi JavaScript modern".
Hmmm. Bundel modul statis. Apakah itu yang saya inginkan? Saya tidak yakin. Saya terus membaca tetapi semakin saya membaca, semakin tidak jelas saya. Saat itu, konsep seperti grafik ketergantungan, memuat ulang modul panas, dan titik masuk pada dasarnya hilang pada saya.
Beberapa malam setelah meneliti Webpack, saya mengabaikan gagasan untuk menggunakannya.
Saya yakin dalam situasi yang tepat dan tangan yang lebih berpengalaman, Webpack sangat kuat dan sesuai, tetapi sepertinya terlalu berlebihan untuk kebutuhan saya yang sederhana. Penggabungan modul, pengguncangan pohon, dan pemuatan ulang modul panas terdengar hebat; Saya hanya tidak yakin saya membutuhkannya untuk 'aplikasi' kecil saya.
Jadi, kembali ke Gulp.
Dengan tema tidak mengubah sesuatu demi perubahan, teknologi lain yang ingin saya evaluasi adalah Yarn over NPM untuk mengelola dependensi proyek. Sampai saat itu, saya selalu menggunakan NPM dan Benang disebut-sebut sebagai alternatif yang lebih baik dan lebih cepat. Tidak banyak yang bisa saya katakan tentang Yarn selain jika Anda saat ini menggunakan NPM dan semuanya baik-baik saja, Anda tidak perlu repot mencoba Yarn.
Salah satu alat yang datang terlambat bagi saya untuk menilai aplikasi ini adalah Parceljs. Dengan konfigurasi nol dan BrowserSync seperti browser yang memuat ulang, saya telah menemukan utilitas hebat di dalamnya! Selain itu, dalam pertahanan Webpack, saya diberitahu bahwa v4 dan seterusnya dari Webpack tidak memerlukan file konfigurasi. Secara anekdot, dalam jajak pendapat yang lebih baru yang saya jalankan di Twitter, dari 87 responden, lebih dari setengahnya memilih Webpack daripada Gulp, Parcel atau Grunt.
Saya memulai file Gulp saya dengan fungsionalitas dasar untuk bangun dan berjalan.
Tugas 'default' akan melihat folder 'sumber' dari lembar gaya dan file TypeScript dan mengompilasinya ke folder build
bersama dengan HTML dasar dan peta sumber terkait.
Saya juga membuat BrowserSync bekerja dengan Gulp. Saya mungkin tidak tahu apa yang harus dilakukan dengan file konfigurasi Webpack tetapi itu tidak berarti saya semacam binatang. Harus me-refresh browser secara manual saat iterasi dengan HTML/CSS sangat bagus 2010 dan BrowserSync memberi Anda umpan balik singkat dan loop iterasi yang sangat berguna untuk pengkodean front-end.
Berikut adalah file tegukan dasar pada 11.6.2017
Anda dapat melihat bagaimana saya mengubah Gulpfile mendekati akhir pengiriman, menambahkan minifikasi dengan ugilify:
Struktur Proyek
Sebagai konsekuensi dari pilihan teknologi saya, beberapa elemen organisasi kode untuk aplikasi tersebut mendefinisikan diri mereka sendiri. Sebuah gulpfile.js
di root proyek, folder node_modules
(tempat Gulp menyimpan kode plugin), folder preCSS
untuk lembar gaya authoring, folder ts
untuk file TypeScript, dan folder build
untuk kode yang dikompilasi untuk hidup.
Idenya adalah untuk memiliki index.html
yang berisi 'shell' dari aplikasi, termasuk struktur HTML non-dinamis dan kemudian link ke gaya dan file JavaScript yang akan membuat aplikasi bekerja. Pada disk, itu akan terlihat seperti ini:
build/ node_modules/ preCSS/ img/ partials/ styles.css ts/ .gitignore gulpfile.js index.html package.json tsconfig.json
Mengonfigurasi BrowserSync untuk melihat folder build
itu berarti saya dapat mengarahkan browser saya ke localhost:3000
dan semuanya baik-baik saja.
Dengan sistem build dasar, organisasi file diselesaikan, dan beberapa desain dasar untuk memulai, saya kehabisan makanan penundaan yang dapat saya gunakan secara sah untuk mencegah saya benar-benar membangunnya!
Menulis Aplikasi
Prinsip bagaimana aplikasi akan bekerja adalah ini. Akan ada penyimpanan data. Ketika JavaScript dimuat, itu akan memuat data itu, mengulang setiap pemain dalam data, membuat HTML yang diperlukan untuk mewakili setiap pemain sebagai baris dalam tata letak dan menempatkannya di bagian masuk/keluar yang sesuai. Kemudian interaksi dari pengguna akan memindahkan pemain dari satu keadaan ke keadaan lain. Sederhana.
Ketika benar-benar menulis aplikasi, dua tantangan konseptual besar yang perlu dipahami adalah:
- Bagaimana merepresentasikan data untuk aplikasi dengan cara yang dapat dengan mudah diperluas dan dimanipulasi;
- Bagaimana membuat UI bereaksi ketika data diubah dari input pengguna.
Salah satu cara paling sederhana untuk merepresentasikan struktur data dalam JavaScript adalah dengan notasi objek. Kalimat itu berbunyi sedikit ilmu komputer-y. Lebih sederhananya, 'objek' dalam istilah JavaScript adalah cara praktis untuk menyimpan data.
Pertimbangkan objek JavaScript ini yang ditetapkan ke variabel yang disebut ioState
(untuk Status Masuk/Keluar):
var ioState = { Count: 0, // Running total of how many players RosterCount: 0; // Total number of possible players ToolsExposed: false, // Whether the UI for the tools is showing Players: [], // A holder for the players }
Jika Anda tidak begitu mengetahui JavaScript dengan baik, Anda mungkin setidaknya dapat memahami apa yang terjadi: setiap baris di dalam kurung kurawal adalah properti (atau 'kunci' dalam bahasa JavaScript) dan pasangan nilai. Anda dapat mengatur segala macam hal ke kunci JavaScript. Misalnya, fungsi, larik data lain, atau objek bersarang. Berikut ini contohnya:
var testObject = { testFunction: function() { return "sausages"; }, testArray: [3,7,9], nestedtObject { key1: "value1", key2: 2, } }
Hasil akhirnya adalah bahwa dengan menggunakan struktur data semacam itu Anda bisa mendapatkan, dan mengatur, salah satu kunci objek. Misalnya, jika kita ingin mengatur jumlah objek ioState ke 7:
ioState.Count = 7;
Jika kita ingin menyetel sepotong teks ke nilai itu, notasinya berfungsi seperti ini:
aTextNode.textContent = ioState.Count;
Anda dapat melihat bahwa mendapatkan nilai dan menyetel nilai ke objek status itu sederhana di sisi JavaScript. Namun, mencerminkan perubahan dalam Antarmuka Pengguna kurang begitu. Ini adalah area utama di mana kerangka kerja dan perpustakaan berusaha untuk menghilangkan rasa sakit.
Secara umum, ketika berurusan dengan memperbarui antarmuka pengguna berdasarkan status, lebih baik untuk menghindari kueri DOM, karena ini umumnya dianggap sebagai pendekatan yang kurang optimal.
Pertimbangkan antarmuka Masuk/Keluar. Biasanya menampilkan daftar pemain potensial untuk sebuah game. Mereka terdaftar secara vertikal, satu di bawah yang lain, di bawah halaman.
Mungkin setiap pemain diwakili dalam DOM dengan label
yang membungkus input
kotak centang . Dengan cara ini, mengklik pemain akan mengalihkan pemain ke 'Masuk' berdasarkan label yang membuat input 'dicentang'.
Untuk memperbarui antarmuka kami, kami mungkin memiliki 'pendengar' pada setiap elemen input dalam JavaScript. Pada klik atau perubahan, fungsi menanyakan DOM dan menghitung berapa banyak input pemain kami yang diperiksa. Berdasarkan hitungan itu, kami kemudian akan memperbarui sesuatu yang lain di DOM untuk menunjukkan kepada pengguna berapa banyak pemain yang diperiksa.
Mari kita pertimbangkan biaya operasi dasar itu. Kami mendengarkan pada beberapa node DOM untuk klik/pemeriksaan input, kemudian menanyakan DOM untuk melihat berapa banyak dari jenis DOM tertentu yang diperiksa, kemudian menulis sesuatu ke dalam DOM untuk menunjukkan kepada pengguna, UI bijaksana, jumlah pemain kami hanya menghitung.
Alternatifnya adalah menahan status aplikasi sebagai objek JavaScript di memori. Klik tombol/input di DOM hanya dapat memperbarui objek JavaScript dan kemudian, berdasarkan perubahan pada objek JavaScript itu, lakukan pembaruan sekali jalan dari semua perubahan antarmuka yang diperlukan. Kita bisa melewatkan kueri DOM untuk menghitung pemain karena objek JavaScript sudah menyimpan informasi itu.
Jadi. Menggunakan struktur objek JavaScript untuk status tampak sederhana namun cukup fleksibel untuk merangkum status aplikasi pada waktu tertentu. Teori tentang bagaimana hal ini dapat dikelola tampaknya cukup masuk akal juga – ini pasti ungkapan seperti 'aliran data satu arah'? Namun, trik nyata pertama adalah membuat beberapa kode yang secara otomatis memperbarui UI berdasarkan perubahan apa pun pada data tersebut.
Kabar baiknya adalah bahwa orang-orang yang lebih pintar dari saya telah mengetahui hal ini ( syukurlah! ). Orang-orang telah menyempurnakan pendekatan untuk tantangan semacam ini sejak awal aplikasi. Kategori masalah ini adalah roti dan mentega dari 'pola desain'. 'Pola desain' moniker terdengar esoteris bagi saya pada awalnya tetapi setelah menggali sedikit semuanya mulai terdengar kurang ilmu komputer dan lebih masuk akal.
Pola desain
Pola desain, dalam leksikon ilmu komputer, adalah cara yang telah ditentukan dan terbukti untuk memecahkan tantangan teknis umum. Pikirkan pola desain sebagai coding yang setara dengan resep memasak.
Mungkin literatur paling terkenal tentang pola desain adalah "Pola Desain: Elemen Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali" dari tahun 1994. Meskipun itu berhubungan dengan C++ dan obrolan ringan, konsepnya dapat ditransfer. Untuk JavaScript, "Pola Desain JavaScript Pembelajaran" Addy Osmani mencakup dasar yang sama. Anda juga dapat membacanya secara online secara gratis di sini.
Pola Pengamat
Biasanya pola desain dibagi menjadi tiga kelompok: Penciptaan, Struktural, dan Perilaku. Saya mencari sesuatu Behavioral yang membantu menangani perubahan komunikasi di sekitar berbagai bagian aplikasi.
Baru-baru ini, saya telah melihat dan membaca pembahasan mendalam tentang penerapan reaktivitas di dalam aplikasi oleh Gregg Pollack. Ada posting blog dan video untuk kesenangan Anda di sini.
Saat membaca deskripsi pembukaan pola 'Pengamat' dalam Learning JavaScript Design Patterns
, saya cukup yakin bahwa itu adalah pola untuk saya. Digambarkan demikian:
Pengamat adalah pola desain di mana suatu objek (dikenal sebagai subjek) menyimpan daftar objek yang bergantung padanya (pengamat), secara otomatis memberi tahu mereka tentang setiap perubahan status.
Ketika subjek perlu memberi tahu pengamat tentang sesuatu yang menarik terjadi, ia menyiarkan pemberitahuan kepada pengamat (yang dapat mencakup data spesifik yang terkait dengan topik pemberitahuan).
Kunci kegembiraan saya adalah bahwa ini tampaknya menawarkan beberapa cara untuk memperbarui diri mereka sendiri saat dibutuhkan.
Misalkan pengguna mengklik pemain bernama “Betty” untuk memilih bahwa dia 'Masuk' untuk game tersebut. Beberapa hal mungkin perlu terjadi di UI:
- Tambahkan 1 ke hitungan bermain
- Hapus Betty dari kumpulan pemain 'Keluar'
- Tambahkan Betty ke kumpulan pemain 'Dalam'
Aplikasi juga perlu memperbarui data yang mewakili UI. Yang sangat ingin saya hindari adalah ini:
playerName.addEventListener("click", playerToggle); function playerToggle() { if (inPlayers.includes(e.target.textContent)) { setPlayerOut(e.target.textContent); decrementPlayerCount(); } else { setPlayerIn(e.target.textContent); incrementPlayerCount(); } }
Tujuannya adalah untuk memiliki aliran data yang elegan yang memperbarui apa yang dibutuhkan di DOM ketika dan jika data pusat diubah.
Dengan pola Pengamat, dimungkinkan untuk mengirim pembaruan ke status dan oleh karena itu antarmuka pengguna cukup ringkas. Berikut adalah contoh, fungsi aktual yang digunakan untuk menambahkan pemain baru ke dalam daftar:
function itemAdd(itemString: string) { let currentDataSet = getCurrentDataSet(); var newPerson = new makePerson(itemString); io.items[currentDataSet].EventData.splice(0, 0, newPerson); io.notify({ items: io.items }); }
Bagian yang relevan dengan pola Observer adalah metode io.notify
. Karena itu menunjukkan kami memodifikasi items
bagian dari status aplikasi, izinkan saya menunjukkan kepada Anda pengamat yang mendengarkan perubahan pada 'item':
io.addObserver({ props: ["items"], callback: function renderItems() { // Code that updates anything to do with items... } });
Kami memiliki metode pemberitahuan yang membuat perubahan pada data dan kemudian Pengamat pada data tersebut yang merespons saat properti yang mereka minati diperbarui.
Dengan pendekatan ini, aplikasi dapat memiliki observable yang mengawasi perubahan pada properti data apa pun dan menjalankan fungsi setiap kali terjadi perubahan.
Jika Anda tertarik dengan pola Pengamat yang saya pilih, saya jelaskan lebih lengkap di sini.
Sekarang ada pendekatan untuk memperbarui UI secara efektif berdasarkan status. Sangat bagus. Namun, ini masih meninggalkan saya dengan dua masalah mencolok.
Salah satunya adalah cara menyimpan status di seluruh pemuatan ulang/sesi halaman dan fakta bahwa meskipun UI berfungsi, secara visual, itu tidak terlalu 'seperti aplikasi'. Misalnya, jika tombol ditekan, UI langsung berubah di layar. Itu tidak terlalu menarik.
Mari kita berurusan dengan sisi penyimpanan terlebih dahulu.
Menyimpan Negara
Minat utama saya dari sisi pengembangan yang masuk ke dalam ini berpusat pada pemahaman bagaimana antarmuka aplikasi dapat dibangun dan dibuat interaktif dengan JavaScript. Cara menyimpan dan mengambil data dari server atau menangani otentikasi pengguna dan login adalah 'di luar jangkauan'.
Oleh karena itu, alih-alih terhubung ke layanan web untuk kebutuhan penyimpanan data, saya memilih untuk menyimpan semua data di klien. Ada sejumlah metode platform web untuk menyimpan data pada klien. Saya memilih localStorage
.
API untuk localStorage sangat sederhana. Anda mengatur dan mendapatkan data seperti ini:
// Set something localStorage.setItem("yourKey", "yourValue"); // Get something localStorage.getItem("yourKey");
LocalStorage memiliki metode setItem
tempat Anda meneruskan dua string. Yang pertama adalah nama kunci yang ingin Anda simpan datanya dan string kedua adalah string sebenarnya yang ingin Anda simpan. Metode getItem
mengambil string sebagai argumen yang mengembalikan kepada Anda apa pun yang disimpan di bawah kunci itu di localStorage. Bagus dan sederhana.
Namun, di antara alasan untuk tidak menggunakan localStorage adalah kenyataan bahwa semuanya harus disimpan sebagai 'string'. Ini berarti Anda tidak dapat langsung menyimpan sesuatu seperti array atau objek. Misalnya, coba jalankan perintah ini di konsol browser Anda:
// Set something localStorage.setItem("myArray", [1, 2, 3, 4]); // Get something localStorage.getItem("myArray"); // Logs "1,2,3,4"
Meskipun kami mencoba menetapkan nilai 'myArray' sebagai array; ketika kami mengambilnya, itu telah disimpan sebagai string (perhatikan tanda kutip di sekitar '1,2,3,4').
Anda tentu saja dapat menyimpan objek dan array dengan localStorage tetapi Anda harus berhati-hati bahwa mereka perlu mengonversi bolak-balik dari string.
Jadi, untuk menulis data status ke localStorage, itu ditulis ke string dengan metode JSON.stringify()
seperti ini:
const storage = window.localStorage; storage.setItem("players", JSON.stringify(io.items));
Saat data perlu diambil dari localStorage, string diubah kembali menjadi data yang dapat digunakan dengan metode JSON.parse()
seperti ini:
const players = JSON.parse(storage.getItem("players"));
Menggunakan localStorage
berarti semuanya ada di klien dan itu berarti tidak ada layanan pihak ketiga atau masalah penyimpanan data.
Data sekarang terus diperbarui dan sesi — Yay! Berita buruknya adalah localStorage tidak dapat bertahan jika pengguna mengosongkan data browser mereka. Ketika seseorang melakukan itu, semua data Masuk/Keluar mereka akan hilang. Itu kekurangan yang serius.
Tidak sulit untuk menyadari bahwa `localStorage` mungkin bukan solusi terbaik untuk aplikasi yang 'tepat'. Selain masalah string yang disebutkan di atas, ini juga lambat untuk pekerjaan serius karena memblokir 'utas utama'. Alternatif akan datang, seperti Penyimpanan KV tetapi untuk saat ini, buat catatan mental untuk memperingatkan penggunaannya berdasarkan kesesuaian.
Meskipun rapuhnya penyimpanan data secara lokal pada perangkat pengguna, menghubungkan ke layanan atau database ditolak. Alih-alih, masalah ini diabaikan dengan menawarkan opsi 'muat/simpan'. Ini akan memungkinkan pengguna Masuk/Keluar untuk menyimpan data mereka sebagai file JSON yang dapat dimuat kembali ke dalam aplikasi jika diperlukan.
Ini bekerja dengan baik di Android tetapi jauh lebih tidak elegan untuk iOS. Di iPhone, itu menghasilkan banyak teks di layar seperti ini:
Seperti yang dapat Anda bayangkan, saya tidak sendirian dalam memarahi Apple melalui WebKit tentang kekurangan ini. Bug yang relevan ada di sini.
Pada saat penulisan, bug ini memiliki solusi dan tambalan tetapi belum masuk ke iOS Safari. Diduga, iOS13 memperbaikinya tetapi itu dalam Beta saat saya menulis.
Jadi, untuk produk minimum saya yang layak, itu ditujukan untuk penyimpanan. Sekarang saatnya mencoba membuat segalanya lebih 'seperti aplikasi'!
Aplikasi-I-Ness
Ternyata setelah banyak berdiskusi dengan banyak orang, mendefinisikan dengan tepat apa yang dimaksud dengan 'app like' cukup sulit.
Pada akhirnya, saya memilih 'seperti aplikasi' yang identik dengan kelicikan visual yang biasanya hilang dari web. Ketika saya memikirkan aplikasi yang terasa nyaman untuk digunakan, semuanya memiliki fitur gerak. Bukan serampangan, tapi gerak yang menambah cerita aksi Anda. Mungkin transisi halaman antar layar, cara menu muncul. Sulit untuk dijelaskan dengan kata-kata tetapi kebanyakan dari kita mengetahuinya ketika kita melihatnya.
Bagian pertama dari bakat visual yang dibutuhkan adalah menggeser nama pemain ke atas atau ke bawah dari 'In' ke 'Out' dan sebaliknya saat dipilih. Membuat pemain langsung berpindah dari satu bagian ke bagian lainnya sangatlah mudah, tetapi tentu saja tidak 'seperti aplikasi'. Animasi saat nama pemain diklik diharapkan akan menekankan hasil interaksi tersebut – pemain berpindah dari satu kategori ke kategori lainnya.
Seperti banyak dari jenis interaksi visual ini, kesederhanaannya yang tampak memungkiri kompleksitas yang terlibat dalam membuatnya bekerja dengan baik.
Butuh beberapa iterasi untuk mendapatkan gerakan yang benar tetapi logika dasarnya adalah ini:
- Setelah 'pemain' diklik, tangkap di mana pemain itu, secara geometris, di halaman;
- Ukur seberapa jauh bagian atas area yang harus ditempuh pemain jika naik ('Masuk') dan seberapa jauh bagian bawah, jika turun ('Keluar');
- Jika naik, ruang yang sama dengan ketinggian barisan pemain harus dibiarkan saat pemain bergerak ke atas dan pemain di atas harus runtuh ke bawah dengan kecepatan yang sama dengan waktu yang dibutuhkan pemain untuk melakukan perjalanan hingga mendarat di ruang tersebut. dikosongkan oleh pemain 'Dalam' yang ada (jika ada) yang turun;
- Jika seorang pemain akan 'Keluar' dan bergerak ke bawah, segala sesuatu yang lain perlu pindah ke ruang yang tersisa dan pemain harus berakhir di bawah pemain 'Keluar' saat ini.
Fiuh! Itu lebih sulit daripada yang saya kira dalam bahasa Inggris — apalagi JavaScript!
Ada kerumitan tambahan untuk dipertimbangkan dan dicoba seperti kecepatan transisi. Pada awalnya, tidak jelas apakah kecepatan gerakan yang konstan (misalnya 20px per 20ms), atau durasi gerakan yang konstan (misalnya 0.2s) akan terlihat lebih baik. Yang pertama sedikit lebih rumit karena kecepatan perlu dihitung 'on the fly' berdasarkan seberapa jauh pemain perlu melakukan perjalanan — jarak yang lebih jauh membutuhkan durasi transisi yang lebih lama.
Namun, ternyata durasi transisi yang konstan tidak hanya lebih sederhana dalam kode; itu benar-benar menghasilkan efek yang lebih menguntungkan. Perbedaannya tidak kentara tetapi ini adalah jenis pilihan yang hanya dapat Anda tentukan setelah Anda melihat kedua opsi tersebut.
Sering kali ketika mencoba untuk memakukan efek ini, kesalahan visual akan menarik perhatian tetapi tidak mungkin untuk didekonstruksi secara real time. Saya menemukan proses debugging terbaik adalah membuat rekaman QuickTime dari animasi dan kemudian melewatinya satu per satu. Selalu ini mengungkapkan masalah lebih cepat daripada debugging berbasis kode.
Melihat kodenya sekarang, saya dapat menghargai bahwa pada sesuatu di luar aplikasi sederhana saya, fungsi ini hampir pasti dapat ditulis dengan lebih efektif. Mengingat bahwa aplikasi akan mengetahui jumlah pemain dan mengetahui ketinggian tetap dari bilah, itu harus sepenuhnya mungkin untuk membuat semua perhitungan jarak dalam JavaScript saja, tanpa pembacaan DOM.
Bukannya apa yang dikirimkan tidak berfungsi, hanya saja itu bukan jenis solusi kode yang akan Anda tunjukkan di Internet. Oh tunggu.
Interaksi 'seperti aplikasi' lainnya jauh lebih mudah dilakukan. Alih-alih menu hanya masuk dan keluar dengan sesuatu yang sederhana seperti mengubah properti tampilan, banyak jarak tempuh diperoleh hanya dengan mengeksposnya dengan sedikit lebih banyak kemahiran. Itu masih dipicu secara sederhana tetapi CSS melakukan semua pekerjaan berat:
.io-EventLoader { position: absolute; top: 100%; margin-top: 5px; z-index: 100; width: 100%; opacity: 0; transition: all 0.2s; pointer-events: none; transform: translateY(-10px); [data-evswitcher-showing="true"] & { opacity: 1; pointer-events: auto; transform: none; } }
Di sana ketika atribut data-evswitcher-showing="true"
diaktifkan pada elemen induk, menu akan memudar, berubah kembali ke posisi defaultnya dan peristiwa penunjuk akan diaktifkan kembali sehingga menu dapat menerima klik.
Metodologi Lembar Gaya ECSS
Anda akan melihat dalam kode sebelumnya bahwa dari sudut pandang penulisan, penggantian CSS sedang bersarang di dalam pemilih induk. Itulah cara saya selalu menyukai menulis lembar gaya UI; satu sumber kebenaran untuk setiap selektor dan penggantian apa pun untuk pemilih yang dienkapsulasi dalam satu set kurung kurawal. Ini adalah pola yang memerlukan penggunaan prosesor CSS (Sass, PostCSS, LESS, Stylus, et al) tetapi saya rasa ini adalah satu-satunya cara positif untuk memanfaatkan fungsionalitas bersarang.
Saya telah memperkuat pendekatan ini dalam buku saya, Enduring CSS dan meskipun ada banyak metode yang lebih terlibat yang tersedia untuk menulis CSS untuk elemen antarmuka, ECSS telah melayani saya dan tim pengembangan besar tempat saya bekerja dengan baik sejak pendekatan ini pertama kali didokumentasikan. kembali di tahun 2014! Ini terbukti sama efektifnya dalam hal ini.
Mempartisi TypeScript
Bahkan tanpa prosesor CSS atau bahasa superset seperti Sass, CSS memiliki kemampuan untuk mengimpor satu atau lebih file CSS ke file lain dengan direktif impor:
@import "other-file.css";
Ketika memulai dengan JavaScript saya terkejut tidak ada yang setara. Setiap kali file kode menjadi lebih panjang dari layar atau lebih tinggi, selalu terasa seperti membaginya menjadi potongan-potongan yang lebih kecil akan bermanfaat.
Bonus lain untuk menggunakan TypeScript adalah ia memiliki cara sederhana yang indah untuk membagi kode menjadi file dan mengimpornya saat dibutuhkan.
Kemampuan ini mendahului modul JavaScript asli dan merupakan fitur kenyamanan yang luar biasa. Ketika TypeScript dikompilasi, semuanya kembali ke satu file JavaScript. Itu berarti adalah mungkin untuk dengan mudah memecah kode aplikasi menjadi file parsial yang dapat dikelola untuk pembuatan dan impor kemudian ke file utama dengan mudah. Bagian atas inout.ts
utama tampak seperti ini:
/// <reference path="defaultData.ts" /> /// <reference path="splitTeams.ts" /> /// <reference path="deleteOrPaidClickMask.ts" /> /// <reference path="repositionSlat.ts" /> /// <reference path="createSlats.ts" /> /// <reference path="utils.ts" /> /// <reference path="countIn.ts" /> /// <reference path="loadFile.ts" /> /// <reference path="saveText.ts" /> /// <reference path="observerPattern.ts" /> /// <reference path="onBoard.ts" />
Tugas rumah tangga dan organisasi sederhana ini sangat membantu.
Beberapa Acara
Pada awalnya, saya merasa bahwa dari sudut pandang fungsionalitas, satu acara, seperti “Tuesday Night Football” sudah cukup. Dalam skenario itu, jika Anda memuat Masuk/Keluar, Anda baru saja menambahkan/menghapus atau memindahkan pemain masuk atau keluar dan hanya itu. Tidak ada gagasan tentang beberapa peristiwa.
Saya segera memutuskan bahwa (bahkan untuk produk minimum yang layak) ini akan membuat pengalaman yang sangat terbatas. Bagaimana jika seseorang mengatur dua pertandingan pada hari yang berbeda, dengan daftar pemain yang berbeda? Tentunya In/Out dapat/harus mengakomodir kebutuhan tersebut? Tidak butuh waktu terlalu lama untuk membentuk kembali data untuk memungkinkan hal ini dan mengubah metode yang diperlukan untuk memuat di set yang berbeda.
Pada awalnya, kumpulan data default terlihat seperti ini:
var defaultData = [ { name: "Daz", paid: false, marked: false, team: "", in: false }, { name: "Carl", paid: false, marked: false, team: "", in: false }, { name: "Big Dave", paid: false, marked: false, team: "", in: false }, { name: "Nick", paid: false, marked: false, team: "", in: false } ];
Array yang berisi objek untuk setiap pemain.
Setelah memperhitungkan beberapa peristiwa, itu diubah agar terlihat seperti ini:
var defaultDataV2 = [ { EventName: "Tuesday Night Footy", Selected: true, EventData: [ { name: "Jack", marked: false, team: "", in: false }, { name: "Carl", marked: false, team: "", in: false }, { name: "Big Dave", marked: false, team: "", in: false }, { name: "Nick", marked: false, team: "", in: false }, { name: "Red Boots", marked: false, team: "", in: false }, { name: "Gaz", marked: false, team: "", in: false }, { name: "Angry Martin", marked: false, team: "", in: false } ] }, { EventName: "Friday PM Bank Job", Selected: false, EventData: [ { name: "Mr Pink", marked: false, team: "", in: false }, { name: "Mr Blonde", marked: false, team: "", in: false }, { name: "Mr White", marked: false, team: "", in: false }, { name: "Mr Brown", marked: false, team: "", in: false } ] }, { EventName: "WWII Ladies Baseball", Selected: false, EventData: [ { name: "C Dottie Hinson", marked: false, team: "", in: false }, { name: "P Kit Keller", marked: false, team: "", in: false }, { name: "Mae Mordabito", marked: false, team: "", in: false } ] } ];
Data baru adalah array dengan objek untuk setiap peristiwa. Kemudian di setiap acara ada properti EventData
yang merupakan larik dengan objek pemain seperti sebelumnya.
Butuh waktu lebih lama untuk mempertimbangkan kembali bagaimana antarmuka dapat menangani kemampuan baru ini dengan sebaik-baiknya.
Sejak awal, desainnya selalu sangat steril. Mengingat ini juga seharusnya menjadi latihan desain, saya merasa saya tidak cukup berani. Jadi sedikit lebih banyak bakat visual ditambahkan, dimulai dengan header. Inilah yang saya tiru di Sketch:
Itu tidak akan memenangkan penghargaan tapi itu pasti lebih menarik daripada di mana itu dimulai.
Di samping estetika, tidak sampai orang lain menunjukkannya, saya menghargai ikon plus besar di header sangat membingungkan. Kebanyakan orang mengira itu adalah cara untuk menambahkan acara lain. Pada kenyataannya, itu beralih ke mode 'Tambah Pemain' dengan transisi mewah yang memungkinkan Anda mengetikkan nama pemain di tempat yang sama dengan nama acara saat ini.
Ini adalah contoh lain di mana mata segar sangat berharga. Itu juga merupakan pelajaran penting dalam melepaskan. Kebenaran yang jujur adalah saya telah berpegang pada transisi mode input di header karena saya merasa itu keren dan pintar. Namun, kenyataannya tidak melayani desain dan aplikasi secara keseluruhan.
Ini diubah dalam versi langsung. Sebaliknya, tajuk hanya membahas peristiwa — skenario yang lebih umum. Sementara itu, penambahan pemain dilakukan dari sub-menu. Ini memberi aplikasi hierarki yang jauh lebih mudah dipahami.
Pelajaran lain yang dipetik di sini adalah bahwa bila memungkinkan, sangat bermanfaat untuk mendapatkan umpan balik yang jujur dari rekan-rekan. Jika mereka adalah orang yang baik dan jujur, mereka tidak akan membiarkan Anda memberi diri Anda izin!
Ringkasan: Kode Saya Bau
Benar. Sejauh ini, potongan retrospektif tech-adventure begitu normal; hal-hal ini sepuluh sen di Medium! Rumusnya kira-kira seperti ini: dev merinci bagaimana mereka menghancurkan semua rintangan untuk merilis perangkat lunak yang disetel dengan baik ke Internet dan kemudian mengambil wawancara di Google atau dipekerjakan di suatu tempat. Namun, kenyataannya adalah bahwa saya adalah pertama kali di malarkey pembuatan aplikasi ini sehingga kode akhirnya dikirimkan sebagai aplikasi 'selesai' stunk ke surga yang tinggi!
Misalnya, implementasi pola Observer yang digunakan bekerja dengan sangat baik. Saya terorganisir dan metodis pada awalnya tetapi pendekatan itu 'berjalan ke selatan' ketika saya menjadi lebih putus asa untuk menyelesaikan semuanya. Seperti pelaku diet serial, kebiasaan lama yang sudah dikenal merayap kembali dan kualitas kode kemudian turun.
Looking now at the code shipped, it is a less than ideal hodge-bodge of clean observer pattern and bog-standard event listeners calling functions. In the main inout.ts
file there are over 20 querySelector
method calls; hardly a poster child for modern application development!
I was pretty sore about this at the time, especially as at the outset I was aware this was a trap I didn't want to fall into. However, in the months that have since passed, I've become more philosophical about it.
The final post in this series reflects on finding the balance between silvery-towered code idealism and getting things shipped. It also covers the most important lessons learned during this process and my future aspirations for application development.