Rehberler
Bileşen Kütüphanesi Yazmak
Uygulamaların kullandığı bir bileşen kütüphanesinin kuralları; prop'ların tek bir bağla izlenmesi, geliştiricinin verisinin olduğu gibi kullanılması, bileşenin içinde hiçbir şeyin izlenmemesi, elle çizilen parçalar, değişince yazma, geliştiricinin bileşenine geçici öznitelik, olayın geldiği bileşen, ömür tuzakları ve ölçme.
Bir bileşen kütüphanesi (kontroller, bir veri ızgarası, bir planlayıcı), tanımadığı uygulamalarda ve çoğu zaman bir sayfada binlerce parçasıyla kullanılır. Uygulama kodu reaktiviteye yaslanabilir; kütüphane kodu yaslanmamalıdır. Bu sayfadaki kurallar eksiksiz bir kit yazılıp ölçülerek çıkarıldı. Önce Bileşen Oluşturma ve Bileşen Bağları sayfalarını okuyun.
Üç giriş
Bir kütüphane bileşeni yalnızca üç şeyi dinler:
- Prop'ları: her biri bir değer ya da bir fonksiyon (
Bind<T>). - Geliştiricinin verisi: olduğu gibi kullanılır. Aynı nesneler tutulur ve geri verilir;
reactive()ile sarılmaz, kopyalanmaz, dönüştürülmez. Yerinde yapılan bir değişiklikreState()ya da açık birrefresh()ile yeniden okunur. - Geliştiricinin
reState()çağrısı.
Geri kalan her şey bileşenin kendi durumudur ve bileşen onu elle çizer.
Prop'lar: değer ya da fonksiyon, tek bir bağla izlenir
- Her prop ve her özellik setter'ı bir değer ya da bir fonksiyon alır. Fonksiyon, bileşenin tek bir bağıyla
izlenir. Bu bağ
bindings.add(binding)ile kaydedilir (IBaseBindingbiçimi, tipi@motifx/core'dan alınır; bkz. Bileşenin özelliğine bağ). Bileşenin bağ kaydında olduğu için geliştiricininreState()çağrısı ona ulaşır; düz veri de reaktif veri kadar çalışır. - Değer, son okunandan farklıysa (
Object.is) iletilir. Aynı değeri bulan birreState(), kullanıcının o arada yaptığını (yazdığı metni, geçtiği günü) bozmaz. - Koddan atanan bir özellik, fonksiyonunu izlemeyi bırakır.
- Bir özelliği çizmek için effect değil, bir uygulayıcı listesi kullanılır: değer şimdi ve her değişimde, izlenmeden uygulanır. Düz değer olarak verilen bir özellik için hiç effect oluşmaz.
- Alanları fonksiyon olabilen veri nesneleri (menü öğeleri, araç çubuğu öğeleri, özet öğeleri): bir kaynağın bütün alanları tek bir bağla okunur, okunan değerler saklanır ve çizim onlardan yapılır. Çizerken geliştiricinin fonksiyonu yeniden çağrılmaz; yeni bir düğüm döndüren bir fonksiyon her çizimde yeni bir düğüm üretir.
- Kullanıcının da değiştirdiği bir değer (metin kutusu, liste, onay kutusu) kontrole yalnızca kaynağı değiştiğinde verilir, her çizimde değil; yoksa kullanıcının yazdığı ezilir:
const handed = new WeakMap<object, { value: unknown }>();
export function handOver<V>(control: { value: V }, value: V) {
const last = handed.get(control);
if (last && Object.is(last.value, value)) return;
handed.set(control, { value });
control.value = value;
}
İçeride hiçbir şey izlenmez
- Değişiklik, yapıldığı yerde söylenir. Bileşenin durumunun her parçası
tell(ad)çağırır. Adlar toplanır ve turda bir kez duyurulur; her görünüm, söylenen adların ilgilendirdiği parçaları yeniden çizer. Durumu izleyen bir şey yoktur. - Bileşenin bir değişikliğe verdiği karşılık görünümlerden önce çalışır. Yeniden yükleme, yeniden yerleşim, bir
düzenleyiciyi kapatma önce çalışır; onların söyledikleri aynı tura katılır ve görünümler bir kez çizer.
- Böyle bir karşılık, okuduğu değerler son okuduklarından farklı olduğunda çalışır.
- Kararlı değerler karşılaştırılır: her okumada yeniden kurulan nesneler ve
Map'ler hiçbir zaman eşit çıkmaz; onlar JSON olarak anahtarlanır. - "Yeniden oku" sayacı gibi sayaçlar da anahtara girer.
- Yalnızca gerçek bir değişiklik söylenir. Her çizimde söylenen bir bildirim bir döngüdür (çiz, söyle, çiz) ve bir tarayıcı sekmesini kilitleyebilir.
- Elle çizilen parçalar kodla kurulan bileşenlerdir. Orada JSX kullanılmaz: derleyici, bir değişken okuyan her
ifadeyi, bir özniteliği bile, bir bağa çevirir.
bindings.watchve serbesteffect()da yoktur. Bir veri ızgarası sayfasında:- bağlar 1.306'dan 87'ye indi;
- açılışta çalışan effect'ler 2.478'den 346'ya indi;
- bir sıralama artık hiç effect çalıştırmıyor (önceden 140).
- Reaktif durum uygulama için kalabilir. "Reaktif" diye belgelenen bir API (bir toplam, seçili satır sayısı), uygulamanın kendi bağları için deposunu tutabilir; bileşen onu hiçbir zaman izleyerek okumaz.
- Dil ya da yerel ayar değişimi bir olaydır, sinyal değil. Her kontrol bir kez abone olur, metinlerini ve biçimlerini şimdi ve her değişimde çizer; abonelik kontrolle birlikte gider:
export function onLanguage(component: ComponentBase, draw: () => void) {
untracked(draw);
const changed = uiLocale.onChanged(() => !component.isDisposed && untracked(draw));
component.motif.setDisposable(() => changed.dispose());
}
- Değişmeyen içerik bir kez kurulur.
{cond && <X/>}vekeyverilen.mapsatırları öğe başına bağ açar. Değişmeyen içerikonConfigya dainitializeComponentiçindecontrols.addile bir kez kurulabilir. Bir doküman sayfasında bu, sayfanın effect sayısını 16 binden 10 bine indirdi.
Bileşenin ağacı
view()tek birComponentBasedöndürür. Birden çok parçamotifFragment()içine konur;<>…</>de buna derlenir.- Elemanın doğrudan çocuğu olması gereken parçalar (başlık hücreleri, taşınan satırlar),
view()'den döndürülmez;initializeComponent(bir override) içinde eklenir. - Anahtarlı parçalar elle eşlenir. Zaten yerinde olan bir düğüm hiçbir zaman taşınmaz: odaktaki elemanı taşımak odağını alır.
controls.remove(parça)parçayı hemen çıkarır.parça.dispose()elemanını bir tur sonra kaldırır; hemen ardından yapılan bir çizim elemanı hâlâ bulur.x-displaygibi yerinde gösterilip gizlenen bir parça, bir yorum yer tutucusuyla yer değiştirir ve oraya geri konur. Yer tutucu için bir ebeveyn gerekir; bu yüzden en üst düzeydeki parçalar ilk kezonBuiltiçinde çizilir.
Değişince yazma
- Bir öznitelik, stil ya da metni aynı değerle yeniden yazmak bile tarayıcıya stilleri yeniden hesaplatır. Her çizimde değişmemiş öznitelikleri yeniden yazan elle çizim kodu, yerini aldığı reaktif kodun beş katı stil hesaplaması yaptı. Bir değer yalnızca değiştiğinde yazılır.
- Bir constructable stylesheet'i (
replaceSync) aynı metinle değiştirmek, bütün belgenin stillerini geçersiz kılar. Bir sütun genişletilirken her adımda sütun stil sayfasını yeniden yazan bir ızgara, adım başına 60–110 ms harcadı. Sayfa yalnızca metni değiştiğinde yazılınca uzun görevler kalktı.
Geliştiricinin bileşenine geçici öznitelik
- Kütüphane, içine konan ve geliştiricinin yazdığı bir bileşene bir süreliğine öznitelik vermek zorunda kalabilir:
disabled,tabindex,aria-*.attr.addbuna uymaz: yazdığı anahtarda geliştiricinin bağını kaldırır ve eski değeri geri veremez. attr.hold, değeri bileşenin kendi değerinin üstünde tutar ve serbest bırakılabilir bir nesne döndürür:
const held = part.attr.hold({ disabled: true, tabindex: -1, "aria-disabled": "true" });
this.motif.register(held);
held.dispose()her anahtarı bileşenin o andaki kendi değerine döndürür.motif.registerile verildiği bileşen serbest bırakıldığında da aynısı olur.- Tutma sürerken bileşenin o anahtarlara yazdıkları elemana ulaşmaz, kenarda bekler: canlı bağın yeni değeri,
attr.add,attr.removevebindings.addile elemanın aynı adlı özelliğine yazan bağ.reState(),setState()ve bileşenin başka bir yere taşınması tutmayı bozmaz. - Geri gelen, tutma başladığında elemanda ne varsa odur;
attrile yazılmamış bir öznitelik de, hiç olmayan bir öznitelik de aynen döner. Bileşen tutma sırasında o anahtara yazdıysa son yazdığı gelir. - Aynı anahtar birden çok kez tutulabilir. Elemanda en son tutulan değer görünür; hangisi önce bırakılırsa bırakılsın, sonuncusu da bırakılınca bileşenin kendi değeri geri gelir.
attr.hold,attr.addile aynı kuralla yazar: verilen anahtar her elemana yazılır.disabled'ı yalnız onu tanıyan elemana vermek çağıranın işidir ("disabled" in part.element). Tutulan değer bir kez yazılır; fonksiyon verilirse izlenmez.
Olayın geldiği bileşen
- Kapsayan bir bileşende dinlenen olay, içindeki bileşenlerin birinden gelir. Olayın belli bir bileşenin içinden
başlayıp başlamadığını
motif.isEventFrom(e)söyler:
this.motif.on("click:capture", (_sender, e) => {
if (part.motif.isEventFrom(e)) return;
e.preventDefault();
e.stopImmediatePropagation();
});
- Elemanı olmayan bileşen, içerik elemanları için cevap verir. Cevap olay bittikten sonra sorulduğunda da aynıdır.
Ömür
- Bir bileşeni serbest bırakmak, kendi alanlarını
undefinedyapar. Sonradan gelen geri çağrılar (zamanlayıcılar, promise'ler, gözlemciler, ertelenmiş bir çizim) önceisDisposed'a bakar. onDisposing, eleman DOM'dan çıkmadan önce çalışır ve odaktaki bir elemanı kaldırmakfocusouttetikler. Bu yüzden birfocusoutişleyicisi, serbest bırakılmakta olan bir bileşende çalışabilir.- Odak gizlenen ya da pasifleşen bir kontrolden taşınacaksa sonraki kontrol
focus({ preventScroll: true })ile odaklanır; yoksa sayfa ona kayar. refgeri çağrıları, alt sınıfın alan başlatıcılarından önce, temel yapıcının içinde çalışır. Birref'in dokunabileceği durum sınıf alanlarında değil, gerektiğinde oluşturulan, sembol anahtarlı bir nesnede tutulur.
Uzun işler
Promise.thenvequeueMicrotasktarayıcının çizmesine izin vermez; bir mikro görev zinciri yine tek bir uzun görevdir. Uzun işler makro görevlerle (setTimeout,MessageChannel) parçalara bölünür. Ölçüm:- mikro görevlerle tek bir 300 ms'lik uzun görev;
setTimeoutparçalarıyla hiç uzun görev olmadan, 18 çizilmiş kare.
İşi doğrulamak
- Bağlar bileşen türü başına, bileşen ağacında dolaşılarak sayılır (DevTools
inspect(path).bindings); yalnızca canlı effect'ler sayılmaz. Bir prop'u izleyen bağın effect sahibi yoktur ve effect sayısında görünmez. - Bir görünüm yeniden yazılırken o görünümün her sayfasında eski ve yeni hâl piksel piksel karşılaştırılır. Her adımda testler ve e2e denetimleri yeşil tutulur; effect sayısının düştüğü kanıtlanır.
- Sayfayı
load'dan hemen sonra okuyan bir e2e denetimi önce içeriği bekler: sayfalar bölümleriniload'dan sonra çizer.
Sonraki adım
Kütüphaneyi yayımlamak için MotifJS için npm Kütüphanesi Yayımlama sayfasına geçin.