Temel kavramlar
Olaylar
DOM olayları ve değiştiriciler, motif.on/off/trigger bileşen olayları, yaşam döngüsü olayları, uygulama olay veriyolu ve Emitter ile servis olayları; JSX ile ve JSX'siz.
MotifJS'te üç olay katmanı vardır:
- DOM olayları — bir elemana
onclick,oninputgibi prop'larla, JSX'siz yazımdamotif.onile bağlanır. - Bileşen olayları —
component.motif.on/trigger/offile bileşenler arası iletişim. - Uygulama olayları —
context.on/fireile uygulama genelinde yayın.
Örnekler başlangıç şablonundaki not uygulamasının kesitleridir: not listesi (NotesLayout) ve düzenleyici (NotePage). Note ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındakilerdir.
this.state bileşenin kendi durumudur; this.remove ve this.close bileşenin metotlarıdır.
state bileşenin reactive ile kurduğu yerel durumdur; remove ve close bileşen fonksiyonunun içindeki yerel fonksiyonlardır.
DOM olayları
JSX'te on... prop'ları DOM olaylarına bağlanır; olay adları küçük harfle yazılır. JSX'siz yazımda aynı bağ motif.on(ad, işleyici) çağrısıdır:
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (this.state.query = (e.target as HTMLInputElement).value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (state.query = (e.target as HTMLInputElement).value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
const pin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ type: "search" });
field.motif.on("input", (_sender: ComponentBase, e: Event) => (this.state.query = (e.target as HTMLInputElement).value));
});
const form = motifComponent("form", (form: ComponentBase) => {
form.motif.on("submit", (_sender: ComponentBase, e: Event) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link: ComponentBase) => {
link.attr.add({ href: "/archive" });
link.motif.on("click:prevent", (sender: ComponentBase, _e: Event) => sender.context.navigate("/archive"));
link.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşiv")));
});
const pin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ type: "search" });
field.motif.on("input", (_sender: ComponentBase, e: Event) => (state.query = (e.target as HTMLInputElement).value));
});
const form = motifComponent("form", (form: ComponentBase) => {
form.motif.on("submit", (_sender: ComponentBase, e: Event) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link: ComponentBase) => {
link.attr.add({ href: "/archive" });
link.motif.on("click:prevent", (sender: ComponentBase, _e: Event) => sender.context.navigate("/archive"));
link.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşiv")));
});
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (this.state.query = e.target.value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (state.query = e.target.value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
const pin = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field) => {
field.attr.add({ type: "search" });
field.motif.on("input", (_sender, e) => (this.state.query = e.target.value));
});
const form = motifComponent("form", (form) => {
form.motif.on("submit", (_sender, e) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link) => {
link.attr.add({ href: "/archive" });
link.motif.on("click:prevent", (sender, _e) => sender.context.navigate("/archive"));
link.controls.add(motifComponent("text", (node) => node.setText("Arşiv")));
});
const pin = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field) => {
field.attr.add({ type: "search" });
field.motif.on("input", (_sender, e) => (state.query = e.target.value));
});
const form = motifComponent("form", (form) => {
form.motif.on("submit", (_sender, e) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link) => {
link.attr.add({ href: "/archive" });
link.motif.on("click:prevent", (sender, _e) => sender.context.navigate("/archive"));
link.controls.add(motifComponent("text", (node) => node.setText("Arşiv")));
});
Düz etikette her DOM olayı bu yazımla bağlanır (onpointerdown, onmouseenter, onanimationend dahil); onClick gibi camelCase yazım da aynı olayı bağlar.
Bir bileşen etiketinde DOM olay adı taşıyan prop (onchange/onChange, onCopy, onResize, onSelect, onReset, onContextMenu …) bileşenin elemanına DOM dinleyicisi olarak bağlanır ve bileşenin props'una ulaşmaz; camelCase yazımda derleyici MJX002 uyarısı verir. Geri çağrı prop'u için DOM olayıyla çakışmayan bir ad seçin (onValueChange, onConfirm). Elemanı olmayan (fragment) bileşende böyle bir dinleyicinin bağlanacağı eleman yoktur (bkz. Kök eleman).
İşleyici imzaları
İşleyici iki biçimden biriyle yazılır; MotifJS parametre sayısına göre doğru olanı çağırır. Kural motif.on için de aynıdır: tek parametreli işleyici olayı alır, göndereni değil:
<input type="text" onkeydown={(e) => e.key === "Enter" && notes.create()} /> {/* yalnız olay */}
<li onclick={(sender, e) => sender.class.add("selected")}>…</li> {/* gönderen + olay */}
const field = motifComponent("input", (input: ComponentBase) => {
input.attr.add({ type: "text" });
input.motif.on("keydown", (_sender: ComponentBase, e: KeyboardEvent) => e.key === "Enter" && notes.create());
});
const row = motifComponent("li", (li: ComponentBase) => {
li.motif.on("click", (sender: ComponentBase, _e: Event) => sender.class.add("selected"));
});
<input type="text" onkeydown={(e) => e.key === "Enter" && notes.create()} /> {/* yalnız olay */}
<li onclick={(sender, e) => sender.class.add("selected")}>…</li> {/* gönderen + olay */}
const field = motifComponent("input", (input) => {
input.attr.add({ type: "text" });
input.motif.on("keydown", (_sender, e) => e.key === "Enter" && notes.create());
});
const row = motifComponent("li", (li) => {
li.motif.on("click", (sender, _e) => sender.class.add("selected"));
});
sender— olayı tetikleyenComponentBaseörneğidir;sender.element,sender.context,sender.motifgibi üyelere erişebilirsiniz.- İşleyici
{ cancel: true }döndürürse MotifJSpreventDefault()vestopPropagation()çağırır.falsedahil diğer dönüş değerleri yok sayılır; bu yüzden yukarıdakionkeydownkısa gövdesi diğer tuşları iptal etmez. Bu metotlar yalnızca olay nesnesinde varsa çağrılır;motif.trigger("ad", { … })ile düz bir veri nesnesi geçildiğinde hiçbiri çağrılmaz ve hata oluşmaz.:prevent/:stopdeğiştiricileri de aynı kurala uyar. - Parametre sayısı fonksiyonun
lengthdeğeridir: bildirilen parametre sayısı ≤ 1 isefn(event), 2 ve üzeri isefn(sender, event)çağrılır. Varsayılan değerli ya da rest parametre ve ondan sonrakiler sayılmaz; bu yüzden(...a) => …0 sayılır ve yalnızcaeventalır,(s = null, e) => …de 0 sayılır. - TypeScript'te iki biçim de, düz etikette ve bileşen etiketinde, elemanın olayından tiplenir:
onkeydown={(e) => e.key}yazıncaeKeyboardEventolur,(s, e) => …biçiminde deeaynı tipi alır. İlk parametrenin anlamı parametre sayısına bağlı olduğu için olay ve bileşen birlikte tiplenir (KeyboardEvent & ComponentBase);(e) => …içinde olayın,(s, e) => …içinde bileşenin üyelerini kullanın.motif.on'un tipi(sender, e)imzasıdır; olayı kullanacaksanız iki parametre yazın.
İşleyici hataları
İşleyici hata fırlatırsa ya da async işleyicinin döndürdüğü promise reddedilirse hata MJX123 (The '<olay>' event handler threw.) koduyla bir MotifError olarak errorHandler'a raporlanır; asıl hata cause alanındadır. Rapor üretim kipinde de konsola yazılır (app.useLogging(false) kapatır) ve errorHandler.addListener(fn) dinleyicilerine ulaşır. Aynı olayın diğer dinleyicileri çalışmaya devam eder.
on:ad / on-ad / on_ad yazımı
Bu yazım işleyiciyi olduğu gibi etiketin bileşenine bağlar: <X on:saved={fn}/>, sender.motif.on("saved", fn) demektir. Olay adı, önek (on:, on-, on_) atıldıktan sonra kalan kısmın tamamıdır: on-my-event → my-event, on_my_event → my_event. Hem DOM olayları hem de motif.trigger ile tetiklenen özel olaylar için kullanılır ve yukarıdaki parametre sayısı kuralı aynen geçerlidir:
<DeleteConfirm note={note} on:confirm={(target: Note) => this.remove(target)} /> {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button> {/* iki parametre: gönderen + olay */}
<button type="button" on_click={this.close}>Kapat</button> {/* metot başvurusu olduğu gibi geçer */}
<DeleteConfirm note={note} on:confirm={(target: Note) => remove(target)} /> {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button> {/* iki parametre: gönderen + olay */}
<button type="button" on_click={close}>Kapat</button> {/* fonksiyon başvurusu olduğu gibi geçer */}
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm: ComponentBase) => confirm.motif.on("confirm", (_confirm: ComponentBase, target: Note) => this.remove(target)) });
const close = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", this.close);
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm: ComponentBase) => confirm.motif.on("confirm", (_confirm: ComponentBase, target: Note) => remove(target)) });
const close = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", close);
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
<DeleteConfirm note={note} on:confirm={(target) => this.remove(target)} /> {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button> {/* iki parametre: gönderen + olay */}
<button type="button" on_click={this.close}>Kapat</button> {/* metot başvurusu olduğu gibi geçer */}
<DeleteConfirm note={note} on:confirm={(target) => remove(target)} /> {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button> {/* iki parametre: gönderen + olay */}
<button type="button" on_click={close}>Kapat</button> {/* fonksiyon başvurusu olduğu gibi geçer */}
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm) => confirm.motif.on("confirm", (_confirm, target) => this.remove(target)) });
const close = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender, _e) => sender.motif.hide());
button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", this.close);
button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm) => confirm.motif.on("confirm", (_confirm, target) => remove(target)) });
const close = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender, _e) => sender.motif.hide());
button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", close);
button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
Silme onayı DeleteConfirm, "Sil" düğmesine basılınca kendi bileşeninde motif.trigger("confirm", note) çağırır (sınıfta ve Options'ta this.motif.trigger, fonksiyon bileşeninde döndürdüğü kökün motif.trigger'ı) ve ilk işleyici notu alır. JSX'siz yazımda dinleyici initializeComponent anahtarında kurulur; sınıf, fonksiyon ve Options bileşeninde aynıdır. Olay adı serbest olduğu için on:/on-/on_ prop'larının parametreleri any tiplidir; veriye kendi tipini parametrede verirsiniz ((target: Note) => …). motif.on'un tipi (sender, e) olduğundan JSX'siz yazımda veri ikinci parametredir.
Olay değiştiricileri (modifiers)
Olay adına iki nokta ile eklenen değiştiriciler davranışı ayarlar. Bunlar component.motif.on API'sinde ve tip sisteminde tanımlıdır:
| Değiştirici | Etki |
|---|---|
:once |
Bir kez çalışır, sonra kendini kaldırır. |
:passive |
addEventListener için passive: true. |
:capture |
Yakalama (capture) fazında dinler. |
:prevent |
İşleyici sonrası preventDefault(). |
:stop |
İşleyici sonrası stopPropagation(). |
:self |
Yalnızca olay hedefi elemanın kendisiyse çalışır. |
:trusted |
Yalnızca isTrusted olaylarda çalışır. |
JSX'te öznitelik adı tek bir : alabildiği için etikette öznitelik başına bir değiştirici yazılır; motif.on ile zincir kurulur. Değiştiricili prop da değiştiricisiz hâliyle aynı tipi alır; onkeydown:once={(e) => e.key} içinde e KeyboardEvent olur:
<form onsubmit:prevent={() => notes.create()}>…</form>
<button type="button" onclick:once={() => notes.create()}>İlk notu oluştur</button>
<div class="list" onscroll:passive={() => rememberScroll()}>…</div>
const form = motifComponent("form", (form: ComponentBase) => {
form.motif.on("submit:prevent", () => notes.create());
});
const first = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click:once", () => notes.create());
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("İlk notu oluştur")));
});
const list = motifComponent("div", (list: ComponentBase) => {
list.class.add("list");
list.motif.on("scroll:passive", () => rememberScroll());
});
<form onsubmit:prevent={() => notes.create()}>…</form>
<button type="button" onclick:once={() => notes.create()}>İlk notu oluştur</button>
<div class="list" onscroll:passive={() => rememberScroll()}>…</div>
const form = motifComponent("form", (form) => {
form.motif.on("submit:prevent", () => notes.create());
});
const first = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click:once", () => notes.create());
button.controls.add(motifComponent("text", (node) => node.setText("İlk notu oluştur")));
});
const list = motifComponent("div", (list) => {
list.class.add("list");
list.motif.on("scroll:passive", () => rememberScroll());
});
Bileşen olay API'si: motif.on / motif.off / motif.trigger
Her bileşende programatik olay yönetimi motif ad alanındadır. Örnekte satır tıklanınca bileşen kendi saved olayını tetikler; o olayın işleyicisi yalnız dahili olarak kaydedilmiştir:
const onSaved = (sender: ComponentBase, saved: Note) => notes.touch(saved);
this.motif.on("click", () => this.motif.trigger("saved", note));
this.motif.on("saved", onSaved, false);
this.motif.off("saved", onSaved);
const onSaved = (sender: ComponentBase, saved: Note) => notes.touch(saved);
row.motif.on("click", () => row.motif.trigger("saved", note));
row.motif.on("saved", onSaved, false);
row.motif.off("saved", onSaved);
const onSaved = (sender, saved) => notes.touch(saved);
this.motif.on("click", () => this.motif.trigger("saved", note));
this.motif.on("saved", onSaved, false);
this.motif.off("saved", onSaved);
const onSaved = (sender, saved) => notes.touch(saved);
row.motif.on("click", () => row.motif.trigger("saved", note));
row.motif.on("saved", onSaved, false);
row.motif.off("saved", onSaved);
Fonksiyon bileşeninde bu çağrılar bileşenin kendisi üzerindedir (örnekte row): JSX'te kökün onConfig kancasına ya da ref'ine gelen bileşen, JSX'siz yazımda kökü kuran motifComponent fonksiyonunun aldığı bileşen.
motif.onile eklenen dinleyiciler, bileşen serbest bırakıldığında otomatik olarak temizlenir.motif.on'un üçüncü argümanıdomEvent(varsayılantrue)'dur.falseverirseniz DOM'aaddEventListeneryapılmaz; yalnızca dahili (özel) olay olarak tutulur vemotif.triggerile tetiklenir.- Elemansız (fragment) bileşende DOM dinleyicisi bileşenin içeriğini dinler: üst düzey eleman çocuklarının her biri, iç içe fragment'lar dâhil. Sonradan eklenen çocuk kapsanır, ayrılan çocuk bırakılır;
:selfhedefin bu elemanlardan biri olması demektir;:oncevemotif.offhepsinde birden işler. motif.isEventFrom(e), bir DOM olayının bu bileşenin elemanından ya da onun içindeki bir elemandan başlayıp başlamadığını söyler. Elemansız (fragment) bileşen içerik elemanları için cevap verir. Cevap olay bittikten sonra sorulduğunda da aynıdır;motif.triggerile verilen veri bir DOM olayı olmadığı içinfalsedöner.motif.trigger("ad", veri)yalnız tam o adla (değiştiricisiz, büyük/küçük harf duyarsız) kaydedilmiş işleyicileri çağırır;"click:once"ile kaydedilen işleyiciyitrigger("click")çağırmaz. İşleyicilerveri'yi olay nesnesi olarak alır; DOM'a olay gönderilmez.:onceişleyicisini ona ilk ulaşan yol tüketir:trigger("click:once", veri)onu bir kez koşturup kaldırır, tıpkı bir DOM tıklaması gibi; ardındanmotif.options.hasEvent("click:once")falsedöner.- DOM olaylarında
motif.off(ad, fn)aynı işleyicinin bir kaydını kaldırır; aynı işleyici birden çok kez eklendiyse her biri için çağrılır. Olay adı kayıttaki yazımla (değiştiriciler dâhil) verilir. motif.addHandler(ad, fn),motif.on(ad, fn)ile aynıdır (dönüş değeri yok).motif.toggle()görünürse gizler, değilse gösterir.
x:mounted — DOM'a bağlanma anı
Eleman gerçekten DOM'a eklendiğinde çalışacak bir kanca; başlık alanına odak vermek gibi işler için:
this.motif.on("x:mounted", () => this.element.focus());
title.motif.on("x:mounted", () => title.element.focus());
this.motif.on("x:mounted", () => this.element.focus());
title.motif.on("x:mounted", () => title.element.focus());
Fonksiyon bileşeninde title başlık alanının bileşenidir: JSX'te ref ile alınır, JSX'siz yazımda motifComponent ile kurulan değişkendir.
Her abonelik bir kez, eleman document'a bağlandıktan sonraki görevde çalışır; eleman zaten document içindeyse aboneliği izleyen görevde. Bileşen sökülüp yeniden yerleştiğinde (taşıma, motif.hide/motif.show, keepAlive) x:mounted tekrar fırlamaz; o an için x:activated kullanın (Yaşam Döngüsü). Henüz çalışmamış bir aboneliği motif.off("x:mounted", işleyici) iptal eder. İşleyicinin fırlattığı hata MJX122 olarak raporlanır.
Yaşam döngüsü olayları
Bileşen yaşam döngüsü olaylarını da motif.on ile dinleyebilirsiniz. x: öneki bu olayları DOM'a değil bileşenin kendi yayıcısına bağlar; işleyici (sender, e) imzasıyla çağrılır (e: { cancel }), tek parametreli işleyici yalnızca e alır:
this.motif.on("x:built", (sender) => sender.class.add("ready"));
this.motif.on("x:disposed", () => clearInterval(timer));
row.motif.on("x:built", (sender) => sender.class.add("ready"));
row.motif.on("x:disposed", () => clearInterval(timer));
this.motif.on("x:built", (sender) => sender.class.add("ready"));
this.motif.on("x:disposed", () => clearInterval(timer));
row.motif.on("x:built", (sender) => sender.class.add("ready"));
row.motif.on("x:disposed", () => clearInterval(timer));
Tanınan adlar: x:initializing, x:initialized, x:config, x:configured, x:building, x:built, x:mounted, x:visibilityChanged, x:activated, x:deactivated, x:disposing, x:disposed. Abonelik bileşenle birlikte temizlenir; motif.off ile elle de kaldırılır. Aynı işleyici birden çok kez abone olduysa motif.off hepsini kaldırır. x:initializing ve x:initialized bileşenin yapıcısında biter; kurulmuş bir bileşene sonradan abone olunursa işleyici abone olunduğu anda bir kez çalışır.
Bu dinleyicilerden biri hata fırlatırsa hata, kanca sınıf metodu ya da prop olarak yazılmış gibi MJX122 (ör. The component x:built hook threw.) koduyla errorHandler'a raporlanır; bileşen kurulmaya devam eder.
Zamanlama notu: düz
new Component("div", …)örneklerindex:configyapıcıda fırlar; yapıcı döndükten sonra abone olmak için geç kalınmış olur. Alt sınıflarda (class X extends Component)onConfigbuild()başında çalıştığı için yapıcıdan sonra abone olmak yeterlidir. Ayrıntı: Yaşam Döngüsü.
Uygulama olayları — context.on / context.fire
Bileşenler arasında (ebeveyn-çocuk ilişkisi olmadan) haberleşmek için uygulama olay veriyolu kullanılır. Bileşenin context'i çalışan Application'dır (sınıfta ve Options'ta this.context, fonksiyon bileşeninde kök bileşenin context'i); dinleyici bileşen serbest bırakılınca abonelik kendiliğinden kalkar. ArchiveCounter arşivlenen notları sayar: sınıfta onConfig içinde this.context.on, fonksiyonda köke yazılan onConfig prop'u, Options'ta onConfigured; JSX'siz yazımda hepsi aynı context.on çağrısıdır:
import { Component, reactive } from "@motifx/core";
export class ArchiveCounter extends Component {
state = reactive({ count: 0 });
override onConfig() {
this.context.on("noteArchived", () => this.state.count++);
}
override view() {
return <span class="archived">{() => `${this.state.count} arşivlendi`}</span>;
}
}
import { reactive } from "@motifx/core";
export function ArchiveCounter() {
const state = reactive({ count: 0 });
return (
<span class="archived" onConfig={(counter) => counter.context.on("noteArchived", () => state.count++)}>
{() => `${state.count} arşivlendi`}
</span>
);
}
import { motifOptions, reactive } from "@motifx/core";
export const ArchiveCounter = () => motifOptions({
el: "span",
data: reactive({ count: 0 }),
onConfigured() {
this.class.add("archived");
this.context.on("noteArchived", () => this.data.count++);
},
view() {
return <>{() => `${this.data.count} arşivlendi`}</>;
},
});
import { Component, ComponentBase, motifComponent, reactive } from "@motifx/core";
export class ArchiveCounter extends Component {
state = reactive({ count: 0 });
override onConfig() {
this.context.on("noteArchived", () => this.state.count++);
}
override view() {
return motifComponent("span", (span: ComponentBase) => {
span.class.add("archived");
span.bindings.text(() => `${this.state.count} arşivlendi`);
});
}
}
import { ComponentBase, motifComponent, reactive } from "@motifx/core";
export function ArchiveCounter() {
const state = reactive({ count: 0 });
return motifComponent("span", {
onConfig: (counter: ComponentBase) => counter.context.on("noteArchived", () => state.count++),
initializeComponent: (span: ComponentBase) => {
span.class.add("archived");
span.bindings.text(() => `${state.count} arşivlendi`);
},
});
}
import { ComponentBase, motifComponent, motifOptions, reactive } from "@motifx/core";
export const ArchiveCounter = () => motifOptions({
el: "span",
data: reactive({ count: 0 }),
onConfigured() {
this.class.add("archived");
this.context.on("noteArchived", () => this.data.count++);
},
view() {
return motifComponent("text", (node: ComponentBase) => node.bindings.text(() => `${this.data.count} arşivlendi`));
},
});
import { Component, reactive } from "@motifx/core";
export class ArchiveCounter extends Component {
state = reactive({ count: 0 });
onConfig() {
this.context.on("noteArchived", () => this.state.count++);
}
view() {
return <span class="archived">{() => `${this.state.count} arşivlendi`}</span>;
}
}
import { reactive } from "@motifx/core";
export function ArchiveCounter() {
const state = reactive({ count: 0 });
return (
<span class="archived" onConfig={(counter) => counter.context.on("noteArchived", () => state.count++)}>
{() => `${state.count} arşivlendi`}
</span>
);
}
import { motifOptions, reactive } from "@motifx/core";
export const ArchiveCounter = () => motifOptions({
el: "span",
data: reactive({ count: 0 }),
onConfigured() {
this.class.add("archived");
this.context.on("noteArchived", () => this.data.count++);
},
view() {
return <>{() => `${this.data.count} arşivlendi`}</>;
},
});
import { Component, motifComponent, reactive } from "@motifx/core";
export class ArchiveCounter extends Component {
state = reactive({ count: 0 });
onConfig() {
this.context.on("noteArchived", () => this.state.count++);
}
view() {
return motifComponent("span", (span) => {
span.class.add("archived");
span.bindings.text(() => `${this.state.count} arşivlendi`);
});
}
}
import { motifComponent, reactive } from "@motifx/core";
export function ArchiveCounter() {
const state = reactive({ count: 0 });
return motifComponent("span", {
onConfig: (counter) => counter.context.on("noteArchived", () => state.count++),
initializeComponent: (span) => {
span.class.add("archived");
span.bindings.text(() => `${state.count} arşivlendi`);
},
});
}
import { motifComponent, motifOptions, reactive } from "@motifx/core";
export const ArchiveCounter = () => motifOptions({
el: "span",
data: reactive({ count: 0 }),
onConfigured() {
this.class.add("archived");
this.context.on("noteArchived", () => this.data.count++);
},
view() {
return motifComponent("text", (node) => node.bindings.text(() => `${this.data.count} arşivlendi`));
},
});
Yayıncı, olayı gönderen bileşenin context'i üzerinden yayınlar; fire'ın ikinci argümanı dinleyicilere veri olarak gider:
<button type="button" onclick={(sender, e) => { notes.archive(note); sender.context.fire("noteArchived", note); }}>Arşivle</button>
const archive = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender: ComponentBase, _e: Event) => { notes.archive(note); sender.context.fire("noteArchived", note); });
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
});
<button type="button" onclick={(sender, e) => { notes.archive(note); sender.context.fire("noteArchived", note); }}>Arşivle</button>
const archive = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", (sender, _e) => { notes.archive(note); sender.context.fire("noteArchived", note); });
button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
});
Application olay API'si:
| Metot | Açıklama |
|---|---|
app.on(event, handler) |
Dinler; abonelikten çıkmak için bir fonksiyon döndürür. İşleyicinin parametresi fire'a verilen veridir ((note?: Note) => …). |
app.fire(event, args?) |
Olayı tetikler. |
app.off(event, handler) |
Dinleyiciyi kaldırır. |
app.onRouterChanged(handler) |
Her navigasyonda (açılış ve 404 dâhil) çalışacak kanca; abonelikten çıkmak için bir fonksiyon döndürür. |
app.onLifecycle(handler) |
Sekme/uygulama görünürlüğü ve bağlantı değişimleri; bkz. Uygulama yaşam döngüsü. |
Olay adı dizge ya da Symbol olabilir; fire dinleyicileri ekleniş sırasıyla, senkron çağırır.
Uygulama olayı dinleyicisinin fırlattığı hata MJX123 (The '<olay>' event handler threw.) koduyla raporlanır ve olayın diğer dinleyicileri çalışır. onRouterChanged işleyicileri de böyledir (The 'motifjs-router-navigated' event handler threw.); gezinme yine tamamlanır.
Bir bileşenin context'i uygulamanın bileşene bağlı bir görünümüdür: onun üzerinden açılan on ve onRouterChanged abonelikleri bileşen serbest bırakılınca kendiliğinden kaldırılır (onLifecycle bu kapsama girmez). Application.main, elde tutulan app örneği ya da useApplication().application üzerinden açılan abonelikler hiçbir bileşene bağlı değildir; döndürülen fonksiyonu kendiniz çağırın ya da bir bileşene motif.setDisposable ile kaydedin (Bellek Yönetimi ve Serbest Bırakma):
const unsubscribe = app.on<Note>("noteArchived", (note) => {
if (note) document.title = `${note.title} arşivlendi`;
});
unsubscribe();
const unsubscribe = app.on("noteArchived", (note) => {
if (note) document.title = `${note.title} arşivlendi`;
});
unsubscribe();
Servis olayları — Emitter<T>
Uygulama olayları adla çalışır ve uygulamanın tamamına açıktır. Bir servisin kendi olayını tipiyle birlikte yayması için Emitter<T> kullanılır; bileşenden bağımsız, tek tip taşıyan küçük bir yayıcıdır. Örnekteki servis silinen notu bildirir:
import { Emitter, Injectable, reactive } from "@motifx/core";
export interface Note {
id: number;
title: string;
body: string;
pinned: boolean;
archived: boolean;
createdAt: number;
updatedAt: number;
}
@Injectable({ lifetime: "singleton" })
export class NotesService {
readonly state = reactive({ items: [] as Note[] });
private readonly removed = new Emitter<Note>();
readonly onRemoved = this.removed.event;
remove(id: number): boolean {
const index = this.state.items.findIndex((note) => note.id === id);
if (index < 0) return false;
const [note] = this.state.items.splice(index, 1);
this.removed.fire(note);
return true;
}
dispose() {
this.removed.dispose();
}
}
import { Emitter, Injectable, reactive } from "@motifx/core";
@Injectable({ lifetime: "singleton" })
export class NotesService {
state = reactive({ items: [] });
removed = new Emitter();
onRemoved = this.removed.event;
remove(id) {
const index = this.state.items.findIndex((note) => note.id === id);
if (index < 0) return false;
const [note] = this.state.items.splice(index, 1);
this.removed.fire(note);
return true;
}
dispose() {
this.removed.dispose();
}
}
Bir bileşen aboneliği kendi ömrüne motif.register ile bağlar; bileşen serbest bırakılınca abonelik kalkar:
this.motif.register(this.notes.onRemoved((note) => (this.state.removed = note.title)));
list.motif.register(notes.onRemoved((note) => (state.removed = note.title)));
this.motif.register(this.notes.onRemoved((note) => (this.state.removed = note.title)));
list.motif.register(notes.onRemoved((note) => (state.removed = note.title)));
Fonksiyon bileşeninde list, aboneliği taşıyan bileşendir: JSX'te kökün onConfig kancasına ya da ref'ine gelen bileşen, JSX'siz yazımda kökü kuran motifComponent fonksiyonunun aldığı bileşen.
event(listener, thisArgs?, disposables?)abone olur ve birIDisposabledöndürür;disposablesbirDisposableStoreya daIDisposable[]ise abonelik oraya da eklenir (notes.onRemoved(fn, undefined, store)).fire(value)dinleyicileri ekleniş sırasıyla, senkron çağırır. Dağıtım sırasında eklenen dinleyici o dağıtımı almaz; kaldırılan dinleyici bir daha çağrılmaz.- Dinleyicinin fırlattığı hata diğerlerini durdurmaz; beklenmeyen hata yoluna gider (bkz. Beklenmeyen hatalar).
dispose()bütün abonelikleri geçersiz kılar; sonrakievent(...)çağrıları boş birIDisposabledöndürür. Servisindispose()metodunu sağlayıcı çağırır; singleton için buapp.dispose()anıdır (bkz. Servislerin Serbest Bırakılması).
Sonraki adım
Form girdilerini ve iki yönlü bağlamayı öğrenmek için Formlar ve İki Yönlü Bağlama bölümüne geçin.