Çerçeve Olmadan Aşamalı Bir Web Uygulaması Tasarlama ve Oluşturma (Bölüm 2)
Yayınlanan: 2022-03-10Bu maceranın varlık nedeni, mütevazı yazarınızı görsel tasarım ve JavaScript kodlama disiplinlerinde biraz zorlamaktı. Oluşturmaya karar verdiğim uygulamanın işlevselliği, bir 'yapılacak' uygulamasından farklı değildi. Bunun özgün bir düşünce alıştırması olmadığını vurgulamak önemlidir. Hedef, yolculuktan çok daha az önemliydi.
Uygulamanın nasıl sonuçlandığını öğrenmek ister misiniz? Telefon tarayıcınızı https://io.benfrain.com adresine yönlendirin.
İşte bu makalede ele alacağımız şeylerin bir özeti:
- Proje kurulumu ve neden bir inşa aracı olarak Gulp'u tercih ettiğim;
- Uygulama tasarım kalıpları ve pratikte ne anlama geldikleri;
- Uygulama durumu nasıl saklanır ve görselleştirilir;
- CSS'nin bileşenlere nasıl dahil edildiği;
- işleri daha 'uygulama benzeri' hale getirmek için hangi UI/UX incelikleri kullanıldı;
- Havalenin yineleme yoluyla nasıl değiştiği.
Derleme araçlarıyla başlayalım.
Yapı Araçları
Temel TypeScipt ve PostCSS araçlarımı çalışır duruma getirmek ve düzgün bir geliştirme deneyimi oluşturmak için bir derleme sistemine ihtiyacım var.
Günlük işimde, son beş yıldır HTML/CSS'de ve daha az ölçüde JavaScript'te arayüz prototipleri oluşturuyorum. Yakın zamana kadar, oldukça mütevazi yapı ihtiyaçlarımı karşılamak için neredeyse yalnızca herhangi bir sayıda eklentiyle Gulp'u kullandım.
Genellikle CSS'yi işlemem, JavaScript'i veya TypeScript'i daha yaygın olarak desteklenen JavaScript'e dönüştürmem ve zaman zaman kod çıktısını küçültme ve varlıkları optimize etme gibi ilgili görevleri gerçekleştirmem gerekir. Gulp'u kullanmak, bu sorunları aplomb ile çözmeme her zaman izin verdi.
Gulp, aşina olmayanlar için yerel dosya sisteminizdeki dosyalara 'bir şeyler' yapmak için JavaScript yazmanıza izin verir. Gulp'u kullanmak için, projenizin kökünde genellikle tek bir dosyanız ( gulpfile.js
olarak adlandırılır) bulunur. Bu JavaScript dosyası, görevleri işlevler olarak tanımlamanıza olanak tanır. Temelde daha ileri JavaScript işlevleri olan ve belirli görevlerle ilgilenen üçüncü taraf 'Eklentiler' ekleyebilirsiniz.
Örnek Bir Gulp Görevi
Örnek bir Gulp görevi, bir yazma stil sayfasını (gulp-postcss) değiştirdiğinizde CSS'ye işlemek için PostCSS'yi kullanmak için bir eklenti kullanıyor olabilir. Veya TypeScript dosyalarını kaydederken vanilya JavaScript'e (gulp-typescript) derlemek. İşte Gulp'ta bir görevi nasıl yazdığınıza dair basit bir örnek. Bu görev, 'build' adlı bir klasördeki tüm dosyaları silmek için 'del' gulp eklentisini kullanır:
var del = require("del"); gulp.task("clean", function() { return del(["build/**/*"]); });
require
, del
eklentisini bir değişkene atar. Ardından gulp.task
yöntemi çağrılır. Görevi ilk argüman (“temiz”) olarak bir dizge ile adlandırırız ve ardından bu durumda argüman olarak kendisine iletilen klasörü silmek için 'del' yöntemini kullanan bir fonksiyon çalıştırırız. Yıldız işareti sembolleri, esasen derleme klasörünün 'herhangi bir klasördeki herhangi bir dosya' diyen 'glob' kalıplarıdır.
Gulp görevleri yığınları daha karmaşık hale getirebilir, ancak özünde, işlerin nasıl ele alındığının mekaniği budur. Gerçek şu ki, Gulp ile geçinmek için bir JavaScript sihirbazı olmanıza gerek yok; Tüm ihtiyacınız olan 3. sınıf kopyala ve yapıştır becerileri.
Tüm bu yıllar boyunca 'eğer bozuk değilse; denemeyin ve düzeltmeyin'.
Ancak, yolumda sıkışıp kalacağımdan endişelendim. Düşmesi kolay bir tuzak. İlk olarak, her yıl aynı yerde tatil yapmaya başlarsınız, ardından yeni moda trendlerini benimsemeyi reddedersiniz, daha sonra da yeni inşa araçlarını denemeyi eninde sonunda ve kararlı bir şekilde reddedersiniz.
İnternette 'Webpack' hakkında pek çok gevezelik duydum ve ön uç geliştirici havalı çocukların yeni moda tostunu kullanarak bir projeyi denemenin benim görevim olduğunu düşündüm.
Web paketi
Büyük bir ilgiyle webpack.js.org sitesine atladığımı net bir şekilde hatırlıyorum. Webpack'in ne olduğu ve ne yaptığının ilk açıklaması şöyle başladı:
import bar from './bar';
Ne dedin? Dr. Evil'in sözleriyle, “Bana buraya lanet olası bir kemik at Scott”.
Başa çıkmam gereken kendi sorunum olduğunu biliyorum ama 'foo', 'bar' veya 'baz'dan bahseden tüm kodlama açıklamalarına karşı bir tiksinti geliştirdim. Buna ek olarak, Webpack'in gerçekte ne için olduğunu kısa ve öz bir şekilde açıklamanın eksikliği, belki de benim için olmadığından şüphelenmeme neden oldu.
Webpack belgelerine biraz daha derinlemesine bakıldığında, biraz daha az opak bir açıklama sunuldu, "Özünde web paketi, modern JavaScript uygulamaları için statik bir modül paketleyicidir".
Hımm. Statik modül paketleyici. İstediğim bu muydu? ikna olmadım Okumaya devam ettim ama ne kadar çok okursam o kadar az netleşiyordum. O zamanlar, bağımlılık grafikleri, sıcak modül yeniden yükleme ve giriş noktaları gibi kavramlar benim için esasen kayboldu.
Birkaç akşam sonra Webpack'i araştırdıktan sonra, onu kullanma fikrinden vazgeçtim.
Eminim doğru durumda ve daha deneyimli ellerde Webpack son derece güçlü ve uygundur, ancak mütevazi ihtiyaçlarım için tam bir abartı gibi görünüyordu. Modül paketleme, ağaç sallama ve sıcak modül yeniden yükleme kulağa harika geliyordu; Küçük 'uygulamam' için onlara ihtiyacım olduğuna ikna olmadım.
O zaman Gulp'a geri dönelim.
Değişiklik olsun diye bir şeyleri değiştirmemek konusunda, değerlendirmek istediğim bir diğer teknoloji de proje bağımlılıklarını yönetmek için NPM üzerinden Yarn idi. O zamana kadar her zaman NPM kullanmıştım ve Yarn daha iyi, daha hızlı bir alternatif olarak lanse ediliyordu. Şu anda NPM kullanıyorsanız ve her şey yolundaysa, Yarn'ı denemekle uğraşmanıza gerek yok, bunun dışında Yarn hakkında söyleyecek pek bir şeyim yok.
Bu uygulama için değerlendirmem için çok geç gelen bir araç Parceljs. Sıfır yapılandırma ve tarayıcının yeniden yüklenmesi gibi bir BrowserSync ile, o zamandan beri içinde harika bir yardımcı program buldum! Ek olarak, Webpack'in savunmasında, Webpack'ten sonraki v4'ün bir yapılandırma dosyası gerektirmediği söylendi. Anekdot olarak, Twitter'da yaptığım daha yakın tarihli bir ankette, 87 katılımcının yarısından fazlası Gulp, Parcel veya Grunt yerine Webpack'i seçti.
Gulp dosyamı, kalkmak ve çalıştırmak için temel işlevlerle başlattım.
'Varsayılan' bir görev, stil sayfalarının ve TypeScript dosyalarının 'kaynak' klasörlerini izler ve bunları temel HTML ve ilişkili kaynak haritalarıyla birlikte bir build
klasöründe derler.
Ben de Gulp ile çalışan BrowserSync'im var. Bir Webpack yapılandırma dosyasıyla ne yapacağımı bilmiyor olabilirim ama bu bir tür hayvan olduğum anlamına gelmiyordu. HTML/CSS ile yineleme yaparken tarayıcıyı manuel olarak yenilemek zorunda olmak 2010 soooo ve BrowserSync size ön uç kodlama için çok yararlı olan bu kısa geri bildirim ve yineleme döngüsünü verir.
İşte 11.6.2017 itibariyle temel gulp dosyası
Ugilify ile küçültme ekleyerek Gulpfile'ı nakliyenin sonuna nasıl yaklaştırdığımı görebilirsiniz:
Proje Yapısı
Teknoloji seçimlerimin bir sonucu olarak, uygulama için kod organizasyonunun bazı unsurları kendilerini tanımlıyordu. Projenin kökünde bir gulpfile.js
, bir node_modules
klasörü (Gulp'un eklenti kodunu sakladığı yer), yazarlık stil sayfaları için bir preCSS
klasörü, TypeScript dosyaları için bir ts
klasörü ve derlenmiş kodun yaşaması için bir build
klasörü.
Buradaki fikir, dinamik olmayan herhangi bir HTML yapısı da dahil olmak üzere uygulamanın 'kabuğu'nu içeren bir index.html
sahip olmak ve ardından uygulamanın çalışmasını sağlayacak olan stiller ve JavaScript dosyasına bağlantılar oluşturmaktı. Diskte şöyle görünürdü:
build/ node_modules/ preCSS/ img/ partials/ styles.css ts/ .gitignore gulpfile.js index.html package.json tsconfig.json
BrowserSync'i bu build
klasörüne bakacak şekilde yapılandırmak, tarayıcımı localhost:3000
yönlendirebileceğim anlamına geliyordu ve her şey yolundaydı.
Yerinde temel bir yapı sistemi, dosya organizasyonu yerleşmiş ve başlangıç yapmak için bazı temel tasarımlar ile, aslında bir şeyi inşa etmeme engel olmak için yasal olarak kullanabileceğim erteleme yemim tükendi!
Başvuru Yazma
Uygulamanın nasıl işleyeceği ilkesi şuydu. Bir veri deposu olurdu. JavaScript yüklendiğinde, bu verileri yükler, verilerdeki her oyuncu arasında döngü yapar, her bir oyuncuyu düzende bir satır olarak temsil etmek için gereken HTML'yi oluşturur ve bunları uygun giriş/çıkış bölümüne yerleştirir. Daha sonra kullanıcıdan gelen etkileşimler bir oyuncuyu bir durumdan diğerine taşır. Basit.
Uygulamayı gerçekten yazmaya gelince, anlaşılması gereken iki büyük kavramsal zorluk şunlardı:
- Bir uygulama için verilerin kolayca genişletilip manipüle edilebilecek şekilde nasıl temsil edileceği;
- Kullanıcı girişinden veriler değiştirildiğinde kullanıcı arabiriminin tepki vermesi nasıl sağlanır.
JavaScript'te bir veri yapısını temsil etmenin en basit yollarından biri nesne gösterimidir. Bu cümle biraz bilgisayar bilimi-y okur. Daha basit olarak, JavaScript dilinde bir 'nesne' veri depolamanın kullanışlı bir yoludur.
ioState
(Giriş/Çıkış Durumu için) adlı bir değişkene atanan bu JavaScript nesnesini düşünün:
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 }
JavaScript'i gerçekten o kadar iyi bilmiyorsanız, muhtemelen en azından neler olduğunu kavrayabilirsiniz: kaşlı ayraçların içindeki her satır bir özellik (veya JavaScript dilinde 'anahtar') ve değer çiftidir. Her türlü şeyi bir JavaScript anahtarına ayarlayabilirsiniz. Örneğin, işlevler, diğer veri dizileri veya iç içe nesneler. İşte bir örnek:
var testObject = { testFunction: function() { return "sausages"; }, testArray: [3,7,9], nestedtObject { key1: "value1", key2: 2, } }
Net sonuç, bu tür bir veri yapısını kullanarak nesnenin herhangi bir anahtarını alıp ayarlayabilmenizdir. Örneğin, ioState nesnesinin sayısını 7 olarak ayarlamak istersek:
ioState.Count = 7;
Bu değere bir metin parçası ayarlamak istersek, gösterim şu şekilde çalışır:
aTextNode.textContent = ioState.Count;
Bu durum nesnesine değer almanın ve değerleri ayarlamanın, şeylerin JavaScript tarafında basit olduğunu görebilirsiniz. Ancak, bu değişiklikleri Kullanıcı Arayüzüne yansıtmak daha azdır. Bu, çerçevelerin ve kitaplıkların acıyı soyutlamaya çalıştığı ana alandır.
Genel anlamda, duruma dayalı olarak kullanıcı arayüzünün güncellenmesi söz konusu olduğunda, genellikle optimal olmayan bir yaklaşım olarak kabul edildiğinden, DOM'yi sorgulamaktan kaçınmak tercih edilir.
Giriş/Çıkış arayüzünü düşünün. Genellikle bir oyun için potansiyel oyuncuların bir listesini gösterir. Sayfada alt alta dikey olarak listelenirler.
Belki her oyuncu DOM'da bir onay kutusu input
saran bir label
temsil edilir. Bu şekilde, bir oyuncuya tıklamak, girdiyi 'işaretli' yapan etiket sayesinde oynatıcıyı 'In' konumuna getirir.
Arayüzümüzü güncellemek için JavaScript'teki her giriş öğesinde bir 'dinleyici' olabilir. Bir tıklama veya değişiklikte, işlev DOM'yi sorgular ve oynatıcı girişlerimizden kaçının kontrol edildiğini sayar. Bu sayıya dayanarak, kullanıcıya kaç oyuncunun kontrol edildiğini göstermek için DOM'da başka bir şeyi güncelleyeceğiz.
Bu temel işlemin maliyetini düşünelim. Bir girdinin tıklanması/kontrol edilmesi için birden fazla DOM düğümünü dinliyoruz, ardından belirli bir DOM türünden kaç tanesinin kontrol edildiğini görmek için DOM'yi sorguluyor, ardından kullanıcıya, UI bilge, oyuncu sayısını göstermek için DOM'a bir şeyler yazıyoruz. sadece saydık.
Alternatif, uygulama durumunu bir JavaScript nesnesi olarak bellekte tutmaktır. DOM'deki bir düğme/giriş tıklaması yalnızca JavaScript nesnesini güncelleyebilir ve ardından, JavaScript nesnesindeki bu değişikliğe bağlı olarak, gereken tüm arayüz değişikliklerinin tek geçişli bir güncellemesini yapabilir. JavaScript nesnesi zaten bu bilgiyi tutacağından oyuncuları saymak için DOM'yi sorgulamayı atlayabiliriz.
Böyle. Durum için bir JavaScript nesne yapısı kullanmak, basit ama herhangi bir zamanda uygulama durumunu kapsüllemek için yeterince esnek görünüyordu. Bunun nasıl yönetilebileceği teorisi de yeterince sağlam görünüyordu - 'tek yönlü veri akışı' gibi ifadeler bununla ilgili olmalı mı? Bununla birlikte, ilk gerçek numara, bu verilerdeki herhangi bir değişikliğe bağlı olarak kullanıcı arayüzünü otomatik olarak güncelleyecek bir kod oluşturmak olacaktır.
İyi haber şu ki, benden daha zeki insanlar bu konuyu çoktan çözdüler ( şükürler olsun! ). İnsanlar, uygulamaların başlangıcından beri bu tür zorluklara yönelik yaklaşımları mükemmelleştiriyorlar. Bu sorun kategorisi, 'tasarım kalıplarının' ekmek ve tereyağıdır. "Tasarım modeli" takma adı ilk başta bana ezoterik geldi, ancak biraz kazdıktan sonra her şey daha az bilgisayar bilimi ve daha sağduyulu gelmeye başladı.
Tasarım desenleri
Bilgisayar bilimi sözlüğünde bir tasarım modeli, ortak bir teknik sorunu çözmenin önceden tanımlanmış ve kanıtlanmış bir yoludur. Tasarım kalıplarını bir yemek tarifinin kodlama eşdeğeri olarak düşünün.
Tasarım kalıpları üzerine belki de en ünlü literatür, 1994'ten kalma "Tasarım Kalıpları: Yeniden Kullanılabilir Nesne Yönelimli Yazılımın Öğeleri"dir. Bu, C++ ve küçük konuşma ile ilgili olsa da, kavramlar aktarılabilir. JavaScript için Addy Osmani'nin "JavaScript Tasarım Modellerini Öğrenmek" benzer bir alanı kapsar. Ayrıca buradan çevrimiçi olarak ücretsiz okuyabilirsiniz.
Gözlemci Modeli
Tipik olarak tasarım desenleri üç gruba ayrılır: Yaratıcı, Yapısal ve Davranışsal. Uygulamanın farklı bölümlerindeki değişiklikleri iletmeye yardımcı olacak Davranışsal bir şey arıyordum.
Daha yakın zamanlarda, Gregg Pollack tarafından bir uygulama içinde tepkiselliğin uygulanmasına ilişkin gerçekten harika bir derin dalış gördüm ve okudum. Burada eğlenmeniz için hem bir blog yazısı hem de bir video var.
Learning JavaScript Design Patterns
bölümünde 'Gözlemci' kalıbının açılış açıklamasını okurken, kalıbın benim için olduğundan oldukça emindim. Şu şekilde anlatılmaktadır:
Gözlemci, bir nesnenin (özne olarak bilinir) kendisine bağlı olarak nesnelerin bir listesini (gözlemciler) tuttuğu ve durumdaki herhangi bir değişikliği otomatik olarak onlara bildirdiği bir tasarım modelidir.
Bir öznenin gözlemcileri ilginç bir şey hakkında bilgilendirmesi gerektiğinde, gözlemcilere bir bildirim yayınlar (bildirimin konusuyla ilgili belirli verileri içerebilir).
Heyecanımın anahtarı, bunun gerektiğinde kendilerini güncellemenin bir yolunu sunuyor gibi görünmesiydi.
Kullanıcının, oyun için "İçerde" olduğunu seçmek için "Betty" adlı bir oyuncuya tıkladığını varsayalım. Kullanıcı arayüzünde birkaç şeyin olması gerekebilir:
- Oynatma sayısına 1 ekleyin
- Betty'yi 'Çıkış' oyuncu havuzundan çıkarın
- Betty'yi 'In' oyuncu havuzuna ekleyin
Uygulamanın ayrıca kullanıcı arayüzünü temsil eden verileri güncellemesi gerekir. Kaçınmaya çok hevesli olduğum şey şuydu:
playerName.addEventListener("click", playerToggle); function playerToggle() { if (inPlayers.includes(e.target.textContent)) { setPlayerOut(e.target.textContent); decrementPlayerCount(); } else { setPlayerIn(e.target.textContent); incrementPlayerCount(); } }
Amaç, merkezi veriler değiştiğinde ve değiştiğinde DOM'da ihtiyaç duyulanları güncelleyen zarif bir veri akışına sahip olmaktı.
Bir Gözlemci modeliyle, duruma ve dolayısıyla kullanıcı arayüzüne kısa ve öz bir şekilde güncellemeler göndermek mümkün oldu. İşte bir örnek, listeye yeni bir oyuncu eklemek için kullanılan gerçek fonksiyon:
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 }); }
Gözlemci modeliyle ilgili kısım, io.notify
yöntemidir. Bu, uygulama durumunun items
kısmını değiştirmemizi gösterdiğinden, size 'öğelerdeki' değişiklikleri dinleyen gözlemciyi göstereyim:
io.addObserver({ props: ["items"], callback: function renderItems() { // Code that updates anything to do with items... } });
Verilerde değişiklik yapan bir bildirim yöntemimiz var ve ardından ilgilendikleri özellikler güncellendiğinde yanıt veren bu verilere Gözlemciler.

Bu yaklaşımla, uygulama, verilerin herhangi bir özelliğindeki değişiklikleri izleyen gözlemlenebilirlere sahip olabilir ve bir değişiklik meydana geldiğinde bir işlevi çalıştırabilir.
Seçtiğim Gözlemci modeliyle ilgileniyorsanız, burada daha ayrıntılı olarak açıklarım.
Artık, duruma dayalı olarak kullanıcı arayüzünü etkin bir şekilde güncellemek için bir yaklaşım vardı. Şeftali. Ancak, bu hala beni iki göze batan sorunla bıraktı.
Biri, sayfa yeniden yüklemeleri/oturumları arasında durumun nasıl saklanacağı ve kullanıcı arayüzünün çalışmasına rağmen görsel olarak pek 'uygulama gibi' olmadığı gerçeğiydi. Örneğin, bir düğmeye basıldığında, kullanıcı arayüzü anında ekranda değişti. Sadece özellikle zorlayıcı değildi.
Önce şeylerin depolama tarafıyla ilgilenelim.
Durum Kaydetme
Bu konuya giren bir geliştirme tarafında birincil ilgim, uygulama arayüzlerinin nasıl oluşturulabileceğini ve JavaScript ile etkileşimli hale getirilebileceğini anlamaya odaklandı. Bir sunucudan veri depolama ve alma veya kullanıcı kimlik doğrulaması ve oturum açma işlemleri nasıl 'kapsam dışı' idi.
Bu nedenle, veri depolama ihtiyaçları için bir web servisine bağlanmak yerine, tüm verileri istemcide tutmayı seçtim. Bir istemcide veri depolamak için bir dizi web platformu yöntemi vardır. localStorage
seçtim.
localStorage için API inanılmaz derecede basittir. Bunun gibi verileri ayarlar ve alırsınız:
// Set something localStorage.setItem("yourKey", "yourValue"); // Get something localStorage.getItem("yourKey");
LocalStorage, iki dize ilettiğiniz bir setItem
yöntemine sahiptir. Birincisi, verileri depolamak istediğiniz anahtarın adıdır ve ikinci dize, saklamak istediğiniz gerçek dizedir. getItem
yöntemi, localStorage'da bu anahtarın altında depolananları size döndüren bir argüman olarak bir dize alır. Güzel ve basit.
Ancak localStorage kullanmama nedenleri arasında her şeyin bir 'string' olarak kaydedilmesi gerektiği gerçeği de var. Bu, dizi veya nesne gibi bir şeyi doğrudan depolayamayacağınız anlamına gelir. Örneğin, tarayıcı konsolunuzda şu komutları çalıştırmayı deneyin:
// Set something localStorage.setItem("myArray", [1, 2, 3, 4]); // Get something localStorage.getItem("myArray"); // Logs "1,2,3,4"
'myArray' değerini bir dizi olarak belirlemeye çalışmış olsak da; onu aldığımızda, bir dizge olarak saklanmıştı ('1,2,3,4' etrafındaki tırnak işaretlerine dikkat edin).
Nesneleri ve dizileri localStorage ile kesinlikle saklayabilirsiniz, ancak bunların dizelerden ileri geri dönüştürülmeleri gerektiğine dikkat etmeniz gerekir.
Bu nedenle, durum verilerini localStorage'a yazmak için JSON.stringify()
yöntemiyle bir dizeye şöyle yazılmıştır:
const storage = window.localStorage; storage.setItem("players", JSON.stringify(io.items));
Verilerin localStorage'dan alınması gerektiğinde, dize, aşağıdaki gibi JSON.parse()
yöntemiyle tekrar kullanılabilir verilere döndürüldü:
const players = JSON.parse(storage.getItem("players"));
localStorage
kullanmak, her şeyin istemcide olduğu anlamına geliyordu ve bu, 3. taraf hizmetleri veya veri depolama endişeleri olmadığı anlamına geliyordu.
Veriler artık sürekli yenilemeler ve oturumlardı — Yaşasın! Kötü haber, localStorage'ın tarayıcı verilerini boşaltan bir kullanıcıdan sağ çıkamamasıydı. Biri bunu yaptığında, tüm Giriş/Çıkış verileri kaybolacaktı. Bu ciddi bir eksiklik.
'localStorage'ın muhtemelen 'uygun' uygulamalar için en iyi çözüm olmadığını anlamak zor değil. Yukarıda bahsedilen dize sorununun yanı sıra, 'ana ipliği' engellediği için ciddi işler için de yavaştır. KV Storage gibi alternatifler geliyor, ancak şimdilik, uygunluğuna göre kullanımını uyarmak için zihinsel bir not alın.
Verileri bir kullanıcının cihazında yerel olarak kaydetmenin kırılganlığına rağmen, bir hizmete veya veri tabanına bağlanmaya direnildi. Bunun yerine, bir 'yükle/kaydet' seçeneği sunularak sorun yan adıma geçildi. Bu, herhangi bir In/Out kullanıcısının verilerini, gerektiğinde uygulamaya geri yüklenebilecek bir JSON dosyası olarak kaydetmesine olanak tanır.
Bu, Android'de iyi çalıştı, ancak iOS için çok daha az zarif. Bir iPhone'da, ekranda aşağıdaki gibi bir metin savurganlığı ile sonuçlandı:

Tahmin edebileceğiniz gibi, bu eksiklik hakkında WebKit aracılığıyla Apple'ı azarlamakta yalnız değildim. İlgili hata buradaydı.
Bu hatanın yazıldığı sırada bir çözümü ve yaması var ancak henüz iOS Safari'ye girmedi. İddiaya göre, iOS13 düzeltiyor ama ben yazarken bu Beta'da.
Dolayısıyla, minimum uygulanabilir ürünüm için bu, depolamaya yönelikti. Şimdi işleri daha 'uygulama benzeri' hale getirmeye çalışma zamanıydı!
App-I-Ness
Pek çok insanla yapılan birçok tartışmadan sonra, "applike" nin tam olarak ne anlama geldiğini tanımlamanın oldukça zor olduğu ortaya çıktı.
Sonunda, genellikle web'de eksik olan görsel bir kayganlıkla eşanlamlı olan 'uygulama benzeri' olduğuna karar verdim. Kullanmak için iyi hissettiren uygulamaları düşündüğümde, hepsinde hareket var. Gereksiz değil, eylemlerinizin hikayesine katkıda bulunan hareket. Menülerin ortaya çıkma şekli, ekranlar arasındaki sayfa geçişleri olabilir. Kelimelerle anlatmak zor ama çoğumuz gördüğümüzde anlıyoruz.
Gereken ilk görsel yetenek, oyuncu adlarını 'Giriş'ten 'Dışarı'ya ve seçildiğinde tam tersi olarak yukarı veya aşağı kaydırmaktı. Bir oyuncunun bir bölümden diğerine anında geçmesini sağlamak basitti ama kesinlikle 'uygulama gibi' değildi. Oyuncu adı tıklandığında bir animasyon, umarız bu etkileşimin sonucunu vurgular - oyuncu bir kategoriden diğerine geçer.
Bu tür görsel etkileşimlerin çoğu gibi, görünüşteki basitlikleri de aslında iyi çalışmasını sağlamanın karmaşıklığını gizler.
Hareketi doğru yapmak birkaç yineleme aldı ancak temel mantık şuydu:
- Bir 'oyuncu' tıklandığında, o oyuncunun sayfada geometrik olarak nerede olduğunu yakalayın;
- Oyuncunun yukarı çıkıyorsa ('Giriş') alanın üst kısmının ne kadar uzağa gitmesi gerektiğini ve aşağı iniyorsa ('Dışarı') alt kısmının ne kadar uzakta olduğunu ölçün;
- Yukarı çıkıyorsa, oyuncu yukarı çıkarken oyuncu sırasının yüksekliğine eşit bir boşluk bırakılmalı ve yukarıdaki oyuncular, oyuncunun uzaya inmesi için geçen süre ile aynı oranda aşağı doğru çökmelidir. mevcut 'In' oyuncuların (eğer varsa) tarafından boşalan, aşağı inen;
- Bir oyuncu 'Dışarı' çıkıyor ve aşağı hareket ediyorsa, diğer her şeyin kalan boşluğa doğru hareket etmesi ve oyuncunun mevcut 'Çıkış' oyuncularının altında kalması gerekir.
Vay! İngilizce düşündüğümden daha zordu - JavaScript'i boşver!
Geçiş hızları gibi dikkate alınması ve denenmesi gereken ek karmaşıklıklar vardı. Başlangıçta, sabit bir hareket hızının mı (örneğin 20 ms başına 20 piksel) yoksa hareket için sabit bir sürenin mi (örneğin 0,2sn) daha iyi görüneceği belli değildi. İlki, hızın, oyuncunun ne kadar uzağa gitmesi gerektiğine bağlı olarak "anında" hesaplanması gerektiğinden biraz daha karmaşıktı - daha uzun bir geçiş süresi gerektiren daha büyük mesafe.
Ancak, sabit bir geçiş süresinin sadece kodda daha basit olmadığı ortaya çıktı; aslında daha olumlu bir etki yarattı. Fark çok küçüktü, ancak bunlar ancak her iki seçeneği de gördükten sonra belirleyebileceğiniz türden seçenekler.
Arada bir bu etkiyi yakalamaya çalışırken, görsel bir aksaklık göze çarpıyordu ama gerçek zamanlı olarak yapısını bozmak imkansızdı. En iyi hata ayıklama işleminin, animasyonun bir QuickTime kaydını oluşturmak ve ardından her seferinde bir kareden geçmek olduğunu buldum. Her zaman bu, sorunu herhangi bir kod tabanlı hata ayıklamadan daha hızlı ortaya çıkardı.
Şimdi koda baktığımda, mütevazi uygulamamın ötesinde bir şey için bu işlevin neredeyse kesinlikle daha etkili bir şekilde yazılabileceğini anlayabiliyorum. Uygulamanın oyuncu sayısını ve kaburgaların sabit yüksekliğini bildiği göz önüne alındığında, tüm mesafe hesaplamalarını herhangi bir DOM okuması olmadan yalnızca JavaScript'te yapmak tamamen mümkün olmalıdır.
Gönderilen şey çalışmıyor değil, sadece internette sergileyeceğiniz türden bir kod çözümü değil. Bekle.
Diğer 'uygulama benzeri' etkileşimleri çekmek çok daha kolaydı. Bir görüntüleme özelliğini değiştirmek gibi basit bir şeyle basitçe girip çıkan menüler yerine, onları biraz daha incelikle göstererek çok fazla mesafe kat edildi. Hala basitçe tetikleniyordu ama CSS tüm ağır kaldırma işini yapıyordu:
.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; } }
Orada bir üst öğede data-evswitcher-showing="true"
özniteliği değiştirildiğinde, menü kararır, varsayılan konumuna geri döner ve menünün tıklama alabilmesi için işaretçi olayları yeniden etkinleştirilir.
ECSS Stil Sayfası Metodolojisi
Bu önceki kodda, yazarlık bakış açısından, CSS geçersiz kılmalarının bir üst seçici içinde yuvalandığını fark edeceksiniz. UI stil sayfaları yazmayı her zaman bu şekilde tercih ederim; her seçici için tek bir doğruluk kaynağı ve tek bir küme ayracı içinde bu seçici için herhangi bir geçersiz kılma. Bu, bir CSS işlemcisi (Sass, PostCSS, LESS, Stylus, ve diğerleri) kullanılmasını gerektiren bir kalıptır, ancak iç içe yerleştirme işlevini kullanmanın tek olumlu yolu olduğunu düşünüyorum.
Bu yaklaşımı Enduring CSS adlı kitabımda pekiştirdim ve arayüz öğeleri için CSS yazmak için çok sayıda daha ilgili yöntem olmasına rağmen, yaklaşım ilk belgelendiğinden beri ECSS bana ve birlikte çalıştığım büyük geliştirme ekiplerine hizmet etti. 2014'e geri döndük! Bu durumda da aynı derecede etkili olduğunu kanıtladı.
TypeScript'i Kısıtlama
Sass gibi bir CSS işlemcisi veya üst küme dili olmasa bile, CSS, içe aktarma yönergesi ile bir veya daha fazla CSS dosyasını diğerine aktarma yeteneğine sahiptir:
@import "other-file.css";
JavaScript ile başlarken eşdeğeri olmadığına şaşırdım. Kod dosyaları bir ekrandan uzun veya çok yüksek olduğunda, onu her zaman daha küçük parçalara bölmek faydalı olacaktır.
TypeScript kullanmanın bir başka avantajı da, kodu dosyalara bölmenin ve gerektiğinde bunları içe aktarmanın güzel ve basit bir yoluna sahip olmasıdır.
Bu özellik, yerel JavaScript modüllerinin eski tarihli ve büyük bir kolaylık özelliğiydi. TypeScript derlendiğinde, hepsini tek bir JavaScript dosyasına birleştirdi. Bu, uygulama kodunu yazma için kolayca yönetilebilir kısmi dosyalara ayırmanın ve ardından ana dosyaya kolayca aktarmanın mümkün olduğu anlamına geliyordu. Ana inout.ts
üst kısmı şuna benziyordu:
/// <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" />
Bu basit ev işleri ve organizasyon görevi çok yardımcı oldu.
Birden Çok Etkinlik
Başlangıçta, işlevsellik açısından “Salı Gecesi Futbolu” gibi tek bir etkinliğin yeterli olacağını hissettim. Bu senaryoda, Giriş/Çıkış'ı yüklediyseniz, oyuncuları eklediniz/kaldırdınız veya içeri veya dışarı taşıdınız ve hepsi bu kadar. Birden fazla olay kavramı yoktu.
Hemen karar verdim (en az uygulanabilir bir ürün için bile olsa) bunun oldukça sınırlı bir deneyim sağlayacağına. Ya biri farklı günlerde, farklı oyuncu kadrosuyla iki oyun düzenleseydi? Elbette In/Out bu ihtiyacı karşılayabilir/karşılayabilir mi? Bunu mümkün kılmak için verileri yeniden şekillendirmek ve farklı bir kümeye yüklemek için gereken yöntemleri değiştirmek çok uzun sürmedi.
Başlangıçta, varsayılan veri seti şuna benziyordu:
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 } ];
Her oyuncu için bir nesne içeren bir dizi.
Birden fazla olayı çarpanlara ayırdıktan sonra, şöyle görünecek şekilde değiştirildi:
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 } ] } ];
Yeni veriler, her olay için bir nesne içeren bir diziydi. Daha sonra her olayda, daha önce olduğu gibi oyuncu nesnelerinin bulunduğu bir dizi olan bir EventData
özelliği vardı.
Arayüzün bu yeni yetenekle en iyi nasıl başa çıkabileceğini yeniden düşünmek çok daha uzun sürdü.
En başından beri, tasarım her zaman çok steril olmuştu. Bunun aynı zamanda bir tasarım alıştırması olması gerektiğini düşünürsek, yeterince cesur olduğumu hissetmedim. Böylece başlıktan başlayarak biraz daha görsel yetenek eklendi. Sketch'te alay ettiğim şey bu:

Ödül kazanmayacaktı ama kesinlikle başladığı yerden daha fazla dikkat çekiciydi.
Estetik bir yana, bir başkası işaret edene kadar, başlıktaki büyük artı simgesinin çok kafa karıştırıcı olduğunu takdir ettim. Çoğu insan bunun başka bir etkinlik eklemenin bir yolu olduğunu düşündü. Gerçekte, oyuncunun adını etkinlik adıyla aynı yere yazmanıza izin veren süslü bir geçişle 'Oyuncu Ekle' moduna geçti.
Bu, taze gözlerin paha biçilmez olduğu başka bir örnekti. Aynı zamanda bırakma konusunda da önemli bir dersti. Dürüst gerçek şuydu ki, başlıktaki giriş modu geçişini tuttum çünkü bunun havalı ve zekice olduğunu hissettim. Ancak gerçek şu ki, tasarıma ve dolayısıyla bir bütün olarak uygulamaya hizmet etmiyordu.
Bu, canlı sürümde değiştirildi. Bunun yerine, başlık yalnızca olaylarla ilgilenir - daha yaygın bir senaryo. Bu arada alt menüden oyuncu ekleme işlemi yapılır. Bu, uygulamaya çok daha anlaşılır bir hiyerarşi sağlar.
Burada öğrenilen diğer ders, mümkün olduğunda akranlardan samimi geri bildirim almanın çok faydalı olduğuydu. İyi ve dürüst insanlarsa, kendinize bir şans vermenize izin vermezler!
Özet: Kodum Kokuyor
Doğru. Şimdiye kadar, çok normal bir teknoloji-macera retrospektif parçası; bu şeyler Medium'da on bir kuruş! Formül şuna benzer: Geliştirici, ince ayarlanmış bir yazılımı İnternet'e bırakmak için tüm engelleri nasıl yıktıklarını ve ardından Google'da bir röportajı nasıl aldıklarını veya bir yerden işe alındıklarını ayrıntılarıyla anlatıyor. Bununla birlikte, işin gerçeği şu ki, bu uygulama oluşturma malarkeyinde ilk zamanlayıcıydım, bu yüzden kod sonunda 'bitmiş' uygulama olarak gönderilen kod yüksek cennete kokardı!
Örneğin, kullanılan Gözlemci desen uygulaması çok iyi çalıştı. Başlangıçta düzenli ve sistemliydim, ancak işleri bitirmek için daha umutsuz hale geldikçe bu yaklaşım 'güney'e gitti. Seri bir diyetçi gibi, eski tanıdık alışkanlıklar geri sızdı ve ardından kod kalitesi düştü.
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.