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?)

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ı:

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ı

  1. 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 elementTag yazın ya da super("div") çağırın. view()'den sarmalayıcı döndürmek bileşene eleman vermez.
  2. initializeComponent içindeki hata yutulur. Kurulum fonksiyonu fırlatırsa eleman boş kalır; tek iz MJX122 ile console.error'a düşen kayıttır. Sık neden: kendi kurulumunda aynı adlı bir fonksiyonu çağıran const. Kurulum build()'de çalıştığı için o anda ad bileşeni tutar: const title = motifComponent("p", (p: ComponentBase) => p.controls.add(title("…"))) içindeki title("…") bileşeni çağırmaya çalışır. motifComponent any döndürdüğünden tip denetimi bunu görmez; testte çizilen metni doğrulayın ya da console.error'ı gözleyin.
  3. display / wait yanlış kancada. initializeComponent içinde kaydedilirse gizli eleman bir kez kurulur; preconfig içinde kaydedin.
  4. Derleyici yok, tanı yok. MJX001–MJX005 ve MJX016 yalnı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: CountBadge için motifComponent(CountBadge, { count: notes.count("pinned") }) sayıyı bir kez okur, canlı kalması için count: () => notes.count("pinned") yazın.
  5. Tipler sizindir. Kurulum parametresini ((c: ComponentBase)), ref alanını (search!: Component<HTMLInputElement>) ve motif.on işleyicisini ((_sender: ComponentBase, e: KeyboardEvent)) siz yazarsınız.
  6. controls.add anındadır, beklenecek bir şey yoktur; reaktif güncellemeler ise JSX'teki gibi bir mikro görevde uygulanır (testte await Promise.resolve()).

Sonraki adım