JavaScript

Web Component Nedir? Nasıl Kullanılır?

Günümüzde birçok modern web uygulaması, bileşenler(components) kullanılarak oluşturulmuştur. Bir uygulama eklemek için React gibi çerçeveler mevcut olsa da, web bileşenleri bu uygulamaları standart hale getirmeye ve tarayıcınızın bir parçası haline getirmeye çalışır.

Bu yazıda, Web Component’lerin ne olduğuna, bunları bir çerçeve olmadan nasıl oluşturabileceğimize ve geliştirme sırasında akılda tutulması gereken bazı sınırlamalara değineceğiz. Daha sonra, bir sonraki makalede, hafif bir çerçevenin (Lit gibi) daha büyük ölçekli uygulamalar oluşturmak isteyenler için nasıl yaşam kalitesi iyileştirmeleri sağlayabileceğini göstereceğiz.

Web Component Nedir? Nasıl Kullanılır?
Web Component Nedir? Nasıl Kullanılır?

Web Component(Web Bileşeni) Nedir?

Web bileşenlerinin ne olduğu hakkında bile birçok yanlış anlama var. Bazıları bunun yalnızca özel kullanıcı arabirimi, stil ve mantıkla özel öğeleri tek bir konsolide yerde oluşturma yeteneği olduğunu varsaysa da (daha fazlası için), kesinlikle daha fazlası var

Web bileşenleri, birlikte kullanıldıklarında, benzer işlevsellik sunan React gibi bir çerçeve kullanmaya uygun bir alternatif sunabilen 3 farklı web standardının bir karışımıdır. Bu web standartları şunlardan oluşur:

  1. Custom elements – ilgili HTML etiketi eklendiğinde benzersiz kullanıcı arayüzü ve uygulama mantığı sağlayacak yeni öğeler oluşturma yeteneği sağlar.
  2. Shadow DOM – Belirli öğeleri ana belge DOM’unuzdan bölümlere ayırarak belge çakışma sorunlarından kaçınmanıza olanak tanıma yeteneği sağlar.
  3. HTML templates – Sayfaya çizilmemiş HTML yazmanıza izin veren, ancak başka bir yerde yeniden kullanmak üzere biçimlendirme için bir şablon olarak kullanılabilen öğeler sağlar.

Shadow DOM ve HTML templates uygulamalarda kuşkusuz yararlı olsa da, web bileşenlerini bir bütün olarak tanıtmaya başlamak için en kolay yer olduklarını düşündüğümüzden, bugün özel öğelere odaklanacağız.

Bunlar Web Bileşenlerinin tek resmi belirtimleri olsa da, uyumlu bir geliştirme deneyimi oluşturmak için genellikle diğer JavaScript ve tarayıcı özellikleriyle birlikte kullanılırlar.

Sık kullanılan bu özelliklerden biri JavaScript Modülleridir. Uygulamanızı birden çok dosyaya bölme kavramı, Webpack gibi paketleyicilerde bir süredir olağan olmakla birlikte, tarayıcıya entegre olmak oyunun kurallarını değiştiriyor.

Custom Element(Özel Öğe) nedir?

Özünde, özel öğeler esasen yeni HTML etiketleri oluşturmanıza olanak tanır. Bu etiketler daha sonra uygulamanız boyunca kullanılabilecek özel kullanıcı arabirimi ve mantığı uygulamak için kullanılır.

Bu bileşenler, stilize edilmiş bir düğme kadar basit veya iş mantığınızla birlikte uygulamanızın tam bir sayfası kadar karmaşık olabilir.

HTML etiketlerini doğrudan tek bir DOM öğesine eşleme olarak düşünme eğiliminde olsak da, özel öğelerde durum her zaman böyle değildir. Örneğin, yukarıdaki örnekteki “sayfa-header” etiketi, alt öğelerinin listesi olarak “nav” ve “a” öğelerini içerebilir.

Bu nedenle, daha iyi bir akışla okumak için tek bir dosyada görünen etiket miktarını azaltarak bir uygulamanın organizasyonunu iyileştirebiliriz.

Ancak özel öğeler yalnızca HTML’den oluşmaz – JavaScript mantığını bu etiketlerle de ilişkilendirebilirsiniz! Bu, mantığınızı ilişkili kullanıcı arayüzünün yanında tutmanıza olanak tanır. Başlığınızın JavaScript tarafından desteklenen bir açılır menü olduğunu söyleyin. Artık bu JavaScript’i “page-header” bileşeninizin içinde tutabilir ve mantığınızı bir arada tutabilirsiniz.

Son olarak, bileşenlerin sağladığı önemli bir gelişme, birleştirilebilirliktir. Bu bileşenleri farklı sayfalarda kullanabilir ve başlık kodunuzu sayfalar arasında senkronize halde tutabilirsiniz. Bu, standart bileşenlerde (bir sayfada birden çok farklı boyutta düğme olması gibi) kullanıcılarınızın kafasını karıştırabilecek varyasyonlara sahip olma potansiyelini azaltır. Mevcut bileşenlerinizi kullanma konusunda dikkatli olduğunuz sürece, uygulamanızı bu şekilde daha tutarlı hale getirebilirsiniz.

Yaşam Döngüsü Metotları

Bileşenlerin birçok uygulamasında farklılıklar olsa da, oldukça evrensel olan bir kavram “yaşam döngüsü metotları”dır. Özünde, yaşam döngüsü yöntemleri, bir öğede olaylar meydana geldiğinde kod çalıştırmanıza olanak tanır. Sınıflardan uzaklaşan React gibi çerçeveler bile, bir bileşen bir şekilde değiştirildiğinde hala benzer eylemler gerçekleştirme kavramlarına sahiptir.

Tarayıcının uygulanmasına dahil edilen bazı yaşam döngüsü yöntemlerine bir göz atalım.

Özel öğeler, bir bileşene eklenebilecek 4 yaşam döngüsü yöntemine sahiptir.

connectedCallbackDOM’a eklendiğinde çalıştırılır
disconnectedCallbackDOM’a bağlantısı kaldırıldığında çalıştırılır
attributeChangedCallbackWeb bileşeninin özniteliklerinden biri değiştirildiğinde çalıştırılır.
adoptedCallbackBir HTML belgesinden diğerine taşındığında çalıştırılır.
Yaşam Döngüsü Metotları

Her birinin kendi kullanımları olsa da, öncelikle ilk 3’e odaklanacağız. attributeChangedCallback, öncelikle niş durumlarda kullanışlıdır ve bu nedenle basit bir demo yapmak zordur.

Artık yaşam döngüsü yöntemlerinin ne olduğunu bildiğimize göre, eylem halindeki bir örneğini görelim.

Web Component Örnek

Bahsedeceğimiz ilk iki yaşam döngüsü yöntemi tipik olarak bir çift olarak birlikte kullanılır: connectedCallback ve disconnectedCallback

DOM’a bir bileşen eklendiğinde connectCallback çalıştırılır. Bu, öğenin gösterilmesini istediğinizde innerHTML’nizi değiştirebileceğiniz, öğelere olay dinleyicileri ekleyebileceğiniz veya bileşeninizi kurmak için başka herhangi bir tür kod mantığı yapabileceğiniz anlamına gelir.

Bu arada, öğe DOM’dan kaldırılırken disconnectedCallback çalıştırılır. Bu genellikle, connectCallback sırasında eklenen olay dinleyicilerini kaldırmak veya öğe için gereken diğer temizleme biçimlerini yapmak için kullanılır.

İşte “Merhaba dünya” metniyle bir başlık oluşturan basit bir web bileşeni örneği.

Kodları çalışan bir örnek üzerinde görelim.

Özellik Değiştirildi (attributeChanged)

Bir öğeye veri aktarmanın başka yöntemleri olsa da (ki buna birazdan değineceğiz), niteliklerin yadsınamaz basitliğini inkar etmek zordur. HTML spesifik etiketlerinde yaygın olarak kullanılırlar ve çoğu görüntü özel öğesi, bir üst öğeden önemsiz bir şekilde veri iletmek için öznitelikleri kullanabilmelidir.

AttributeChangedCallback, bir özniteliğin değerinin ne zaman değiştirildiğini algılamak için kullanılan yaşam döngüsü yöntemi olsa da, bileşene hangi özniteliklerin izleneceğini söylemelisiniz.

Örneğin, bu örnekte mesaj özniteliğini izliyoruz. Mesaj özniteliği değeri değişirse, this.render()‘ı çalıştırır. Ancak, başka hiçbir özniteliğin değer değişikliği, AttributeChangedCallback tetiklemeyecektir çünkü başka hiçbir şey izlenecek şekilde işaretlenmemiştir.

attributeChangedCallback“in, değiştirilen özniteliğin adını, önceki değerini ve mevcut değerini aldığını fark edeceksiniz. Bu, ayrıntılı manuel değişiklik algılama optimizasyonları için kullanışlıdır.

Ancak, değerleri bir bileşene iletmek için öznitelikleri kullanmanın sınırlamaları vardır. Bu sınırlamaları açıklamak için önce serileştirilebilirlik hakkında konuşarak başlamalıyız.

Serileştirme

Serileştirme, bir veri yapısını veya nesnesini daha sonra saklanabilecek ve yeniden oluşturulabilecek bir biçime dönüştürme işlemidir. Basit bir serileştirme örneği, verileri kodlamak için JSON kullanmaktır.

Bu JavaScript nesnesi basit olduğundan ve yalnızca ilkel veri türlerini kullandığından, bir dizeye dönüştürmek nispeten önemsizdir. Bu dize daha sonra bir dosyaya kaydedilebilir, HTTP üzerinden bir sunucuya (ve geri) gönderilebilir ve verilere yeniden ihtiyaç duyulduğunda yeniden oluşturulabilir.

Serileştirme Sınırlamaları

Basit nesneler ve diziler nispeten önemsiz bir şekilde serileştirilebilirken, sınırlamalar vardır. Örneğin, aşağıdaki kodu alın:

Bu kodun davranışı, geliştiriciler olarak okumamız için basit görünse de, bunu bir makinenin bakış açısından düşünün.

Bu nesneyi bir istemciden bir sunucuya, yöntem bozulmadan uzaktan göndermek istiyorsak, bunu nasıl yapmalıyız?

window, tarayıcıda mevcutken, sunucunun muhtemelen yazıldığı NodeJS’de mevcut değildir. Window nesnesini seri hale getirmeye ve yöntemle birlikte iletmeye çalışmalı mıyız? Windownesnesindeki yöntemlerden ne haber? Bu yöntemlerle aynı şeyi yapmalı mıyız?

Ölçeğin diğer ucunda, console.log hem NodeJS’de hem de tarayıcılarda uygulanırken, her iki çalışma zamanında da yerel kod kullanılarak uygulanır. İstesek bile yerel yöntemleri seri hale getirmeye nasıl başlayabiliriz? Belki makine kodunu geçebiliriz? Güvenlik endişelerini görmezden gelsek bile, bir kullanıcının ARM cihazı ile bir sunucunun x86_64 mimarisi arasındaki makine kodundaki farklılıkları nasıl ele alabiliriz?

Tüm bunlar, sunucunuzun NodeJS çalıştırmadığını düşünmeden önce bir sorun haline gelir. Bunun kavramını Java gibi bir dilde temsil etmeye nasıl başlarsınız? JavaScript ve C++ gibi dinamik olarak yazılmış bir dil arasındaki farkları nasıl ele alırsınız?

Fonksiyonları Dizgeye Dönüştürelim

Artık serileştirme işlevleriyle ilgili sorunları bildiğinize göre, JSON.stringify() öğesini obj üzerinde çalıştırırsanız ne olacağını merak edebilirsiniz.

Anahtarı JSON dizesinden çıkarır. Bu, ilerlerken akılda tutulması önemlidir.

HTML Özellik Dizeleri

Bu yazıda neden serileştirmeden bahsediyoruz? Bunu yanıtlamak için HTML öğeleriyle ilgili iki gerçeği belirtmek istiyorum.

  • HTML özellikleri büyük/küçük harfe duyarsızdır
  • HTML öznitelikleri dize olmalıdır

Bu gerçeklerden ilki, herhangi bir öznitelik için anahtar kasasını değiştirebilirsiniz ve o da aynı şekilde yanıt verecektir. HTML spesifikasyonuna göre, aşağıdakiler arasında fark yoktur:

ve

İkinci gerçek, bu tartışmada bizim için çok daha alakalı. Bir özniteliğe dize olmayan değerler atayabilirsiniz gibi görünse de, bunlar her zaman kaputun altındaki dizeler olarak ayrıştırılır.

Bir özniteliğe dize olmayan değerler atamak için yanıltıcı olmayı ve JavaScript kullanmayı düşünebilirsiniz:

Ancak, özniteliğin atanan değeri beklentilerinize uymayabilir:

Öznitelikte parantez bulunmadığını fark edeceksiniz. Bunun nedeni, JavaScript’in dizinizde örtük olarak toString çalıştırıyor olması ve bu da onu özniteliğe atamadan önce onu bir dizgeye dönüştürmesidir.

Nasıl döndürdüğünüz önemli değil – niteliğiniz bir dize olacaktır.

Bu nedenle, dize olmayan değerler için öznitelikleri kullanmaya çalışırken, aksi takdirde beklenmeyen davranışlarla karşılaşabilirsiniz. Bu, girdi gibi yerleşik öğeler için bile geçerlidir.

Bu HTML öznitelik sınırlamasının farkında olmadan, onay kutusunun işaretlenmemiş olmasını bekleyebilirsiniz. Ancak, işlendiğinde, işaretli görünüyor.

Bunun nedeni, yanlış Boolean değerini geçmemeniz, (kafa karıştırıcı bir şekilde) doğru olan “False” dizesini geçmenizdir.

Bazı nitelikler, bir nitelik aracılığıyla bir öğeye bir sayı veya başka bir ilkel değer atamayı düşündüğünüzde bunu bilecek kadar akıllıdır, ancak uygulama dahili olarak şöyle görünebilir:

Bu, HTML öğesinin özniteliklerin seri durumdan çıkarılmasının kapsamı olma eğiliminde olsa da, bu işlevi daha da genişletebiliriz.

String Dizisi Geçme

Birazdan değindiğimiz gibi, JavaScript’in setAttribute özelliğini kullanarak bir özniteliğe bir dizi iletmeyi denersek, bu parantezleri içermeyecektir. Bunun nedeni Array.toString()‘in çıktısıdır.

JS’den bir özniteliğe ["deneme", "hayri", "merhaba"] dizisini geçirmeye çalışırsak, çıktı şöyle görünür:

Bu, HTML öğesinin özniteliklerin seri durumdan çıkarılmasının kapsamı olma eğiliminde olsa da, bunu toString’in çıktısı nedeniyle genişletebiliriz, öznitelik değerini tekrar bir dizgeye dönüştürmek zordur. Bu nedenle, yalnızca bir

etiketinin içindeki verileri görüntüleriz. Ancak listeler tek bir paragraf etiketine ait değildir! Listedeki her bir öğe için ayrı liste içeren bir ul’ye aittirler. Sonuçta, semantik HTML, erişilebilir bir web sitesi için ayrılmazdır!

Bunun yerine, bu verileri seri hale getirmek için JSON.stringify’ı kullanalım, bu dizeyi öznitelik değerine iletelim, ardından JSON.parse.unctionality kullanarak öğede bunu seri durumdan çıkaralım.

Dize Dizisini Geç
Birazdan değindiğimiz gibi, JavaScript’in setAttribute özelliğini kullanarak bir özniteliğe bir dizi iletmeyi denersek, bu parantezleri içermeyecektir. Bunun nedeni Array.toString()’in çıktısıdır.

JS’den bir özniteliğe ["deneme", "hayri", "merhaba"] dizisini geçirmeye çalışırsak, çıktı şöyle görünür:

Bu metodu kullanarak render metodumuzda bir dizi elde edebiliyoruz. Oradan, li öğeleri oluşturmak için bu dizinin haritasını çıkarırız, ardından bunu innerHTML’mize iletiriz.

Nesne Geçme

Bir dizi dizi, serileştirme özniteliklerinin basit bir gösterimi olsa da, gerçek dünyadaki veri yapılarını pek temsil etmez.

Verilerimizi daha gerçekçi hale getirmek için çalışmaya başlayalım. Dize dizimizi bir dizi nesneye dönüştürmek iyi bir başlangıç olabilir. Sonuçta, bir yapılacaklar uygulamasında öğeleri “tamamlandı” olarak işaretleyebilmek istiyoruz.

Şimdilik küçük tutacağız ve daha sonra büyüteceğiz. Yapılacaklar öğesinin “adını” ve tamamlanıp tamamlanmadığını takip edelim:

Özel öğemizi kullanarak bunu nasıl makul bir şekilde gösterebileceğimize bir göz atalım:

Fonksiyonla Nesneleri Geçirme

Bir özel öğedeki kullanıcı girdisinin bir ebeveynin veri kümesiyle etkileşime girmesini sağlamanın birçok yolu olsa da, her bir yapılacaklar nesnesinde bir yöntem depolayalım ve onu özel öğeye aktaralım.

Bu model, verilerin tek yönlü geçmesini sağlayarak bileşenler için en iyi uygulamaları takip eder. Geçmişte, hem React hem de Web Bileşenleri için bileşenlerinizi nasıl tek yönlü tutacağınıza değinmiştik.

Benzer bir şeyi yansıtmak için yapılacaklar nesnesini değiştirelim:

Ardından, ilgili yapılacaklar nesnesini değiştirmek için kimliği kullanarak toggleTodoItem yöntemimizi uygulayacağız:

Bu değişikliklerle, onay kutusu mantığını işlemek için ebeveynimizden ihtiyacımız olan tüm mantığa sahibiz. Şimdi onay kutusu işaretlendiğinde onChange yöntemini tetiklemek için özel öğemizi güncellememiz gerekiyor. Bir olay dinleyicisini “input” öğesini bağlamak için, alttaki HTMLElement referansına erişmemiz gerekir. Bunu yapmak için, daha önce kullandığımız innerHTML mantığından document.createElement lehine geçiş yapmamız gerekecek.

Mükemmel! Şimdi gerekli tüm değişiklikleri yaptık, hep birlikte çalışıp çalışmadığını görelim!

Ah… Garip… Onay kutularımız güncelleniyor gibi görünse de, h1’imiz değil. Dahası, geliştirici konsolumuza bakarsak, yeniden oluşturma sırasında görmeyi beklediğimiz console.log’ları görmüyoruz.

Nedenmiş?

Peki, serileştirme sınırlamaları ile ilgili bölümümüzde bahsettiğimiz gibi, fonksiyonlar seri hale getirilemez. Bu nedenle, yöntemleri olan bir nesne JSON.parse’a geçirildiğinde, bu anahtarlar kaldırılır. Olay dinleyicimizi eklerken işlev tanımsızdır ve bu nedenle hiçbir şey yapmaz.

Onay kutusunun verilerimize yansımadan görsel olarak güncellenmesi durumu, DOM ile DOM’u oluşturmak için kullandığımız veriler arasındaki yanlış hizalamaya bir örnektir.

Ancak, serileştirme sorunları dışında kodumuzun doğru olduğunu doğrulayabiliriz. Bu kod satırını doğrudan toggleTodoItem global işlevini kullanacak şekilde değiştirirsek, beklendiği gibi çalışır:

Bu, mevcut kurulumumuz için geçerli olsa da, özel öğeler oluşturmanın avantajlarından biri, uygulamanızın kod tabanını düzenli tutmak için uygulamanızı birden çok dosyaya bölme yeteneğidir. toggleTodoItem artık özel öğeyle aynı kapsamda olmadığında bu kod bozulur.

Bu uzun vadeli iyi bir çözüm değilse, serileştirmeyle ilgili sorunumuzu çözmek için ne yapabiliriz?

Nitelik ve Özelliklerle Parametre Geçme

Nitelikler, ilkel verileri özel öğelerinize iletmek için basit bir yöntem sağlar. Ancak, gösterdiğimiz gibi, verilerinizi seri hale getirme gereksinimi nedeniyle daha karmaşık kullanımda düz kalır.

Nitelikleri kullanarak bu sınırlamayı atlayamayacağımızı bildiğimizden, verileri daha doğrudan iletmek için JavaScript sınıflarından yararlanalım.

Bileşenlerimiz HTMLElement öğesini genişleten sınıflar olduğundan, özel öğemizin üst öğesinden özelliklerimize ve yöntemlerimize erişebiliriz. Diyelim ki, özellik değiştirildiğinde yapılacakları güncellemek ve işlemek istiyoruz.

Bunu yapmak için, bileşenimizin sınıfına “setTodos” adlı bir yöntem ekleyeceğiz. Bu yöntem, daha sonra Document.querySelector kullanarak öğemizi sorguladığımızda erişilebilir olacaktır.

Şimdi, yapılacaklar listemizdeki öğeleri değiştirirsek, h1 etiketimiz beklediğimiz gibi güncellenir: DOM’umuz ve veri katmanımız arasındaki uyuşmazlığı çözdük!

Özel öğelerimizin özelliklerini güncellediğimiz için, buna “özniteliklerden geçme” serileştirme sorunlarını çözen “özellikler üzerinden geçme” adını veriyoruz.

Ama hepsi bu değil! Özelliklerin, veri geçişi için de özniteliklere göre gizli bir avantajı vardır: bellek boyutu.

Yapılacak işlerimizi niteliklere serileştirirken verilerimizi çoğaltıyorduk. Yalnızca yapılacaklar listesini JavaScript’imizde bellekte tutmakla kalmıyor, aynı zamanda tarayıcı yüklü DOM öğelerini de bellekte tutuyor. Bu, eklediğimiz her yapılacak iş için yalnızca JavaScript’te değil, DOM’de de (öznitelik dizesi aracılığıyla) bir kopyasını tuttuğumuz anlamına gelir.

Ama kesinlikle, mülklere geçiş yaparken belleği geliştirmenin tek yolu bu, değil mi? Yanlış!

Unutmayın, ana komut dosyası etiketimizde JS’de ve DOM aracılığıyla tarayıcıda bellek içi yüklenmenin yanı sıra, onu özel öğemizde de seri durumdan çıkarıyorduk! Bu, aynı anda bellekte başlatılan verilerimizin üçüncü bir kopyasını tuttuğumuz anlamına geliyordu!

Bu performans değerlendirmeleri bir demo uygulamasında önemli olmasa da, üretim ölçeğindeki uygulamalarda önemli komplikasyonlar ekler.

1 Yorum

Yorum yapmak için tıklayın