Temel kavramlar
JSX'siz Görünümler
JSX'in indiği motifComponent çağrılarını elle yazmak; çağrı biçimleri, new Component, props anahtarları, metin düğümleri, preconfig, dal ve liste için fragment, JSX'siz dosyada sınıf bileşeni, derlenmiş çıktıyı okuma.
Derleyici her JSX elemanını bir motifComponent(etiket, kurulum) çağrısına çevirir; ağaç sender.controls.add(...) ile kurulur. JSX kullanmayan bir proje (.ts / .js) aynı çağrıları elle yazar: derleyicinin ürettiği kodun elle yazılmış hâli. Çalışma zamanı, bağlar, yaşam döngüsü ve kurallar aynıdır; değişen yalnız yazımdır. Bu sayfa JSX'siz projeler, derleyiciye bağımlı olmaması gereken kütüphaneler, kodla üretilen görünümler ve derlenmiş çıktıyı okumak içindir. Hangi JSX yazımının hangi çağrıya indiği JSX ve Şablonlar sayfasında her örneğin yanında verilir; burada çağrıların kendisi anlatılır.
Örnekler başlangıç şablonundaki not uygulamasının JSX'siz sürümünden kesitlerdir: kenar çubuğu (MainLayout), not listesi (NotesLayout), liste satırı (NoteRow) ve düzenleyici (NotePage). Note, Folder ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındakilerdir. Kurulum fonksiyonlarının parametresi TypeScript'te ComponentBase olarak yazılır; motifComponent any döndürdüğü için tipleri siz bildirirsiniz (bkz. Tuzaklar).
Çağrı biçimleri
| JSX | Elle yazım |
|---|---|
<div class="app">…</div> |
motifComponent("div", (div) => { div.class.add("app"); div.controls.add(…); }) |
Notlar (düz metin) |
motifComponent("text", (node) => node.setText("Notlar")) — bir metin düğümü |
{note.title} |
metin düğümünde node.bindings.add("textContent", note, "title") |
{() => note.title.trim() \|\| "Adsız"} |
span.bindings.text(() => note.title.trim() \|\| "Adsız"); derleyici bindings.method(fn) yazar, o da aynı işi yapar ve bileşen sonucunu da kabul eder |
type="button" aria-pressed={() => note.pinned} aria-current={() => (open() ? "page" : null)} |
button.attr.add({ type: "button", "aria-pressed": () => note.pinned, "aria-current": () => (open() ? "page" : null) }) — fonksiyon canlı getter'dır, düz değer bir kez yazılır; false/null özniteliği kaldırır. action ve formaction'da getter'ın döndürdüğü fonksiyon formun gönderme işleyicisi olur: form.attr.add({ action: () => (data: FormData) => … }) (bkz. Fonksiyon action ile gönderim) |
class={() => (note.pinned ? "note pinned" : "note")} |
li.class.add(() => (note.pinned ? "note pinned" : "note")); dize, { pinned: () => note.pinned } nesnesi ve bunların dizisi de olur |
onclick={() => …}, onsubmit:prevent={…} |
button.motif.on("click", () => …), form.motif.on("submit:prevent", fn); değiştiriciler zincirlenir ("click:once:prevent"). Tipli imza (sender, e): olay gerekiyorsa (_sender: ComponentBase, e: Event) => … |
<input x-model={() => state.query} /> |
field.bindings.model(() => state.query, (value: string) => (state.query = value)) |
<p x-display={() => cond}> |
motifComponent("p", { preconfig: (p) => p.bindings.display(() => cond), initializeComponent: (p) => { … } }); x-wait → bindings.wait (bkz. preconfig) |
{cond ? <A/> : <B/>} |
pane.bindings.ternary(() => cond, (frame) => frame.navigate(a), (frame) => frame.navigate(b)) |
{cond && <X/>} |
pane.bindings.when(() => cond, () => x) |
{visible.map((note) => <NoteRow key={note.id} note={note} folder={folder} />)} |
elemansız tutucu: motifComponent({ initializeComponent: (holder) => holder.bindings.list(() => visible, (note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder })) }) |
<input ref={(c) => (search = c)} /> |
motifComponent("input", { ref: (c) => (search = c), initializeComponent: (c) => { … } }); düğüm search.element |
<RouterLink to="/notes" activeClass="active">Notlar</RouterLink> |
motifComponent(RouterLink, { to: "/notes", activeClass: "active", childs: [motifComponent("text", (node) => node.setText("Notlar"))] }) |
<RouterView class="outlet" /> |
motifComponent(RouterView, { class: "outlet" }) |
<a rel="router" href="/notes"> |
motifComponent("a", (a) => a.attr.add({ rel: "router", href: "/notes" })) |
<NoteRow note={note} folder={folder} /> |
motifComponent(NoteRow, { note, folder }): sınıf props ile kurulur, fonksiyon parçası props ile çağrılır |
<svg viewBox="0 0 10 10"><path d="M0 0h10" /></svg> |
motifComponent("svg", { options: { isSvg: true }, initializeComponent: (svg) => { svg.attr.add({ viewBox: "0 0 10 10" }); svg.controls.add(motifComponent("path", { options: { isSvg: true }, initializeComponent: (p) => p.attr.add({ d: "M0 0h10" }) })); } }) — her SVG çocuğu da isSvg taşır |
onMounted={(c) => …} (etikette) |
nesne biçiminde onMounted anahtarı (öteki kancalar da; imza (sender, e)) ya da kurulumda c.motif.on("x:mounted", …) |
motifComponent(eleman, props?)
motifComponent("div", fn):fnbileşenininitializeComponent'idir. Bileşen kurulurken (build()), çocuklar kurulmadan hemen önce bir kez çalışır, bir daha çalışmaz;thisbağlı değildir, parametreyi kullanın.motifComponent("div", { … }): nesne biçimi. Çerçeve anahtarlarıinitializeComponent,preconfig,ref,childs,options(isSvg,hideStrategy,disableDisposal),transition,key/indexkeyve yaşam döngüsü kancalarıdır (onConfig,onConfigured,onBuilt,onMounted, …). Düz etikette diğer anahtarlar spread gibi uygulanır:{ type: "button", class: "row", onclick: fn, "aria-pressed": () => note.pinned }özniteliğe, sınıfa ve olaya gider, getter canlı kalır. Derleyici öznitelikleri kurulum fonksiyonundaattr.addile yazar; elle yazarken de okunaklı olan budur.motifComponent(Sınıf, props):new Sınıf(props). Nesne biçiminin çerçeve anahtarları (preconfig,initializeComponent,onMounted,childs,ref…) sınıf bileşenine de uygulanır.motifComponent(fonksiyonParçası, props): fonksiyonpropsile çağrılır; nesne biçiminin çerçeve anahtarları sınıftaki gibi döndürdüğü köke uygulanır:ref, ortak öznitelikler (class,id,style,role,aria-*,data-*; bkz. attribute fallthrough),options,transition,key/indexkey,preconfig,initializeComponent, yaşam döngüsü kancaları vex-anahtarları. Kanca vex-anahtarları fonksiyonun aldığıprops'ta yer almaz, sınıfınthis.props'unda da yer almaz;onSavegibi geri çağrı prop'ları kalır.motifComponent(örnek): hazır bir bileşen örneğini olduğu gibi döndürür.motifComponent({ initializeComponent: (holder) => … })(etiketsiz nesne): elemansız bileşen. DOM'da<!--[-->…<!--]-->işaretçileri arasında durur, çocukları ebeveynin içine iner. Derleyicinin liste için kullandığı tutucu budur.new Component("div", { initializeComponent })dize biçimiyle aynıdır;new Component("div")ile kurup sonradancontrols.addçağırmak da olur.motifFragment({ initializeComponent: (f) => f.controls.add(a, b) }): birden çok köklü bir dal ya da yuva için fragment; DOM'da yalnız yorum işaretçileri vardır, öznitelik ve olay yazılamaz (bkz. Fragment).
Zamanlama: düz etikette yapılandırma evresi (preconfig, onConfig) motifComponent çağrısının içinde, bileşen yaratılırken biter; onConfigured, initializeComponent ve onBuilt bileşen kurulmuş bir ebeveyne eklenince (build()), onMounted belgeye bağlanınca çalışır. Sınıf bileşeninde yapılandırma evresi build() başına ertelenir (bkz. Yaşam Döngüsü).
Derleyicinin çıktısında görülen üçüncü argüman (, 1) ve __childExpr, __isSvgElement anahtarları derleyici işaretleridir; elle yazılan kod bunları yazmaz ve motifCompiled / motifReport çağırmaz (bkz. Derlenmiş çıktıyı okuma).
new Component(eleman, props?)
Yapıcının birinci argümanı bileşenin elemanını belirler, ikincisi props nesnesidir:
| Birinci argüman | Sonuç |
|---|---|
Etiket adı: new Component("li", props) |
O etikette yeni bir eleman yaratılır. "text" bir metin düğümü üretir. SVG elemanı için props'a options: { isSvg: true } yazılır. |
DOM düğümü: new Component(node, props) |
Var olan düğüm bileşenin elemanı olur, yeni eleman yaratılmaz. app.run("#app") sayfadaki #app elemanını bu yolla sarar. |
Props nesnesi ya da hiçbir şey: new Component({ … }) |
Eleman yaratılmaz, bileşenin kökü bir yorum düğümüdür. static elementTag bildiren bir alt sınıfta o etikette eleman yaratılır. |
Dizi: new Component([title, body]) |
Elemansız bir bileşen. Dizinin her öğesi aynı kurallarla bileşene çevrilir ve çocuk olarak eklenir. |
Sınıf ya da fonksiyon: new Component(NoteRow, props) |
Sınıf props ile kurulur, fonksiyon props ile çağrılır; new ifadesi dönen bileşeni verir. Props'un çerçeve anahtarları fonksiyonun döndürdüğü köke motifComponent'teki gibi uygulanır (bkz. yukarıdaki fonksiyon parçası maddesi). Fonksiyon bir Options nesnesi döndürüyorsa bileşen o nesneden kurulur. |
Hazır bir bileşen: new Component(row) |
Aynı bileşen döner, yenisi yaratılmaz. |
motifComponent ile farkları:
- İkinci argüman yalnız props nesnesi olarak okunur.
new Component("li", (li) => …)fonksiyonu çalıştırmaz; kurulum koduinitializeComponentanahtarına yazılır. - Options nesnesini doğrudan alan
motifComponent'tir (motifComponent({ el: "section", … })).new Componentnesneyi props sayar ve elemansız bir bileşen çıkar; Options nesnesi döndüren fonksiyonu ise tanır.
Düz Component'te props nesnesindeki çerçeve dışı anahtarlar elemana yazılır: class, style, olaylar (onclick …) ve öznitelikler; value, checked ve selected bağ olarak kurulur, parametresiz bir fonksiyon değer canlı getter olur; action ve formaction'da getter'ın döndürdüğü fonksiyon formun gönderme işleyicisidir. Alt sınıfta bu anahtarlar this.props'ta kalır, elemana yalnız ortak öznitelikler düşer. innerHTML ve srcdoc bu yolla yazılmaz (MJX124); href, src, action ve formaction değerindeki javascript: adresi engellenir (MJX125).
motifFragment(props) new FragmentNode(props) ile aynıdır: kurulurken props'taki childs ve nodes dizilerindeki bileşenleri çocuk olarak ekler. FNComponent(view) bir görünüm fonksiyonunu fonksiyon bileşenine çevirir; dönen fonksiyon props ile çağrılınca elemansız bir bileşen kurar, props'u ona verir ve view(props)'un döndürdüğü bileşeni çocuk olarak ekler. Derleyici çıktısındaki _mfc içe aktarımı budur; JSX'te yazılan fonksiyon bileşenleri ona sarılmaz.
Props nesnesinin anahtarları
Bu anahtarları MotifJS kendisi işler; elemana öznitelik olarak yazılmazlar.
| Anahtar | Ne yapar |
|---|---|
initializeComponent |
Bileşenin kurulum kodu. Bileşen kurulurken, çocuklarından önce bir kez çalışır; parametresi bileşenin kendisidir. |
oninitializeComponent |
initializeComponent'ten hemen sonra çalışır (bkz. Yaşam Döngüsü). |
preconfig |
Yapılandırma evresinde, onConfig'ten önce çalışır (bkz. preconfig). |
onConfig, onConfigured, onInitializing, onInitialized, onBuilding, onBuilt, onMounted, onVisibilityChanged, onActivated, onDeactivated, onDisposing, onDisposed |
Yaşam döngüsü kancaları: bir fonksiyon ya da fonksiyon dizisi, imzası (sender, e) (bkz. Kancalar ve çalışma sırası). |
x-wait, x-display |
bindings.wait ve bindings.display olarak kaydedilir. x-mounted gibi öteki x- anahtarları aynı adlı kancaya bağlanır. |
childs |
Çocuk bileşenler; iç içe diziler düzleştirilir. Elemansız bileşen ve view() yazmayan, eleman verilmeden yaratılan sınıf bunları içerik olarak ekler; view() yazan sınıf onları kendisi yerleştirir. Etiket adıyla yaratılan bileşen childs'ı eklemez: new Component("ul", { childs }) boş bir <ul> verir, çocuklar initializeComponent içinde controls.add ile eklenir. |
ref |
Bileşen yaratılınca bileşenin kendisiyle çağrılan fonksiyon. |
options |
isSvg (SVG elemanı), hideStrategy (bkz. Gizleme stratejisi) ve disableDisposal. |
transition |
CSS sınıflarıyla giriş ve çıkış geçişi (bkz. transition prop'u). |
onElementCreating |
Eleman döndüren bir fonksiyon; bileşen birinci argümandan gelen eleman yerine bunu kullanır. |
key, indexkey |
Liste satırının kimliği; derleyici JSX'teki key'i indexkey olarak yazar. Elemana yazılmaz, yalnız aynı kimliğin iki kez geldiğini bildiren MJX202 uyarısında kullanılır. |
Fonksiyon bileşeni satırında indexkey, fonksiyonun döndürdüğü kökün props nesnesine yazılır. Fonksiyon kökü her çağrıda aynı props nesnesiyle kuruyorsa (modül düzeyinde bir sabit: motifComponent("li", ROW_PROPS)), bütün satırlar bu nesneye en son yazılan anahtarı okur. Anahtarlar benzersiz olsa bile liste ilk güncellendiğinde MJX202 uyarısı gelir; çizim bundan etkilenmez. Kökün props'unu her çağrıda yeni bir nesne olarak verin: motifComponent("li", { class: "row" }). JSX'te derleyici her çağrıda yeni nesne ürettiği için bu durum oluşmaz.
Metin düğümleri
"text" gerçek bir etikettir: motifComponent("text", (node: ComponentBase) => node.setText("…")) bir metin düğümü üretir; elemanın yanındaki metin aynı controls.add çağrısında bir çocuk daha olur. Metin düğümüne bağ da kurulur: node.bindings.text(() => …) getter ile canlı metin, node.bindings.add("textContent", note, "title") tek alana tek yönlü bağ. Tek içeriği metin olan elemanda düğüm yaratmadan span.setText("…") ya da span.bindings.text(fn) yazılır. Çalışma zamanı controls.add'e verilen dizge ve sayıları da metin düğümüne çevirir; ama controls.add'in TypeScript imzası bileşen alır (ComponentBase[]), TypeScript projesinde "text" çağrısı yazılır.
Kenar çubuğundaki klasör bağlantısı adın yanında klasördeki not sayısını gösterir. Ad bir metin düğümüdür, sayı bindings.text ile bağlı bir rozettir; ikisi RouterLink'e childs olarak verilir:
private folderLink(folder: Folder, label: string) {
const badge = motifComponent("span", (span: ComponentBase) => {
span.class.add("badge");
span.bindings.text(() => String(this.notes.count(folder)));
});
return motifComponent(RouterLink, {
to: `/${folder}`,
activeClass: "active",
childs: [motifComponent("text", (node: ComponentBase) => node.setText(`${label} `)), badge],
});
}
private folderLink(folder: Folder, label: string) {
const badge = motifComponent("span", (span: ComponentBase) => {
span.class.add("badge");
span.bindings.text(() => String(notes.count(folder)));
});
return motifComponent(RouterLink, {
to: `/${folder}`,
activeClass: "active",
childs: [motifComponent("text", (node: ComponentBase) => node.setText(`${label} `)), badge],
});
}
folderLink(folder, label) {
const badge = motifComponent("span", (span) => {
span.class.add("badge");
span.bindings.text(() => String(this.notes.count(folder)));
});
return motifComponent(RouterLink, {
to: `/${folder}`,
activeClass: "active",
childs: [motifComponent("text", (node) => node.setText(`${label} `)), badge],
});
}
folderLink(folder, label) {
const badge = motifComponent("span", (span) => {
span.class.add("badge");
span.bindings.text(() => String(notes.count(folder)));
});
return motifComponent(RouterLink, {
to: `/${folder}`,
activeClass: "active",
childs: [motifComponent("text", (node) => node.setText(`${label} `)), badge],
});
}
Fonksiyon bileşeninde notes FromService ile alınan servis, state reactive ile kurulan yerel durumdur; visible(), folder(), confirmation, actions ve remove bileşen fonksiyonunun içindeki yerel fonksiyonlardır.
preconfig
preconfig: (c) => … bileşen yapılandırılırken, onConfig'ten ve build()'den önce çalışır; düz etikette bu motifComponent çağrısının içidir. Orada kaydedilen bindings.display / bindings.wait, gizli başlayan elemanı kurulmamış tutar: alt ağacı, initializeComponent'i, onBuilt'i ve onMounted'ı koşul sağlanana kadar bekler; JSX'teki x-wait / x-display budur. wait fonksiyon true döndürdüğü sürece bekletir, display true iken gösterir. Aynı bağ initializeComponent içinde kaydedilirse eleman önce kurulur, sonra gizlenir.
Liste satırındaki sabitleme işareti yalnız sabitli ve arşivde olmayan notta görünür; liste bölmesindeki boş liste mesajı görünen not kalmayınca çıkar:
const pin = motifComponent("i", {
preconfig: (i: ComponentBase) => i.bindings.display(() => note.pinned && !note.archived),
initializeComponent: (i: ComponentBase) => {
i.class.add("pin");
i.attr.add({ "aria-label": "Sabitli" });
},
});
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned && !note.archived),
initializeComponent: (i) => {
i.class.add("pin");
i.attr.add({ "aria-label": "Sabitli" });
},
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => this.visible.length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("list__empty");
p.bindings.text(() => (this.state.query.trim() ? "Aramayla eşleşen not yok." : FOLDERS[this.folder].empty));
},
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => visible().length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("list__empty");
p.bindings.text(() => (state.query.trim() ? "Aramayla eşleşen not yok." : FOLDERS[folder()].empty));
},
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => this.visible.length === 0),
initializeComponent: (p) => {
p.class.add("list__empty");
p.bindings.text(() => (this.state.query.trim() ? "Aramayla eşleşen not yok." : FOLDERS[this.folder].empty));
},
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => visible().length === 0),
initializeComponent: (p) => {
p.class.add("list__empty");
p.bindings.text(() => (state.query.trim() ? "Aramayla eşleşen not yok." : FOLDERS[folder()].empty));
},
});
İkisi de gizli başladığında kurulmaz; initializeComponent ilk gösterilişte çalışır. Koşul yeniden false olunca eleman kurulu kalır ve gizlenir. wait aynı bağın tersidir: fonksiyon true döndürdüğü sürece bekletir, false olunca gösterir. Bileşene de aynı anahtarla verilir; sınıf ve fonksiyon bileşeninde aynıdır: motifComponent(NoteRow, { note, folder, preconfig: (row: ComponentBase) => row.bindings.display(() => !note.archived) }). display ile wait arasındaki fark ve gizleme stratejisi Koşullu Gösterim ve Listeler ve Yaşam Döngüsü sayfalarındadır.
Dal ve liste için fragment
Bir dalın tek kökü varsa bileşen, birden çok kökü varsa motifFragment, yalnız metinse "text" bileşeni verilir; frame.flush() yeri boşaltır. Düzenleyicinin araç çubuğu notu silmeden önce onay ister. Onay bir soru ve iki düğmedir, birden çok kökü olduğu için motifFragment ile verilir; actions(note) sabitleme, arşivleme ve silme düğmelerini aynı biçimde döndürür:
private toolbar(note: Note) {
return motifComponent("div", (bar: ComponentBase) => {
bar.class.add("toolbar");
bar.bindings.ternary(
() => this.state.confirming,
(frame) => frame.navigate(this.confirmation(note)),
(frame) => frame.navigate(this.actions(note)),
);
});
}
private confirmation(note: Note) {
return motifFragment({
initializeComponent: (tools: ComponentBase) => {
const question = motifComponent("span", (span: ComponentBase) => {
span.class.add("confirm");
span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
});
const remove = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.class.add("button--sm danger accent");
button.motif.on("click", () => this.remove(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sil")));
});
const cancel = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.class.add("button--sm ghost");
button.motif.on("click", () => (this.state.confirming = false));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Vazgeç")));
});
tools.controls.add(question, remove, cancel);
},
});
}
private toolbar(note: Note) {
return motifComponent("div", (bar: ComponentBase) => {
bar.class.add("toolbar");
bar.bindings.ternary(
() => state.confirming,
(frame) => frame.navigate(confirmation(note)),
(frame) => frame.navigate(actions(note)),
);
});
}
private confirmation(note: Note) {
return motifFragment({
initializeComponent: (tools: ComponentBase) => {
const question = motifComponent("span", (span: ComponentBase) => {
span.class.add("confirm");
span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
});
const removeButton = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.class.add("button--sm danger accent");
button.motif.on("click", () => remove(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sil")));
});
const cancel = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.class.add("button--sm ghost");
button.motif.on("click", () => (state.confirming = false));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Vazgeç")));
});
tools.controls.add(question, removeButton, cancel);
},
});
}
toolbar(note) {
return motifComponent("div", (bar) => {
bar.class.add("toolbar");
bar.bindings.ternary(
() => this.state.confirming,
(frame) => frame.navigate(this.confirmation(note)),
(frame) => frame.navigate(this.actions(note)),
);
});
}
confirmation(note) {
return motifFragment({
initializeComponent: (tools) => {
const question = motifComponent("span", (span) => {
span.class.add("confirm");
span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
});
const remove = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.class.add("button--sm danger accent");
button.motif.on("click", () => this.remove(note));
button.controls.add(motifComponent("text", (node) => node.setText("Sil")));
});
const cancel = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.class.add("button--sm ghost");
button.motif.on("click", () => (this.state.confirming = false));
button.controls.add(motifComponent("text", (node) => node.setText("Vazgeç")));
});
tools.controls.add(question, remove, cancel);
},
});
}
toolbar(note) {
return motifComponent("div", (bar) => {
bar.class.add("toolbar");
bar.bindings.ternary(
() => state.confirming,
(frame) => frame.navigate(confirmation(note)),
(frame) => frame.navigate(actions(note)),
);
});
}
confirmation(note) {
return motifFragment({
initializeComponent: (tools) => {
const question = motifComponent("span", (span) => {
span.class.add("confirm");
span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
});
const removeButton = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.class.add("button--sm danger accent");
button.motif.on("click", () => remove(note));
button.controls.add(motifComponent("text", (node) => node.setText("Sil")));
});
const cancel = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.class.add("button--sm ghost");
button.motif.on("click", () => (state.confirming = false));
button.controls.add(motifComponent("text", (node) => node.setText("Vazgeç")));
});
tools.controls.add(question, removeButton, cancel);
},
});
}
Değişen bir liste için elemansız tutucu ile bindings.list kullanılır; satır bileşeni kimliğini indexkey ile bildirir (derleyici key'i böyle yazar). Sıra değişince düğümler taşınır, yeniden kurulmaz. Sabit seçenekler için düz bir for döngüsü yeter. Liste bölmesi görünen notları böyle sıralar:
const list = motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(
() => this.visible,
(note: Note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder }),
),
});
const list = motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(
() => visible(),
(note: Note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() }),
),
});
const list = motifComponent({
initializeComponent: (holder) => holder.bindings.list(
() => this.visible,
(note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder }),
),
});
const list = motifComponent({
initializeComponent: (holder) => holder.bindings.list(
() => visible(),
(note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() }),
),
});
when, ternary, list ve switchCase'in davranışı Koşullu Gösterim ve Listeler sayfasında anlatılır.
JSX'siz dosyada sınıf bileşeni
Vite eklentisi .ts, .mts ve .cts dosyalarını da işler: extends Component<HTMLDivElement> yazan sınıfa static elementTag = "div" ekler, bileşenin kendi elemanı olur. .js dosyasında generic yazılmadığı için static elementTag = "div" elle yazılır. Eklentinin işlemediği dosyada da (yalnız tsc ile derlenen bir kütüphane, node_modules içindeki paket) static elementTag = "div" yazın ya da super("div") çağırın. Eleman türü bildirmeyen sınıf bir fragmenttir: this.element bir yorum işaretçisidir; this.class ve this.attr hiçbir elemana ulaşmaz, this.motif.on içeriğin üst düzey elemanlarını dinler, x-display ve this.motif.hide() çocukları gizler. view()'den bir sarmalayıcı döndürmek bileşene eleman vermez: sarmalayıcı içeriğin ilk çocuğudur, sınıfı ve özniteliği ona yazın (bkz. Kök eleman). Not listesinin düzeni elemansız bir sınıftır; view() içeriği döndürür:
import { Component, ComponentBase, RouterView, motifComponent, reactive, useNavigation } from "@motifx/core";
import { NotesService, type Folder } from "../services/notes";
export default class NotesLayout extends Component {
notes = this.getService(NotesService)!;
nav = useNavigation();
state = reactive({ query: "" });
get folder(): Folder {
return this.nav.meta.folder as Folder;
}
get visible() {
return this.notes.list(this.folder, this.state.query);
}
override view() {
return motifComponent("div", (root: ComponentBase) => {
root.class.add("notes");
const detail = motifComponent("main", (main: ComponentBase) => {
main.class.add("detail");
main.controls.add(motifComponent(RouterView, { class: "outlet" }));
});
root.controls.add(this.listPane(), detail);
});
}
private listPane() {
return motifComponent("section", (list: ComponentBase) => {
list.class.add("list");
list.attr.add({ "aria-label": "Not listesi" });
});
}
}
import { Component, RouterView, motifComponent, reactive, useNavigation } from "@motifx/core";
import { NotesService } from "../services/notes";
export default class NotesLayout extends Component {
notes = this.getService(NotesService);
nav = useNavigation();
state = reactive({ query: "" });
get folder() {
return this.nav.meta.folder;
}
get visible() {
return this.notes.list(this.folder, this.state.query);
}
view() {
return motifComponent("div", (root) => {
root.class.add("notes");
const detail = motifComponent("main", (main) => {
main.class.add("detail");
main.controls.add(motifComponent(RouterView, { class: "outlet" }));
});
root.controls.add(this.listPane(), detail);
});
}
listPane() {
return motifComponent("section", (list) => {
list.class.add("list");
list.attr.add({ "aria-label": "Not listesi" });
});
}
}
NotesLayout'un kendi elemanı yoktur; <div class="notes"> içeriğin ilk çocuğudur. Kendi elemanı olan biçim aşağıdaki PinnedNotes sınıfıdır: elemanı <aside class="pinned">dir, view() yalnız çocukları döndürür. Sınıf view()'i tek bir bileşen döndürür (tipi ComponentBase); birden çok kök için motifFragment yazılır. Options API bileşeninin view()'i dizi de döndürebilir.
Okuma sırası JSX'teki gibi kalır: çocuklar const olarak, sonda tek bir controls.add, parçalar için her biri bir bileşen döndüren özel metotlar. Kenar çubuğuna eklenen PinnedNotes sabitli notları listeler, her satırda sabitlemeyi kaldıran bir düğme vardır; üç biçimde aynı bileşendir:
import { Component, ComponentBase, motifComponent, motifFragment } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class PinnedNotes extends Component<HTMLElement> {
static override elementTag = "aside";
notes = this.getService(NotesService)!;
get pinned() {
return this.notes.list("pinned");
}
override onConfigured() {
this.class.add("pinned");
}
override view() {
const heading = motifComponent("h3", (h3: ComponentBase) => h3.bindings.text(() => `Sabitler (${this.pinned.length})`));
const rows = motifComponent("ul", (ul: ComponentBase) => {
ul.controls.add(motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => this.pinned, (note: Note) => this.row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => this.pinned.length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitlenmiş not yok.")));
},
});
return motifFragment({ initializeComponent: (content: ComponentBase) => content.controls.add(heading, rows, empty) });
}
private row(note: Note) {
return motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => {
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => this.notes.togglePin(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
}
}
import { ComponentBase, FromService, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function PinnedNotes() {
const notes = FromService(NotesService)!;
const pinned = () => notes.list("pinned");
const row = (note: Note) =>
motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => {
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
return motifComponent("aside", (aside: ComponentBase) => {
aside.class.add("pinned");
const heading = motifComponent("h3", (h3: ComponentBase) => h3.bindings.text(() => `Sabitler (${pinned().length})`));
const rows = motifComponent("ul", (ul: ComponentBase) => {
ul.controls.add(motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(pinned, (note: Note) => row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => pinned().length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitlenmiş not yok.")));
},
});
aside.controls.add(heading, rows, empty);
});
}
import { ComponentBase, FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const PinnedNotes = () => motifOptions({
el: "aside",
view() {
const notes = FromService(NotesService)!;
const pinned = () => notes.list("pinned");
const row = (note: Note) =>
motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => {
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
this.class.add("pinned");
const heading = motifComponent("h3", (h3: ComponentBase) => h3.bindings.text(() => `Sabitler (${pinned().length})`));
const rows = motifComponent("ul", (ul: ComponentBase) => {
ul.controls.add(motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(pinned, (note: Note) => row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => pinned().length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitlenmiş not yok.")));
},
});
return [heading, rows, empty];
},
});
import { Component, motifComponent, motifFragment } from "@motifx/core";
import { NotesService } from "../services/notes";
export class PinnedNotes extends Component {
static elementTag = "aside";
notes = this.getService(NotesService);
get pinned() {
return this.notes.list("pinned");
}
onConfigured() {
this.class.add("pinned");
}
view() {
const heading = motifComponent("h3", (h3) => h3.bindings.text(() => `Sabitler (${this.pinned.length})`));
const rows = motifComponent("ul", (ul) => {
ul.controls.add(motifComponent({
initializeComponent: (holder) => holder.bindings.list(() => this.pinned, (note) => this.row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => this.pinned.length === 0),
initializeComponent: (p) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node) => node.setText("Sabitlenmiş not yok.")));
},
});
return motifFragment({ initializeComponent: (content) => content.controls.add(heading, rows, empty) });
}
row(note) {
return motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => {
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => this.notes.togglePin(note));
button.controls.add(motifComponent("text", (node) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
}
}
import { FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";
export function PinnedNotes() {
const notes = FromService(NotesService);
const pinned = () => notes.list("pinned");
const row = (note) =>
motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => {
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
return motifComponent("aside", (aside) => {
aside.class.add("pinned");
const heading = motifComponent("h3", (h3) => h3.bindings.text(() => `Sabitler (${pinned().length})`));
const rows = motifComponent("ul", (ul) => {
ul.controls.add(motifComponent({
initializeComponent: (holder) => holder.bindings.list(pinned, (note) => row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => pinned().length === 0),
initializeComponent: (p) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node) => node.setText("Sabitlenmiş not yok.")));
},
});
aside.controls.add(heading, rows, empty);
});
}
import { FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
export const PinnedNotes = () => motifOptions({
el: "aside",
view() {
const notes = FromService(NotesService);
const pinned = () => notes.list("pinned");
const row = (note) =>
motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => {
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
const unpin = motifComponent("button", (button) => {
button.attr.add({ type: "button", title: "Sabitlemeyi kaldır" });
button.motif.on("click", () => notes.togglePin(note));
button.controls.add(motifComponent("text", (node) => node.setText("×")));
});
li.controls.add(title, unpin);
},
});
this.class.add("pinned");
const heading = motifComponent("h3", (h3) => h3.bindings.text(() => `Sabitler (${pinned().length})`));
const rows = motifComponent("ul", (ul) => {
ul.controls.add(motifComponent({
initializeComponent: (holder) => holder.bindings.list(pinned, (note) => row(note)),
}));
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => pinned().length === 0),
initializeComponent: (p) => {
p.class.add("hint");
p.controls.add(motifComponent("text", (node) => node.setText("Sabitlenmiş not yok.")));
},
});
return [heading, rows, empty];
},
});
Bileşen etikete yazılır gibi kurulur: motifComponent(PinnedNotes); ortak öznitelikler üç biçimde de köke düşer (motifComponent(PinnedNotes, { class: "sidebar__pinned" })). JSX'siz dosyada @Injectable ve öteki dekoratörler, DI, router, useNavigation(), geçişler ve Virtualization aynen çalışır; super'e ulaşmayan bir ezme (MJX015) Vite eklentisinin derlemesinde raporlanır, motif-lint ise yalnız JSX dosyalarına bakar.
Derlenmiş çıktıyı okuma
Bir ifadenin hangi çağrıya indiğini npx motif-explain dosya.tsx yazdırır (bkz. Bu ifade neye derlendi?). Dosyanın tamamı için @motifx/compiler paketindeki Compiler sınıfı kullanılır: new Compiler().motifCompile(kod, "NoteRow.tsx")?.code derlenmiş kodu döndürür. Şu JSX:
<li>
<i class="pin" x-display={() => note.pinned} />
<span>{note.title}</span>
</li>
<li>
<i class="pin" x-display={() => note.pinned} />
<span>{note.title}</span>
</li>
şu koda derlenir:
import { motifComponent as _mc, FNComponent as _mfc, motifCompiled as _mv } from "@motifx/core";
/*#__PURE__*/_mv(1);
_mc("li", {
initializeComponent: sender => {
const _comp2 = _mc("i", {
preconfig: sender => {
sender.bindings.display(() => note.pinned);
},
initializeComponent: sender => {
sender.class.add("pin");
}
});
sender.controls.add(_comp2);
const _comp3 = _mc("span", {
initializeComponent: sender => {
const _comp = _mc("text", {
initializeComponent: sender => {
sender.bindings.add("textContent", note, "title");
},
__childExpr: () => note.title
});
sender.controls.add(_comp);
}
}, 1);
sender.controls.add(_comp3);
}
}, 1);
Elle yazılmış hâli:
motifComponent("li", (li: ComponentBase) => {
const pin = motifComponent("i", {
preconfig: (i: ComponentBase) => i.bindings.display(() => note.pinned),
initializeComponent: (i: ComponentBase) => i.class.add("pin"),
});
const title = motifComponent("span", (span: ComponentBase) => {
span.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title")));
});
li.controls.add(pin, title);
});
motifComponent("li", (li) => {
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned),
initializeComponent: (i) => i.class.add("pin"),
});
const title = motifComponent("span", (span) => {
span.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title")));
});
li.controls.add(pin, title);
});
Çıktıyı okuma anahtarı:
| Çıktıda | Elle yazımda |
|---|---|
_mc( |
motifComponent( |
_mf( |
motifFragment( |
_mfc |
FNComponent; derleyicinin içe aktarımıdır, elle yazılan kod kullanmaz |
_mv(1) |
motifCompiled; derleyici ile çalışma zamanının sözleşme işareti, elle yazılan kodda yoktur |
try { … } catch (__e) { _mr(__e); } |
motifReport; derleyici bir çocuk ifadesinin hatasını böyle raporlar. Elle yazılan getter'a try/catch gerekmez: çalışma zamanı hatayı MJX208 ile raporlar |
preconfig: sender => { sender.bindings.display(…) } |
x-display / x-wait; aynen yazılır |
indexkey: () => n.id |
key |
__isSvgElement: true |
options: { isSvg: true } |
__childExpr: () => note.title, childs: [], üçüncü argüman , 1 |
derleyici işaretleri; atılır |
bindings.method(fn) |
bindings.text(fn) ya da aynen method |
sender |
istediğiniz parametre adı ((li: ComponentBase)) |
Elle yazılan görünümlerin tuzakları
- Yanlışlıkla fragment. Eleman türü bildirmeyen sınıf fragmenttir; Vite eklentisinin işlemediği bir dosyadaki
Component<HTMLDivElement>sınıfı da öyledir.static elementTagyazın ya dasuper("div")çağırın.view()'den sarmalayıcı döndürmek bileşene eleman vermez. initializeComponentiçindeki hata yutulur. Kurulum fonksiyonu fırlatırsa eleman boş kalır; tek izMJX122ileconsole.error'a düşen kayıttır. Sık neden: kendi kurulumunda aynı adlı bir fonksiyonu çağıranconst. Kurulumbuild()'de çalıştığı için o anda ad bileşeni tutar:const title = motifComponent("p", (p: ComponentBase) => p.controls.add(title("…")))içindekititle("…")bileşeni çağırmaya çalışır.motifComponentanydöndürdüğünden tip denetimi bunu görmez; testte çizilen metni doğrulayın ya daconsole.error'ı gözleyin.display/waityanlış kancada.initializeComponentiçinde kaydedilirse gizli eleman bir kez kurulur;preconfigiçinde kaydedin.- Derleyici yok, tanı yok.
MJX001–MJX005veMJX016yalnız JSX içindir;indexkeysiz liste ya da getter olması gereken düz değer için uyarı gelmez. Düz değer anlık görüntüdür, JSX'teki gibi:CountBadgeiçinmotifComponent(CountBadge, { count: notes.count("pinned") })sayıyı bir kez okur, canlı kalması içincount: () => notes.count("pinned")yazın. - Tipler sizindir. Kurulum parametresini (
(c: ComponentBase)),refalanını (search!: Component<HTMLInputElement>) vemotif.onişleyicisini ((_sender: ComponentBase, e: KeyboardEvent)) siz yazarsınız. controls.addanındadır, beklenecek bir şey yoktur; reaktif güncellemeler ise JSX'teki gibi bir mikro görevde uygulanır (testteawait Promise.resolve()).
Sonraki adım
- Reaktivite: bağların izlediği
reactive,createSignalveeffect. - Koşullu Gösterim ve Listeler:
wait/display,when,ternary,listveswitchCase'in davranışı. - Olaylar:
motif.onimzaları, değiştiriciler ve uygulama olayları. - Yaşam Döngüsü:
initializeComponentve kancaların sırası.