UI bileşenlerini Rails at Flywheel'de nasıl oluşturuyoruz?

Yayınlanan: 2019-11-16

Büyük bir web uygulamasında görsel tutarlılığı korumak, birçok kuruluşta paylaşılan bir sorundur. Flywheel'de farklı değiliz. Ana web uygulamamız Ruby on Rails ile oluşturuldu ve herhangi bir günde yaklaşık 15 Rails geliştiricimiz ve üç ön uç geliştiricimiz var. Tasarım konusunda da uzmanız (bir şirket olarak temel değerlerimizden biridir) ve Scrum ekiplerimizde geliştiricilerle çok yakın çalışan üç tasarımcımız var.

Ana hedefimiz, herhangi bir Flywheel geliştiricisinin hiçbir engel olmadan duyarlı bir sayfa oluşturabilmesini sağlamaktır. Barikatlar genellikle, bir maket oluşturmak için hangi mevcut bileşenlerin kullanılacağını bilmemeyi (bu, kod tabanını çok benzer, gereksiz bileşenlerle şişirmeye yol açar) ve tasarımcılarla yeniden kullanılabilirliği ne zaman tartışacağını bilmemeyi içerir. Bu, tutarsız müşteri deneyimlerine, geliştirici hayal kırıklığına ve geliştiriciler ile tasarımcılar arasında farklı bir tasarım diline katkıda bulunur.

UI kalıplarını ve bileşenlerini oluşturmaya/korumaya yönelik stil kılavuzlarının ve yöntemlerinin birkaç yinelemesinden geçtik ve her yineleme, o sırada karşılaştığımız sorunların çözülmesine yardımcı oldu. Şimdi, bizi uzun süre ayakta tutacağına inandığım (bizim için) yeni bir yaklaşıma geçiyoruz. Rails uygulamanızda da benzer sorunlar yaşıyorsanız ve bileşenlere sunucu tarafından yaklaşmak istiyorsanız, umarım bu makale size bir fikir verebilir.

Bu yazıda şu konulara gireceğim:

  • Ne için çözüyoruz
  • Kısıtlayıcı bileşenler
  • Bileşenleri sunucu tarafında oluşturma
  • Sunucu tarafı bileşenlerini kullanamadığımız yerler


Ne için çözüyoruz

UI bileşenlerimizi tamamen kısıtlamak ve aynı UI'nin birden fazla şekilde oluşturulma olasılığını ortadan kaldırmak istedik. Bir müşteri (ilk başta) bunu söyleyemese de, bileşenler üzerinde kısıtlamaların olmaması kafa karıştırıcı bir geliştirici deneyimine yol açar, işleri sürdürmeyi çok zorlaştırır ve küresel tasarım değişiklikleri yapmayı zorlaştırır.

Bileşenlere geleneksel olarak yaklaşma şeklimiz, belirli bir bileşeni oluşturmak için gereken tüm işaretlemeyi listeleyen stil kılavuzumuzdu. Örneğin, çıta bileşenimiz için stil kılavuzu sayfası şöyle görünüyordu:

Bu, birkaç yıl boyunca işe yaradı ve bizim için uygun oldu, ancak değişkenleri, durumları veya bileşeni kullanmanın alternatif yollarını eklediğimizde sorunlar sürünmeye başladı. Karmaşık bir kullanıcı arabirimi parçasıyla, hangi sınıfların kullanılacağını ve hangilerinden kaçınılacağını ve istenen varyasyonun çıktısını almak için işaretlemenin hangi sırada olması gerektiğini bilmek için stil kılavuzuna başvurmak zahmetli hale geldi. Ve çoğu zaman, tasarımcılar belirli bir bileşene küçük eklemeler veya ince ayarlar yapardı. Stil kılavuzu bunu tam olarak desteklemediğinden, bu ince ayarın doğru bir şekilde görüntülenmesini sağlayan alternatif saldırılar (başka bir bileşenin uygun olmayan bir şekilde parçalanması gibi) rahatsız edici bir şekilde yaygınlaştı.

Kısıtlanmamış bileşen örneği

Tutarsızlıkların zaman içinde nasıl ortaya çıktığını göstermek için, Flywheel uygulamasındaki bileşenlerimizden birinin basit (ve yapmacık) ancak çok yaygın bir örneğini kullanacağım: kart başlıkları.

Bir tasarım modelinden başlayarak, bir kart başlığı böyle görünüyordu. Bir başlık, bir düğme ve bir alt kenarlık ile oldukça basitti.

.card__header
  .card__header-left
    %h2 Backups

  .card__header-right
    = link_to "#" do
      = icon("plus_small")

Kodlandıktan sonra, başlığın soluna bir simge eklemek isteyen bir tasarımcı hayal edin. Kutunun dışında, simge ile başlık arasında herhangi bir boşluk olmayacak.

...
  .card__header-left
    = icon("arrow_backup", color: "gray25")
    %h2 Backups
...

İdeal olarak bunu kart başlıkları için CSS'de çözerdik, ancak bu örnek için, diyelim ki başka bir geliştirici “Oh, biliyorum! Bazı marj yardımcılarımız var. Başlığa bir yardımcı sınıfı tokatlayacağım. ”

...
  .card__header-left
    = icon("arrow_backup", color: "gray25")
    %h2.--ml-10 Backups
...

Bu teknik olarak maketin yaptığı gibi görünüyor, değil mi?! Elbette, ama diyelim ki bir ay sonra başka bir geliştiricinin bir kart başlığına ihtiyacı var, ancak simgesi yok. Son örneği bulurlar, kopyalar/yapıştırırlar ve basitçe simgeyi kaldırırlar.

Yine doğru görünüyor, değil mi? Bağlam dışı, tasarıma keskin bir gözle bakmayan birine, elbette! Ama orijinalin yanına bakın. Başlıktaki sol kenar boşluğu hala orada çünkü sol kenar boşluğunun kaldırılması gerektiğini anlamadılar!

Bu örneği bir adım daha ileri götürerek, alt kenarlığı olmayan bir kart başlığı için çağrılan başka bir maket diyelim. Stil kılavuzunda “borderless” olarak adlandırılan bir durumu bulup uygulayabiliriz. Mükemmel!

Başka bir geliştirici daha sonra bu kodu yeniden kullanmayı deneyebilir, ancak bu durumda aslında bir sınıra ihtiyaçları vardır. Varsayımsal olarak, stil kılavuzunda belgelenen uygun kullanımı görmezden geldiklerini ve borderless sınıfının kaldırılmasının onlara kendi sınırlarını vereceğini fark etmediklerini varsayalım. Bunun yerine yatay bir kural eklerler. Başlık ve kenarlık arasında fazladan bir dolgu oluyor, bu yüzden saat ve voila'ya bir yardımcı sınıf uyguluyorlar!

Orijinal kart başlığındaki tüm bu değişikliklerle birlikte, şimdi kodda elimizde bir karışıklık var.

.card__header.--borderless
  .card__header-left
    %h2.--ml-10 Backups

  .card__header-right
    = link_to "#" do
      = icon("plus_small")

  %hr.--mt-0.--mb-0

Yukarıdaki örneğin, sınırlandırılmamış bileşenlerin zamanla nasıl dağınık hale gelebileceğini göstermek için olduğunu unutmayın. Ekibimizdeki herhangi biri bir kart başlığının bir varyasyonunu göndermeye çalıştıysa, bu bir tasarım incelemesi veya kod incelemesi tarafından yakalanmalıdır. Ancak bunun gibi şeyler bazen çatlaklardan geçer, bu nedenle kurşun geçirmez şeylere ihtiyacımız var!


Kısıtlayıcı bileşenler

Yukarıda listelenen sorunların bileşenlerle zaten net bir şekilde çözüldüğünü düşünüyor olabilirsiniz. Bu doğru bir varsayım! React ve Vue gibi ön uç çerçeveler tam da bu amaç için çok popülerdir; UI'yi kapsüllemek için harika araçlardır. Ancak, onlarda her zaman sevmediğimiz bir sorun var - kullanıcı arayüzünüzün JavaScript tarafından oluşturulmasını gerektiriyorlar.

Volan uygulaması, esas olarak sunucu tarafından oluşturulan HTML ile çok ağır bir arka uçtur - ancak neyse ki bizim için bileşenler birçok biçimde olabilir. Günün sonunda, bir UI bileşeni, bir tarayıcıya işaretleme çıktısı veren stillerin ve tasarım kurallarının bir kapsüllenmesidir. Bu farkındalıkla, aynı yaklaşımı bileşenlere de uygulayabiliriz, ancak bir JavaScript çerçevesinin ek yükü olmadan.

Aşağıda kısıtlı bileşenleri nasıl oluşturduğumuza değineceğiz, ancak bunları kullanarak bulduğumuz avantajlardan birkaçı burada:

  • Bir bileşeni bir araya getirmenin asla gerçekten yanlış bir yolu yoktur.
  • Bileşen, tüm tasarım düşüncesini sizin için yapar. (Seçenekleri geçmeniz yeterli!)
  • Bir bileşen oluşturmak için kullanılan sözdizimi çok tutarlıdır ve akıl yürütmesi kolaydır.
  • Bir bileşende tasarım değişikliği gerekiyorsa, bileşende bir kez değiştirebilir ve her yerde güncellendiğinden emin olabiliriz.

Bileşenleri sunucu tarafında oluşturma

Peki bileşenleri kısıtlayarak ne hakkında konuşuyoruz? Hadi kazalım!

Daha önce de belirtildiği gibi, Flywheel uygulamasında çalışan herhangi bir geliştiricinin bir sayfanın tasarım modeline bakabilmesini ve bu sayfayı herhangi bir engel olmadan anında oluşturabilmesini istiyoruz. Bu, kullanıcı arabirimini oluşturma yönteminin A) çok iyi belgelenmiş ve B) çok açıklayıcı ve tahminden uzak olması gerektiği anlamına gelir.

Kısmi kurtarma (ya da biz öyle düşündük)

Geçmişte denediğimiz bu konudaki ilk adım, Rails kısmi öğelerini kullanmaktı. Kısmiler, Rails'in şablonlarda yeniden kullanılabilirlik için size sağladığı tek araçtır. Doğal olarak, herkesin ulaştığı ilk şey onlar. Ancak bunlara güvenmenin önemli dezavantajları vardır, çünkü mantığı yeniden kullanılabilir bir şablonla birleştirmeniz gerekiyorsa iki seçeneğiniz vardır: mantığı kısmi kullanan her denetleyicide çoğaltın veya mantığı kısminin kendisine gömün.

Kısmi bölümler kopyala/yapıştır çoğaltma hatalarını önler ve bir şeyi yeniden kullanmanız gereken ilk birkaç kez sorunsuz çalışırlar. Ancak deneyimlerimize göre, bölümler kısa sürede daha fazla işlevsellik ve mantık desteğiyle darmadağın oluyor. Ama mantık şablonlarda yaşamamalı!

Hücrelere Giriş

Neyse ki, hem kodu yeniden kullanmamıza hem de mantığı görüş dışında tutmamıza izin veren kısmilere daha iyi bir alternatif var. Adı, Trailblazer tarafından geliştirilen bir Ruby mücevher olan Cells. Hücreler, React ve Vue gibi ön uç çerçevelerdeki popülarite artışından çok önce vardı ve hem mantığı hem de şablonu ele alan kapsüllenmiş görünüm modelleri yazmanıza izin veriyorlar. Rails'in gerçekten kutudan çıkmadığı bir görünüm modeli soyutlaması sağlarlar. Aslında bir süredir Volan uygulamasında Hücreleri kullanıyoruz, ancak küresel, süper yeniden kullanılabilir bir ölçekte değil.

En basit düzeyde, Hücreler, bunun gibi bir parça işaretlemeyi soyutlamamıza izin verir (şablonlama dilimiz için Haml kullanıyoruz):

%div
  %h1 Hello, world!

Yeniden kullanılabilir bir görünüm modeline (bu noktada kısmilere çok benzer) ve bunu şuna çevirin:

= cell("hello_world")

Bu sonuçta, bileşeni, hücrenin kendisini değiştirmeden yardımcı sınıfların veya yanlış alt bileşenlerin eklenemeyeceği şekilde sınırlandırmamıza yardımcı olur.

Hücre Oluşturma

Tüm UI Hücrelerimizi bir app/cells/ui dizinine koyduk. Her hücre, son eki _cell.rb olan yalnızca bir Ruby dosyası içermelidir. Şablonları içerik_tag yardımcısıyla teknik olarak doğrudan Ruby'de yazabilirsiniz, ancak Hücrelerimizin çoğu aynı zamanda bileşen tarafından adlandırılan bir klasörde yaşayan karşılık gelen bir Haml şablonu içerir.

İçinde mantığı olmayan süper basit bir hücre şuna benzer:

// cells/ui/slat_cell.rb

module UI
  class SlatCell < ViewModel
    def show
    end
  end
end

Show yöntemi, hücreyi başlattığınızda oluşturulan şeydir ve klasörde hücreyle aynı ada sahip ilgili show.haml dosyasını otomatik olarak arayacaktır. Bu durumda, app/cells/ui/slat'tır (tüm UI Hücrelerimizi UI modülüne dahil ederiz).

Şablonda, hücreye iletilen seçeneklere erişebilirsiniz. Örneğin, hücre = cell(“ui/slat”, title: “Title”, alt başlık: “Subtitle”, label: “Label”) gibi bir görünümde örneklenirse, bu seçeneklere options nesnesi aracılığıyla erişebiliriz.

// cells/ui/slat/show.haml
.slat
  .slat__inner
    .slat__content
      %h4= options[:title]
      %p= options[:subtitle]
      = icon(options[:icon], color: "blue")

Çoğu zaman, bir seçenek yoksa boş öğelerin oluşturulmasını önlemek için basit öğeleri ve değerlerini hücredeki bir yönteme taşırız.

// cells/ui/slat_cell.rb
def title
  return unless options[:title]
  content_tag :h4, options[:title]
end

def subtitle
  return unless options[:subtitle]
  content_tag :p, options[:subtitle]
end
// cells/ui/slat/show.haml
.slat
  .slat__inner
    .slat__content
      = title
      = subtitle

Bir UI yardımcı programı ile Hücreleri Sarma

Bunun büyük ölçekte işe yarayabileceği fikrini kanıtladıktan sonra, bir hücreyi aramak için gereken gereksiz işaretlemeyi ele almak istedim. Sadece tam olarak doğru akmıyor ve hatırlaması zor. Bu yüzden onun için küçük bir yardımcı yaptık! Şimdi sadece = ui “name_of_component” diyebilir ve satır içi seçenekleri iletebiliriz.

= ui "slat", title: "Title", subtitle: "Subtitle", label: "Label"

Seçenekleri satır içi yerine blok olarak geçirme

UI yardımcı programını biraz daha ileri götürdüğümüzde, bir satırda bir dizi seçeneğin bulunduğu bir hücrenin takip edilmesinin çok zor ve sadece çirkin olacağı çabucak ortaya çıktı. Burada, satır içi olarak tanımlanmış birçok seçeneğe sahip bir hücre örneği verilmiştir:

= ui “slat", title: “Title”, subtitle: “Subtitle”, label: “Label”, link: “#”, tertiary_title: “Tertiary”, disabled: true, checklist: [“Item 1”, “Item 2”, “Item 3”]

Bu, Cells setter yöntemlerini engelleyen ve bunları daha sonra seçeneklerle birleştirilen karma değerlere çeviren OptionProxy adında bir sınıf yaratmamıza neden olan çok hantaldır. Bu size karmaşık geliyorsa endişelenmeyin – benim için de karmaşık. İşte kıdemli yazılım mühendislerimizden Adam'ın yazdığı OptionProxy sınıfının bir özeti.

İşte hücremizde OptionProxy sınıfını kullanmanın bir örneği:

module UI
  class SlatCell < ViewModel
    def show
      OptionProxy.new(self).yield!(options, &block)
      super()
    end
  end
end

Şimdi bunu yerine getirdiğimizde, hantal satır içi seçeneklerimizi daha hoş bir bloğa dönüştürebiliriz!

= ui "slat" do |slat|
  - slat.title = "Title"
  - slat.subtitle = "Subtitle"
  - slat.label = "Label"
  - slat.link = "#"
  - slat.tertiary_title = "Tertiary"
  - slat.disabled = true
  - slat.checklist = ["Item 1", "Item 2", "Item 3"]

mantığı tanıtmak

Bu noktaya kadar örnekler, görünümün gösterdiği şeyle ilgili herhangi bir mantık içermedi. Bu, Cells'in sunduğu en iyi şeylerden biri, o yüzden hadi bunun hakkında konuşalım!

Slat bileşenimize bağlı kalarak, bir bağlantı seçeneğinin bulunup bulunmadığına bağlı olarak, bazen her şeyi bir bağlantı olarak ve bazen de bir div olarak oluşturmamız gerekir. Bunun bir div veya bağlantı olarak işlenebilecek elimizdeki tek bileşen olduğuna inanıyorum, ancak bu, Hücrelerin gücünün oldukça güzel bir örneği.

Aşağıdaki yöntem, seçeneklerin varlığına bağlı olarak bir link_to veya bir content_tag yardımcısını çağırır [:link] .

Not: Bu, Hücreler ile tüm bu UI geliştirme yöntemini oluşturmamıza yardım etmede son derece etkili olan Adam Lassek tarafından ilham alındı ​​ve oluşturuldu.

def container(&block)
  tag =
    if options[:link]
      [:link_to, options[:link]]
    else
      [:content_tag, :div]
    end

  send(*tag, class: “slat__inner”, &block)
end

Bu, şablondaki .slat__inner öğesini bir kapsayıcı blokla değiştirmemize olanak tanır:

.slat
  = container do
  ...

Hücrelerde çok kullandığımız bir başka mantık örneği, koşullu çıktı sınıflarıdır. Diyelim ki hücreye devre dışı bir seçenek ekledik. Hücrenin çağrılmasında başka hiçbir şey değişmez, artık bir disable: true seçeneğini iletebilir ve her şeyin devre dışı duruma dönüşmesini (tıklanamayan bağlantılarla grileştirilmiş) izlemeniz dışında hiçbir şey değişmez.

= ui "slat" do |slat|
  ...
  - slat.disabled = true

Devre dışı seçeneği doğru olduğunda, istenen devre dışı görünümü elde etmek için gerekli olan şablondaki öğelere sınıflar ayarlayabiliriz.

.slat{ class: possible_classes("--disabled": options[:disabled]) }
  .slat__inner
    .slat__content
      %h4{ class: possible_classes("--alt": options[:disabled]) }= options[:title]
      %p{ class: possible_classes("--alt": options[:disabled]) }=
      options[:subtitle]
      = icon(options[:icon], color: "gray")

Geleneksel olarak, engelli durumda her şeyin doğru şekilde çalışması için hangi bireysel öğelerin ek sınıflara ihtiyaç duyduğunu hatırlamamız (veya stil kılavuzuna başvurmamız) gerekirdi. Hücreler, bir seçenek beyan etmemize ve ardından bizim için ağır yükü kaldırmamıza izin verir.

Not: Olası_sınıflar, Haml'de sınıfların koşullu olarak güzel bir şekilde uygulanmasına izin vermek için oluşturduğumuz bir yöntemdir.


Sunucu tarafı bileşenlerini kullanamadığımız yerler

Hücre yaklaşımı bizim özel uygulamamız ve çalışma şeklimiz için son derece yararlı olsa da, sorunlarımızın %100'ünü çözdüğünü söylemeyi ihmal etmiş olurum. Hala JavaScript (birçoğu) yazıyoruz ve uygulamamız boyunca Vue'da epeyce deneyim oluşturuyoruz. Zamanın %75'inde, Vue şablonumuz hala Haml'de yaşıyor ve Vue örneklerimizi içeren öğeye bağlıyoruz, bu da hücre yaklaşımından yararlanmamıza izin veriyor.

Ancak, bir bileşeni tek dosyalı bir Vue örneği olarak tamamen kısıtlamanın daha mantıklı olduğu yerlerde, Hücreleri kullanamayız. Örneğin, seçme listelerimizin tümü Vue. Ama bence sorun değil! Hem Hücrelerde hem de Vue bileşenlerinde bileşenlerin yinelenen sürümlerine sahip olma ihtiyacıyla gerçekten karşılaşmadık, bu nedenle bazı bileşenlerin %100 Vue ile, bazılarının ise Hücreler ile oluşturulmuş olması sorun değil. Bir bileşen Vue ile oluşturulmuşsa, bu, onu DOM'de oluşturmak için JavaScript'in gerekli olduğu anlamına gelir ve bunu yapmak için Vue çerçevesinden yararlanırız. Yine de diğer bileşenlerimizin çoğu için JavaScript gerektirmezler ve eğer yaparlarsa, DOM'nin önceden oluşturulmuş olmasını gerektirirler ve biz sadece bağlanıp olay dinleyicileri ekleriz.

Hücre yaklaşımında ilerlemeye devam ettikçe, kesinlikle hücre bileşenleri ve Vue bileşenlerinin kombinasyonunu deneyeceğiz, böylece bileşenleri oluşturmanın ve kullanmanın tek ve tek yolumuz olur. Henüz neye benzediğini bilmiyorum, o yüzden oraya vardığımızda o köprüyü geçeceğiz!


sonucumuz

Şimdiye kadar en çok kullanılan görsel bileşenlerimizden otuz tanesini Hücrelere dönüştürdük. Bize büyük bir üretkenlik patlaması sağladı ve geliştiricilere, oluşturdukları deneyimlerin doğru olduğuna ve birlikte hacklenmediğine dair bir doğrulama hissi veriyor.

Tasarım ekibimiz, uygulamamızdaki bileşenlerin ve deneyimlerin Adobe XD'de tasarladıklarıyla 1:1 olduğundan hiç olmadığı kadar emin. Bileşenlerdeki değişiklikler veya eklemeler artık yalnızca bir tasarımcı ve ön uç geliştirici ile etkileşim yoluyla gerçekleştirilir; bu, ekibin geri kalanının odaklanmış ve bir bileşeni bir tasarım modeliyle eşleştirmek için nasıl ince ayar yapılacağını bilme endişesinden uzak tutar.

UI bileşenlerini kısıtlama yaklaşımımızı sürekli olarak yineliyoruz, ancak umarım bu makalede gösterilen teknikler, bizim için neyin iyi çalıştığına dair size bir fikir verir!


Gel, Flywheel'de çalış!

Flywheel'de her departmanın müşterilerimiz ve kârlılığı üzerinde anlamlı bir etkisi vardır. Müşteri desteği, yazılım geliştirme, pazarlama veya bunların arasındaki herhangi bir şey olsun, hep birlikte insanların gerçekten aşık olabileceği bir barındırma şirketi oluşturma misyonumuz için çalışıyoruz.

Ekibimize katılmaya hazır mısınız? İşe alıyoruz! Buraya başvurun.