Temel kavramlar

Koşullu Gösterim ve Listeler

x-wait/x-display, && ve üçlü operatör, .map listeleri, key ve öğe kimliği, switch ve bağ API'si; JSX ile ve JSX'siz.

MotifJS'te koşullu gösterimin birincil yolu x-wait direktifidir; liste gösterimi JSX içinde .map ile yazılır. @motifx/compiler bunları reaktif bağlara çevirir: bindings.wait, bindings.display, bindings.when, bindings.ternary, bindings.list, bindings.switchCase, bindings.method. Bu sayfa her JSX yazımının yanında JSX'siz karşılığını, yani altındaki bağ çağrısını gösterir.

Örnekler başlangıç şablonundaki not uygulamasının kesitleridir: not listesi (NotesLayout), düzenleyici (NotePage) ve kenar çubuğu (MainLayout). Note ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındakilerdir; visible liste düzeninde görünen notlar (sınıfta ve Options'ta this.visible, fonksiyonda visible()), state bileşenin kendi durumudur (sınıfta ve Options'ta this.state, fonksiyonda yerel state).

Koşullu gösterim

x-wait — birincil yöntem

Bir elemanı koşula bağlı göstermenin MotifJS'teki doğal yolu x-wait direktifidir. Fonksiyon true döndürdüğü sürece eleman bekler, false olunca görünür. Okunuşu "şu koşul sağlanana kadar bekle"dir. x-display aynı mekanizmanın ters çevrilmişidir: true iken gösterir. JSX'siz yazımda ikisi de preconfig'e yazılır, çünkü bileşen kurulmadan önce değerlendirilir:

<div class="rows" x-wait={() => this.visible.length === 0}>…</div>
<p class="list__empty" x-display={() => this.visible.length === 0}>Bu klasörde not yok.</p>
<div class="rows" x-wait={() => visible().length === 0}>…</div>
<p class="list__empty" x-display={() => visible().length === 0}>Bu klasörde not yok.</p>
motifComponent("div", {
  preconfig: (rows: ComponentBase) => rows.bindings.wait(() => this.visible.length === 0),
  initializeComponent: (rows: ComponentBase) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p: ComponentBase) => p.bindings.display(() => this.visible.length === 0),
  initializeComponent: (p: ComponentBase) => {
    p.class.add("list__empty");
    p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Bu klasörde not yok.")));
  },
});
motifComponent("div", {
  preconfig: (rows: ComponentBase) => rows.bindings.wait(() => visible().length === 0),
  initializeComponent: (rows: ComponentBase) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p: ComponentBase) => p.bindings.display(() => visible().length === 0),
  initializeComponent: (p: ComponentBase) => {
    p.class.add("list__empty");
    p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Bu klasörde not yok.")));
  },
});
<div class="rows" x-wait={() => this.visible.length === 0}>…</div>
<p class="list__empty" x-display={() => this.visible.length === 0}>Bu klasörde not yok.</p>
<div class="rows" x-wait={() => visible().length === 0}>…</div>
<p class="list__empty" x-display={() => visible().length === 0}>Bu klasörde not yok.</p>
motifComponent("div", {
  preconfig: (rows) => rows.bindings.wait(() => this.visible.length === 0),
  initializeComponent: (rows) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p) => p.bindings.display(() => this.visible.length === 0),
  initializeComponent: (p) => {
    p.class.add("list__empty");
    p.controls.add(motifComponent("text", (node) => node.setText("Bu klasörde not yok.")));
  },
});
motifComponent("div", {
  preconfig: (rows) => rows.bindings.wait(() => visible().length === 0),
  initializeComponent: (rows) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p) => p.bindings.display(() => visible().length === 0),
  initializeComponent: (p) => {
    p.class.add("list__empty");
    p.controls.add(motifComponent("text", (node) => node.setText("Bu klasörde not yok.")));
  },
});

Fonksiyon bileşeninde notes ve settings FromService ile alınan servisler, state reactive ile kurulan yerel durumdur; visible() ve folder() bileşen fonksiyonunun içindeki yerel fonksiyonlardır.

İkisi de her türlü etikette çalışır: düz DOM elemanı, sınıf bileşeni (elemansız sınıf dahil), fonksiyon ve Options bileşeni. Kenar çubuğundaki sabitler rozeti ilk sabit not gelene kadar kurulmaz; CountBadge hangi biçimde yazılmış olursa olsun aynıdır:

<CountBadge count={() => this.notes.count("pinned")} x-wait={() => this.notes.count("pinned") === 0} />
<CountBadge count={() => notes.count("pinned")} x-wait={() => notes.count("pinned") === 0} />
motifComponent(CountBadge, {
  count: () => this.notes.count("pinned"),
  preconfig: (badge: ComponentBase) => badge.bindings.wait(() => this.notes.count("pinned") === 0),
});
motifComponent(CountBadge, {
  count: () => notes.count("pinned"),
  preconfig: (badge: ComponentBase) => badge.bindings.wait(() => notes.count("pinned") === 0),
});
<CountBadge count={() => this.notes.count("pinned")} x-wait={() => this.notes.count("pinned") === 0} />
<CountBadge count={() => notes.count("pinned")} x-wait={() => notes.count("pinned") === 0} />
motifComponent(CountBadge, {
  count: () => this.notes.count("pinned"),
  preconfig: (badge) => badge.bindings.wait(() => this.notes.count("pinned") === 0),
});
motifComponent(CountBadge, {
  count: () => notes.count("pinned"),
  preconfig: (badge) => badge.bindings.wait(() => notes.count("pinned") === 0),
});

Neden x-wait?

Gizlenen eleman iki kipte de DOM'dan çıkar; gizlenen bileşende ve alt ağacındakilerde onDeactivated çalışır, gösterilince onActivated. Bağlar gizliyken de çalışır, eleman gösterildiğinde güncel değerle döner. hideStrategy farkı belirler: varsayılan 'auto' ve 'placeholder' elemanın yerine bir yorum düğümü bırakır ve gösterirken onunla yer değiştirir; 'detach' iz bırakmadan söker, gösterirken ebeveyndeki sırasına göre yeniden yerleştirir. Varsayılan liste satırlarında da geçerlidir: gizli ya da bekleyen satır yerinde iz bırakır ve liste sıralanınca iz satırla birlikte taşınır. Başta bekleyen eleman da kurulmadan önce yerine iz koyar. Bkz. Görünürlük.

&& — kur ve serbest bırak

{koşul && <X/>} de desteklenir ve bindings.when(koşulFn, kurFn) çağrısına derlenir. Farkı davranışındadır: koşul her doğru olduğunda yeni bir örnek kurulur, her yanlış olduğunda o örnek serbest bırakılır.

<div class="toolbar">
  {this.state.confirming && <span class="confirm">Not kalıcı olarak silinsin mi?</span>}
</div>
<div class="toolbar">
  {state.confirming && <span class="confirm">Not kalıcı olarak silinsin mi?</span>}
</div>
motifComponent("div", (bar: ComponentBase) => {
  bar.class.add("toolbar");
  bar.bindings.when(() => this.state.confirming, () => motifComponent("span", (span: ComponentBase) => {
    span.class.add("confirm");
    span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
  }));
});
motifComponent("div", (bar: ComponentBase) => {
  bar.class.add("toolbar");
  bar.bindings.when(() => state.confirming, () => motifComponent("span", (span: ComponentBase) => {
    span.class.add("confirm");
    span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
  }));
});
<div class="toolbar">
  {this.state.confirming && <span class="confirm">Not kalıcı olarak silinsin mi?</span>}
</div>
<div class="toolbar">
  {state.confirming && <span class="confirm">Not kalıcı olarak silinsin mi?</span>}
</div>
motifComponent("div", (bar) => {
  bar.class.add("toolbar");
  bar.bindings.when(() => this.state.confirming, () => motifComponent("span", (span) => {
    span.class.add("confirm");
    span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
  }));
});
motifComponent("div", (bar) => {
  bar.class.add("toolbar");
  bar.bindings.when(() => state.confirming, () => motifComponent("span", (span) => {
    span.class.add("confirm");
    span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
  }));
});

Dal yalnızca koşulun değeri değiştiğinde yeniden kurulur (Object.is; iki yanlış değer aynı sayılır). Değer aynı kaldıkça dal bileşen örneğini ve durumunu korur: {notes.count("pinned") > 0 && <PinnedNotes />} sabit not sayısı 1 → 2 → 3 giderken aynı PinnedNotes'u tutar. Koşulun değeri bir nesneyse ({state.selected && <NoteFields note={state.selected} />}), başka bir nesneyle değiştirildiğinde dal yeniden kurulur: her seçimde yeni bir NoteFields.

Bu yüzden &&'yi yalnızca kurulum/serbest bırakmanın kendisini istediğinizde seçin: bir üçüncü parti eklentinin gerçekten yok edilmesi gerekiyorsa, ya da içerik o kadar ağır ki bellekte tutulmaması gerekiyorsa. Kalan her durumda x-wait daha ucuz ve daha öngörülebilirdir.

Koşul JS truthiness'ine göre değerlendirilir: false, null, undefined, 0, '' ve NaN için içerik çizilmez. Yani {state.selected && <NoteFields … />} (nesne ya da null) ve {notes.count("pinned") && <PinnedNotes />} (sayı) beklendiği gibi davranır; 0 ekrana yazılmaz. Sayı koşulunda dal, sayı her değiştiğinde yeniden kurulur (yukarıdaki değer kuralı); örneği korumak için {notes.count("pinned") > 0 && <PinnedNotes />} yazın. Bu kural hem gerçek bir elemanın içinde hem de bir fragment (<>…</>) kökünün doğrudan çocuğu olarak geçerlidir.

Hangisini seçmeli?

Durum Yöntem
Göster/gizle, durum korunmalı x-wait / x-display
Açılışta kapalı, ağır içerik x-wait (hiç kurulmaz)
Her kapanışta gerçekten serbest bırakılmalı {koşul && <X/>}
İki daldan biri üçlü operatör
Yalnızca metin değişiyor getter

Üçlü operatör — iki daldan biri

<div class="toolbar">
  {this.state.confirming
    ? <span class="confirm">Not kalıcı olarak silinsin mi?</span>
    : <button type="button" class="button--sm danger" onclick={() => (this.state.confirming = true)}>Sil</button>}
</div>
<div class="toolbar">
  {state.confirming
    ? <span class="confirm">Not kalıcı olarak silinsin mi?</span>
    : <button type="button" class="button--sm danger" onclick={() => (state.confirming = true)}>Sil</button>}
</div>
motifComponent("div", (bar: ComponentBase) => {
  bar.class.add("toolbar");
  bar.bindings.ternary(
    () => this.state.confirming,
    (frame) => frame.navigate(motifComponent("span", (span: ComponentBase) => {
      span.class.add("confirm");
      span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
    })),
    (frame) => frame.navigate(motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (this.state.confirming = true));
      button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sil")));
    })),
  );
});
motifComponent("div", (bar: ComponentBase) => {
  bar.class.add("toolbar");
  bar.bindings.ternary(
    () => state.confirming,
    (frame) => frame.navigate(motifComponent("span", (span: ComponentBase) => {
      span.class.add("confirm");
      span.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Not kalıcı olarak silinsin mi?")));
    })),
    (frame) => frame.navigate(motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (state.confirming = true));
      button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sil")));
    })),
  );
});
<div class="toolbar">
  {this.state.confirming
    ? <span class="confirm">Not kalıcı olarak silinsin mi?</span>
    : <button type="button" class="button--sm danger" onclick={() => (this.state.confirming = true)}>Sil</button>}
</div>
<div class="toolbar">
  {state.confirming
    ? <span class="confirm">Not kalıcı olarak silinsin mi?</span>
    : <button type="button" class="button--sm danger" onclick={() => (state.confirming = true)}>Sil</button>}
</div>
motifComponent("div", (bar) => {
  bar.class.add("toolbar");
  bar.bindings.ternary(
    () => this.state.confirming,
    (frame) => frame.navigate(motifComponent("span", (span) => {
      span.class.add("confirm");
      span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
    })),
    (frame) => frame.navigate(motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (this.state.confirming = true));
      button.controls.add(motifComponent("text", (node) => node.setText("Sil")));
    })),
  );
});
motifComponent("div", (bar) => {
  bar.class.add("toolbar");
  bar.bindings.ternary(
    () => state.confirming,
    (frame) => frame.navigate(motifComponent("span", (span) => {
      span.class.add("confirm");
      span.controls.add(motifComponent("text", (node) => node.setText("Not kalıcı olarak silinsin mi?")));
    })),
    (frame) => frame.navigate(motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (state.confirming = true));
      button.controls.add(motifComponent("text", (node) => node.setText("Sil")));
    })),
  );
});

Her dal bir Frame alır ve frame.navigate(...) ile içeriğini yerleştirir. Koşul değiştikçe uygun dal gösterilir, diğeri serbest bırakılır; eski dalın çıkış geçişi varsa oynatılır ve sıra en yakın eleman atasının transition.mode'una uyar (bkz. Stil ve Animasyon). İki dal da hafifse bu en okunaklı biçimdir; ağırsa iki elemanı x-wait / x-display ile yan yana koymak örnekleri korur. Bir dalın birden çok kökü varsa JSX'te <>…</>, JSX'siz yazımda motifFragment verilir; düzenleyicinin araç çubuğundaki onay böyledir (bkz. Dal ve liste için fragment).

Dal yalnızca koşulun değeri değiştiğinde (Object.is) yeniden kurulur; iç içe ternary'de de iç dal, dış koşulun değeri aynı kaldıkça korunur. {notes.count("pinned") > 0 ? <PinnedNotes /> : <p class="hint">Sabit not yok.</p>} sabit not sayısı 1 → 2 giderken PinnedNotes'u yeniden kurmaz.

Dal içindeki bileşen prop'ları

Dal içinde bileşene düz ifadeyle verilen prop, koşulun dışında olduğu gibi dalın kurulduğu andaki değerdir. <CountBadge count={notes.count("pinned")} /> sonradan sabitlenen notu görmez; dal koşulun değeri değişmedikçe yeniden kurulmaz. Prop'un canlı kalması için getter geçin ve alıcıda Bind<T> sözleşmesiyle okuyun (bkz. Özniteliklerde ternary). JSX'siz yazımda getter'ı zaten siz yazarsınız:

<CountBadge count={() => this.notes.count("pinned")} />
<CountBadge count={() => notes.count("pinned")} />
motifComponent(CountBadge, { count: () => this.notes.count("pinned") });
motifComponent(CountBadge, { count: () => notes.count("pinned") });
<CountBadge count={() => this.notes.count("pinned")} />
<CountBadge count={() => notes.count("pinned")} />
motifComponent(CountBadge, { count: () => this.notes.count("pinned") });
motifComponent(CountBadge, { count: () => notes.count("pinned") });

CountBadge prop'u count: Bind<number> olarak alır ve read(props.count) ile okur (sınıfta read(this.props.count)); değer de getter da verilebilir (bkz. Ortak öznitelikler).

Getter içinde koşullu metin

Basit metin için getter yeterlidir, eleman bile değişmez:

<button type="button" onclick={() => this.notes.togglePin(note)}>{() => (note.pinned ? "Sabitli" : "Sabitle")}</button>
<button type="button" onclick={() => notes.togglePin(note)}>{() => (note.pinned ? "Sabitli" : "Sabitle")}</button>
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => this.notes.togglePin(note));
  button.bindings.text(() => (note.pinned ? "Sabitli" : "Sabitle"));
});
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.bindings.text(() => (note.pinned ? "Sabitli" : "Sabitle"));
});
<button type="button" onclick={() => this.notes.togglePin(note)}>{() => (note.pinned ? "Sabitli" : "Sabitle")}</button>
<button type="button" onclick={() => notes.togglePin(note)}>{() => (note.pinned ? "Sabitli" : "Sabitle")}</button>
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => this.notes.togglePin(note));
  button.bindings.text(() => (note.pinned ? "Sabitli" : "Sabitle"));
});
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.bindings.text(() => (note.pinned ? "Sabitli" : "Sabitle"));
});

JSX'teki getter bindings.ternary'ye derlenir; iki dal birer metin düğümüdür. JSX'siz yazımda aynı metni tek bir bağ yazar: bindings.text elemanın textContent'ini değiştirir, elemanda başka çocuk varsa onlar da silinir.

Blok gövdeli getter

Çocuk konumundaki getter blok gövdeli de yazılabilir; return'den önceki deyimler koşulun getter'ında her değerlendirmede çalışır ve dallar bu değişkenleri görür:

Koşul fonksiyonu dalların kullanacağı değeri de hazırlayabilir: fonksiyon her değerlendirmede çalışır, dallar aynı değişkeni okur:

{() => { const label = note.title.trim() || "Adsız"; return note.pinned ? <b>{label}</b> : <i>{label}</i>; }}
let label = "";
row.bindings.ternary(
  () => {
    label = note.title.trim() || "Adsız";
    return note.pinned;
  },
  (frame) => frame.navigate(motifComponent("b", (b: ComponentBase) => b.setText(label))),
  (frame) => frame.navigate(motifComponent("i", (i: ComponentBase) => i.setText(label))),
);
{() => { const label = note.title.trim() || "Adsız"; return note.pinned ? <b>{label}</b> : <i>{label}</i>; }}
let label = "";
row.bindings.ternary(
  () => {
    label = note.title.trim() || "Adsız";
    return note.pinned;
  },
  (frame) => frame.navigate(motifComponent("b", (b) => b.setText(label))),
  (frame) => frame.navigate(motifComponent("i", (i) => i.setText(label))),
);

Dal değişkeni kurulduğu anda okur: note.pinned değişince dal değişir ve label'ın o anki değerini alır, ama koşul aynı kaldıkça başlıktaki değişiklik kurulu dala yansımaz. Dalın içinde canlı kalması gereken değer dalda bağ olarak yazılır.

Dalda getter yazın: <b>{() => note.title.trim() || "Adsız"}</b>. Deyimler arasında JSX üreten bir ifade ya da sınıf bildirimi varsa derleyici getter'ı bindings.ternary'ye çeviremez, MJX016 uyarır ve getter bindings.method olarak çalışır; içerik her değişimde yeniden kurulur. Parametre alan blok gövdeli bir fonksiyonda ({(x) => { const label = …; return … ? … : …; }}) koşul kapanışın dışına taşınır, yerel değişkenler dallarda görünmez ve ReferenceError fırlatır; derleyici bunu MJX001 ile uyarır (bkz. Derleme zamanı uyarıları).

Dalda bindings.text(() => note.title.trim() || "Adsız") yazın.

Liste gösterimi — .map

Bir diziyi .map ile öğelere dönüştürmek reaktif bir liste bağı kurar; bindings.list(öğelerFn, kurFn) olarak derlenir. JSX'siz yazımda liste, elemanı olmayan bir tutucu bileşene (motifComponent({ initializeComponent })) kurulur; her satır indexkey ile anahtarını taşır:

import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class NoteList extends Component {
  notes = this.getService(NotesService)!;

  override view() {
    return (
      <ul class="notes">
        {this.notes.list("notes").map((note) => (
          <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
            <input type="checkbox" checked={() => note.pinned} onchange={() => this.notes.togglePin(note)} />
            <span>{note.title}</span>
            <button type="button" onclick={() => this.notes.archive(note)}>Arşivle</button>
          </li>
        ))}
      </ul>
    );
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function NoteList() {
  const notes = FromService(NotesService)!;

  return (
    <ul class="notes">
      {notes.list("notes").map((note) => (
        <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
          <input type="checkbox" checked={() => note.pinned} onchange={() => notes.togglePin(note)} />
          <span>{note.title}</span>
          <button type="button" onclick={() => notes.archive(note)}>Arşivle</button>
        </li>
      ))}
    </ul>
  );
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const NoteList = () => motifOptions({
  el: "ul",

  view() {
    const notes = FromService(NotesService)!;
    this.class.add("notes");
    return (
      <>
        {notes.list("notes").map((note) => (
          <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
            <input type="checkbox" checked={() => note.pinned} onchange={() => notes.togglePin(note)} />
            <span>{note.title}</span>
            <button type="button" onclick={() => notes.archive(note)}>Arşivle</button>
          </li>
        ))}
      </>
    );
  },
});
import { Component, ComponentBase, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export class NoteList extends Component {
  notes = this.getService(NotesService)!;

  override view() {
    return motifComponent("ul", (ul: ComponentBase) => {
      ul.class.add("notes");
      ul.controls.add(motifComponent({
        initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => this.notes.list("notes"), (note: Note) => this.row(note)),
      }));
    });
  }

  private row(note: Note) {
    return motifComponent("li", {
      indexkey: () => note.id,
      initializeComponent: (li: ComponentBase) => {
        li.class.add(() => (note.pinned ? "note pinned" : "note"));
        const pin = motifComponent("input", (input: ComponentBase) => {
          input.attr.add({ type: "checkbox" });
          input.bindings.add("checked", () => note.pinned);
          input.motif.on("change", () => this.notes.togglePin(note));
        });
        const title = motifComponent("span", (span: ComponentBase) => span.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))));
        const archive = motifComponent("button", (button: ComponentBase) => {
          button.attr.add({ type: "button" });
          button.motif.on("click", () => this.notes.archive(note));
          button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
        });
        li.controls.add(pin, title, archive);
      },
    });
  }
}
import { ComponentBase, FromService, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export function NoteList() {
  const notes = FromService(NotesService)!;

  const row = (note: Note) =>
    motifComponent("li", {
      indexkey: () => note.id,
      initializeComponent: (li: ComponentBase) => {
        li.class.add(() => (note.pinned ? "note pinned" : "note"));
        const pin = motifComponent("input", (input: ComponentBase) => {
          input.attr.add({ type: "checkbox" });
          input.bindings.add("checked", () => note.pinned);
          input.motif.on("change", () => notes.togglePin(note));
        });
        const title = motifComponent("span", (span: ComponentBase) => span.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))));
        const archive = motifComponent("button", (button: ComponentBase) => {
          button.attr.add({ type: "button" });
          button.motif.on("click", () => notes.archive(note));
          button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
        });
        li.controls.add(pin, title, archive);
      },
    });

  return motifComponent("ul", (ul: ComponentBase) => {
    ul.class.add("notes");
    ul.controls.add(motifComponent({
      initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("notes"), (note: Note) => row(note)),
    }));
  });
}
import { ComponentBase, FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export const NoteList = () => motifOptions({
  el: "ul",

  view() {
    const notes = FromService(NotesService)!;
    const row = (note: Note) =>
      motifComponent("li", {
        indexkey: () => note.id,
        initializeComponent: (li: ComponentBase) => {
          li.class.add(() => (note.pinned ? "note pinned" : "note"));
          const pin = motifComponent("input", (input: ComponentBase) => {
            input.attr.add({ type: "checkbox" });
            input.bindings.add("checked", () => note.pinned);
            input.motif.on("change", () => notes.togglePin(note));
          });
          const title = motifComponent("span", (span: ComponentBase) => span.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))));
          const archive = motifComponent("button", (button: ComponentBase) => {
            button.attr.add({ type: "button" });
            button.motif.on("click", () => notes.archive(note));
            button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
          });
          li.controls.add(pin, title, archive);
        },
      });

    this.class.add("notes");
    return motifComponent({
      initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("notes"), (note: Note) => row(note)),
    });
  },
});
import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class NoteList extends Component {
  notes = this.getService(NotesService);

  view() {
    return (
      <ul class="notes">
        {this.notes.list("notes").map((note) => (
          <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
            <input type="checkbox" checked={() => note.pinned} onchange={() => this.notes.togglePin(note)} />
            <span>{note.title}</span>
            <button type="button" onclick={() => this.notes.archive(note)}>Arşivle</button>
          </li>
        ))}
      </ul>
    );
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function NoteList() {
  const notes = FromService(NotesService);

  return (
    <ul class="notes">
      {notes.list("notes").map((note) => (
        <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
          <input type="checkbox" checked={() => note.pinned} onchange={() => notes.togglePin(note)} />
          <span>{note.title}</span>
          <button type="button" onclick={() => notes.archive(note)}>Arşivle</button>
        </li>
      ))}
    </ul>
  );
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const NoteList = () => motifOptions({
  el: "ul",

  view() {
    const notes = FromService(NotesService);
    this.class.add("notes");
    return (
      <>
        {notes.list("notes").map((note) => (
          <li key={note.id} class={() => (note.pinned ? "note pinned" : "note")}>
            <input type="checkbox" checked={() => note.pinned} onchange={() => notes.togglePin(note)} />
            <span>{note.title}</span>
            <button type="button" onclick={() => notes.archive(note)}>Arşivle</button>
          </li>
        ))}
      </>
    );
  },
});
import { Component, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export class NoteList extends Component {
  notes = this.getService(NotesService);

  view() {
    return motifComponent("ul", (ul) => {
      ul.class.add("notes");
      ul.controls.add(motifComponent({
        initializeComponent: (holder) => holder.bindings.list(() => this.notes.list("notes"), (note) => this.row(note)),
      }));
    });
  }

  row(note) {
    return motifComponent("li", {
      indexkey: () => note.id,
      initializeComponent: (li) => {
        li.class.add(() => (note.pinned ? "note pinned" : "note"));
        const pin = motifComponent("input", (input) => {
          input.attr.add({ type: "checkbox" });
          input.bindings.add("checked", () => note.pinned);
          input.motif.on("change", () => this.notes.togglePin(note));
        });
        const title = motifComponent("span", (span) => span.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))));
        const archive = motifComponent("button", (button) => {
          button.attr.add({ type: "button" });
          button.motif.on("click", () => this.notes.archive(note));
          button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
        });
        li.controls.add(pin, title, archive);
      },
    });
  }
}
import { FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export function NoteList() {
  const notes = FromService(NotesService);

  const row = (note) =>
    motifComponent("li", {
      indexkey: () => note.id,
      initializeComponent: (li) => {
        li.class.add(() => (note.pinned ? "note pinned" : "note"));
        const pin = motifComponent("input", (input) => {
          input.attr.add({ type: "checkbox" });
          input.bindings.add("checked", () => note.pinned);
          input.motif.on("change", () => notes.togglePin(note));
        });
        const title = motifComponent("span", (span) => span.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))));
        const archive = motifComponent("button", (button) => {
          button.attr.add({ type: "button" });
          button.motif.on("click", () => notes.archive(note));
          button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
        });
        li.controls.add(pin, title, archive);
      },
    });

  return motifComponent("ul", (ul) => {
    ul.class.add("notes");
    ul.controls.add(motifComponent({
      initializeComponent: (holder) => holder.bindings.list(() => notes.list("notes"), (note) => row(note)),
    }));
  });
}
import { FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const NoteList = () => motifOptions({
  el: "ul",

  view() {
    const notes = FromService(NotesService);
    const row = (note) =>
      motifComponent("li", {
        indexkey: () => note.id,
        initializeComponent: (li) => {
          li.class.add(() => (note.pinned ? "note pinned" : "note"));
          const pin = motifComponent("input", (input) => {
            input.attr.add({ type: "checkbox" });
            input.bindings.add("checked", () => note.pinned);
            input.motif.on("change", () => notes.togglePin(note));
          });
          const title = motifComponent("span", (span) => span.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))));
          const archive = motifComponent("button", (button) => {
            button.attr.add({ type: "button" });
            button.motif.on("click", () => notes.archive(note));
            button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
          });
          li.controls.add(pin, title, archive);
        },
      });

    this.class.add("notes");
    return motifComponent({
      initializeComponent: (holder) => holder.bindings.list(() => notes.list("notes"), (note) => row(note)),
    });
  },
});

Dizi değiştiğinde (ekleme, çıkarma, sıralama) MotifJS yalnızca farkı DOM'a uygular; tüm listeyi yeniden oluşturmaz. notes.list("notes") arşivlenmemiş notları süzdüğü için archive(note) satırı listeden düşürür; togglePin o satırın sınıfını ve kutusunu günceller; liste sabitli notları başa aldığı için satır yeniden kurulmadan başa taşınır.

key ve öğe kimliği

Satırlar öğe nesnesinin kimliğiyle eşleşir: yeni dizide aynı nesne (reaktif proxy'nin ham nesnesi) yine yer alıyorsa satırı yeniden kullanılır ve gerekirse DOM'da taşınır; dizide bulunmayan nesnenin satırı serbest bırakılır. Diziye yeni nesneler konursa (ör. sunucudan gelen taze liste) aynı id'yi taşısalar bile satırlar yeniden kurulur. Aynı nesne dizide birden çok kez bulunabilir (push(items[0]), splice(i, 0, items[j])); her geçiş kendi satırını alır ve hepsi aynı nesneyi gösterir, nesnedeki değişiklik hepsine yansır. Dizi metotları öğeyi kopyalamaz; bağımsız bir kopya gerekiyorsa açıkça kopyalayın: items.splice(i, 0, { ...items[j] }). İlkel değerli dizilerde (string[]) aynı sıradaki aynı değer satırını korur; şablon fonksiyonu ikinci argümanı (index) almıyorsa yer değiştiren aynı değer de satırını korur. Nesne öğelerde satır taşınırken yeniden kullanıldığı için şablonun aldığı index satırın kurulduğu andaki sıradır; sıralamadan sonra güncellenmez.

key bu eşleşmeye katılmaz ve DOM'un yeniden kullanımını değiştirmez; isteğe bağlıdır ve öğeyi tanımlar. Yazarsanız kararlı, benzersiz bir değer verin (genelde note.id): aynı key iki öğede görülürse geliştirme modunda MJX202 uyarısı verilir. x-key ve indexkey adları key ile aynıdır; JSX'siz yazımda indexkey: () => note.id 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), bütün satırlar en son yazılan anahtarı okur ve liste güncellendiğinde MJX202 yersiz gelir. Kökün props'unu her çağrıda yeni bir nesne olarak verin (bkz. Props nesnesinin anahtarları).

Öğe içi reaktivite

Liste öğeleri de reaktiftir. NoteList'teki satırda note.title değişince yalnız o satırın metni güncellenir. note.pinned değişince satırın sınıfı ve onay kutusu güncellenir; liste sıralamayı da bu alandan okuduğu için satırı yerinde kurulu bırakıp yeni sırasına taşır. Satır şablonu bir bileşen döndürmek zorundadır (örnek, sınıf ya da fabrika); başka bir değer MJX201 fırlatır ve o güncellemede liste hiçbir satır yerleştirmez. Koşula göre satır atlamak için null döndürmeyin, diziyi önce süzün (bkz. Filtreleme).

.map'in ikinci argümanı JS'teki gibi çalışır: items.map(function (i) { … }, ctx) içinde this, ctx'tir. Ok fonksiyonu kendi this'ini korur.

Satır elemanını prop olarak alır. Satıra yalnız kimlik verip elemanı her satırda items.find((n) => n.id === id) ile aratmak her satırı her elemana abone eder, n satırda n² abonelik (bkz. Her satırın listeyi taradığı tuzak).

Filtreleme

.filter().map() zinciri desteklenir; koşul reaktif kaynaklara bağlıysa filtre de reaktif olur:

<ul class="pinned">
  {this.notes.list("notes").filter((note) => note.pinned).map((note) => <li key={note.id}>{note.title}</li>)}
</ul>
<ul class="pinned">
  {notes.list("notes").filter((note) => note.pinned).map((note) => <li key={note.id}>{note.title}</li>)}
</ul>
motifComponent("ul", (ul: ComponentBase) => {
  ul.class.add("pinned");
  ul.controls.add(motifComponent({
    initializeComponent: (holder: ComponentBase) => holder.bindings.list(
      () => this.notes.list("notes").filter((note) => note.pinned),
      (note: Note) => motifComponent("li", { indexkey: () => note.id, initializeComponent: (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))) }),
    ),
  }));
});
motifComponent("ul", (ul: ComponentBase) => {
  ul.class.add("pinned");
  ul.controls.add(motifComponent({
    initializeComponent: (holder: ComponentBase) => holder.bindings.list(
      () => notes.list("notes").filter((note) => note.pinned),
      (note: Note) => motifComponent("li", { indexkey: () => note.id, initializeComponent: (li: ComponentBase) => li.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))) }),
    ),
  }));
});
<ul class="pinned">
  {this.notes.list("notes").filter((note) => note.pinned).map((note) => <li key={note.id}>{note.title}</li>)}
</ul>
<ul class="pinned">
  {notes.list("notes").filter((note) => note.pinned).map((note) => <li key={note.id}>{note.title}</li>)}
</ul>
motifComponent("ul", (ul) => {
  ul.class.add("pinned");
  ul.controls.add(motifComponent({
    initializeComponent: (holder) => holder.bindings.list(
      () => this.notes.list("notes").filter((note) => note.pinned),
      (note) => motifComponent("li", { indexkey: () => note.id, initializeComponent: (li) => li.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))) }),
    ),
  }));
});
motifComponent("ul", (ul) => {
  ul.class.add("pinned");
  ul.controls.add(motifComponent({
    initializeComponent: (holder) => holder.bindings.list(
      () => notes.list("notes").filter((note) => note.pinned),
      (note) => motifComponent("li", { indexkey: () => note.id, initializeComponent: (li) => li.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))) }),
    ),
  }));
});

switch benzeri çoklu dal

Bir ayırıcı değere göre farklı içerikler göstermek için switchCase bağı vardır. JSX'te blok gövdeli bir getter içindeki switch ile yazılır; derleyici her case'in return ettiği dalı switchCase'e çevirir:

Ayarlar sayfası seçili temanın adını gösterir; sistem teması için bir şey yazmaz:

<span class="theme">
  {() => {
    switch (this.settings.state.theme) {
      case "light": return <i>Açık tema</i>;
      case "dark":  return <i>Koyu tema</i>;
    }
  }}
</span>
<span class="theme">
  {() => {
    switch (settings.state.theme) {
      case "light": return <i>Açık tema</i>;
      case "dark":  return <i>Koyu tema</i>;
    }
  }}
</span>
motifComponent("span", (label: ComponentBase) => {
  label.class.add("theme");
  label.bindings.switchCase(() => this.settings.state.theme, {
    light: (frame) => frame.navigate(motifComponent("i", (i: ComponentBase) => i.setText("Açık tema"))),
    dark: (frame) => frame.navigate(motifComponent("i", (i: ComponentBase) => i.setText("Koyu tema"))),
  });
});
motifComponent("span", (label: ComponentBase) => {
  label.class.add("theme");
  label.bindings.switchCase(() => settings.state.theme, {
    light: (frame) => frame.navigate(motifComponent("i", (i: ComponentBase) => i.setText("Açık tema"))),
    dark: (frame) => frame.navigate(motifComponent("i", (i: ComponentBase) => i.setText("Koyu tema"))),
  });
});
<span class="theme">
  {() => {
    switch (this.settings.state.theme) {
      case "light": return <i>Açık tema</i>;
      case "dark":  return <i>Koyu tema</i>;
    }
  }}
</span>
<span class="theme">
  {() => {
    switch (settings.state.theme) {
      case "light": return <i>Açık tema</i>;
      case "dark":  return <i>Koyu tema</i>;
    }
  }}
</span>
motifComponent("span", (label) => {
  label.class.add("theme");
  label.bindings.switchCase(() => this.settings.state.theme, {
    light: (frame) => frame.navigate(motifComponent("i", (i) => i.setText("Açık tema"))),
    dark: (frame) => frame.navigate(motifComponent("i", (i) => i.setText("Koyu tema"))),
  });
});
motifComponent("span", (label) => {
  label.class.add("theme");
  label.bindings.switchCase(() => settings.state.theme, {
    light: (frame) => frame.navigate(motifComponent("i", (i) => i.setText("Açık tema"))),
    dark: (frame) => frame.navigate(motifComponent("i", (i) => i.setText("Koyu tema"))),
  });
});

Her dal bir Frame alır; frame.navigate(...) ile o dalın içeriğini yerleştirirsiniz. Ayırıcının değeri String(...) ile anahtara çevrilir; aynı değer art arda gelirse dal yeniden kurulmaz, eşleşmeyen bir değerden başka bir eşleşmeyen değere geçişte varsayılan dal da yerinde kalır. Hiçbir case eşleşmezse (system gibi) yer boşalır, önceki dal serbest bırakılır. Bir dal null ya da undefined döndürdüğünde de (default: return null, return saving ? <i>…</i> : null) yer boşalır. JSX'siz yazımda varsayılan dal üçüncü argümandır; frame.navigate(null) hiçbir şey yerleştirmez ve önceki içerik yerinde kalır, yeri boşaltmak için (frame) => frame.flush() kullanılır.

case etiketleri dizge, sayı ya da boolean literal olmalıdır. Blok gövdesinde switch'ten önce yazılan deyimler ayırıcı getter'ında her değerlendirmede çalışır ve dallar bu değişkenleri görür; dal değişkeni kurulduğu anda okur, ayırıcı aynı kaldıkça değişkendeki değişiklik dala yansımaz; bir case içinde return'den önceki deyimler dal kurulurken çalışır; case "a": case "b": return <X/>; yazımında "a" da X'i gösterir; break ile çıkan dal ve switch'ten sonraki deyimler varsayılan yolda çalışır. default dalı yoksa switch'ten sonra gelen return <X/> varsayılan dal olur; hiçbir case eşleşmeyince o içerik gösterilir (return null yeri boşaltır). Derleyicinin switchCase'e çeviremediği şekil yazıldığı gibi bindings.method olarak çalışır ve derleyici MJX016 uyarır: literal olmayan ya da iki kez yazılmış case etiketi, ikinci bir default ya da switch, switch'ten önce return, JSX üreten deyim ya da sınıf bildirimi, case içinde son deyim olmayan ya da etiketli break/continue. O yazımda dal tutulmaz, içerik her değişimde yeniden kurulur.

Bağ API'sini doğrudan kullanmak

JSX bu çağrıların kısa yazımıdır; aynı API JSX'siz de yazılır ve initializeComponent / onConfig içinde ya da testlerde doğrudan çağrılır. Hangi JSX biçiminin hangi çağrıya indiğini tahmin etmek yerine yazdırın: npx motif-explain dosya.tsx (bkz. Bu ifade neye derlendi?).

Metot İşlev
bindings.add(prop, source, member?, format?, formatInfo?) Bir DOM özelliğine/özniteliğine tek yönlü bağ kurar.
bindings.text(fn) textContent'i reaktif bağlar (x-text).
bindings.value(fn) value'yu reaktif bağlar (x-value).
bindings.html(fn) innerHTML'i reaktif bağlar (x-html).
bindings.when(condFn, renderFn) Koşullu içerik (&&) — koşulun değeri değişip doğru olduğunda yeni örnek.
bindings.ternary(condFn, trueFn, falseFn) İki dallı koşul; dal koşulun değeri değişince yeniden kurulur.
bindings.list(itemsFn, renderFn) Liste gösterimi (.map); satırlar öğe nesnesiyle eşleşir.
bindings.loop(itemsFn, renderFn) list ile aynı; alternatif ad.
bindings.switchCase(discFn, cases, defaultFn?) Çoklu dal.
bindings.method(fn) Reaktif metin veya reaktif tek-içerik; fn her çalışmada bir kez çağrılır. Dizge, sayı, boolean ve bigint metin olarak yazılır; bileşen bir Frame'e yerleşir. Sonuç metin ile bileşen arasında değişebilir, yeni içerik aynı yerde çizilir. null/undefined gelirse yer boşalır: önceki içerik bileşense serbest bırakılır, metinse metin boşalır; sonra gelen içerik yeri yeniden doldurur.
bindings.watch(fn) Yan etki izleyici (x-watch; serbest bırakılırken otomatik durur).
bindings.model(source, member?, format?, formatInfo?) / bindings.model(getter, setter) İki yönlü bağ (bkz. Formlar).
bindings.writeModel(value) Değeri bileşendeki model/x-model bağlarına geri yazar; yazdıysa true döner (bkz. Formlar).
bindings.wait(fn) / bindings.display(fn) Bekletme / görünürlük (x-wait / x-display) — örnek korunur; preconfig içinde çağrılır.
bindings.remove(binding) / bindings.clear() Bir bağı (ya da hepsini) koleksiyondan çıkarır ve devre dışı bırakır; add/html/list/model/wait/display çağrılarının döndürdüğü bağ ile. when, ternary, switchCase, method ve watch kayıtlı bağ değildir, bileşenle birlikte durur.
bindings.items Kayıtlı bağların kopyası (add/html/list/model/wait/display ile kurulanlar).

bindings.add örneği

<input type="checkbox" onConfig={(box) => box.bindings.add("checked", note, "pinned")} />
motifComponent("input", (box: ComponentBase) => {
  box.attr.add({ type: "checkbox" });
  box.bindings.add("checked", note, "pinned");
});
<input type="checkbox" onConfig={(box) => box.bindings.add("checked", note, "pinned")} />
motifComponent("input", (box) => {
  box.attr.add({ type: "checkbox" });
  box.bindings.add("checked", note, "pinned");
});

bindings.add imzaları:

Çağrı Okunan değer
add(propertyName, dataSource) dataSource'un kendisi; bir getter ise döndürdüğü değer
add(propertyName, dataSource, dataMember) dataSource üzerindeki dataMember alanı (noktalı yol olabilir)
add(propertyName, dataSource, dataMember, formatString) Aynı alan, formatString ile biçimlenmiş
add(propertyName, dataSource, dataMember, formatString, formatInfo) Aynı alan; yerel ayar ve para birimi formatInfo'dan ({ locale, currency })
add(binding) Hazır bir bağ nesnesi

Her biçim eklenen bağı (IBaseBinding) döndürür; propertyName yazılacak özelliğin adıdır ("textContent", "value" …).

dataSource tek başına bir getter da olabilir (add("textContent", () => note.title)); dataMember verildiğinde o alan okunur, noktalı yol da olur ("author.name"). formatString kodları: sayılarda C para, P yüzde (0.25 → %25), N/N2 sabit ondalık (varsayılan 2), sayı kodları küçük harfle de yazılır; Date değerlerinde d tarih, t saat. Başka türdeki değer ya da tanınmayan kod toString() ile yazılır. Yerel ayar ve para birimi formatInfo'dan gelir: bindings.add("textContent", order, "total", "C", { locale: "tr-TR", currency: "TRY" }) → ₺1.234,50. locale verilmezse çalışma ortamının varsayılan yerel ayarı kullanılır; currency verilmezse C tutarı iki ondalıkla, simgesiz yazar.

bindings.list örneği

holder.bindings.list(
  () => this.visible,
  (note, index) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder }),
);
holder.bindings.list(
  () => visible(),
  (note, index) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() }),
);
holder.bindings.list(
  () => this.visible,
  (note, index) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder }),
);
holder.bindings.list(
  () => visible(),
  (note, index) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() }),
);

İlk argüman öğe kaynağıdır: dizi ya da iterable döndüren bir fonksiyon. İkincisi satır şablonudur ve bir bileşen döndürür.

Fonksiyon bileşeninde visible() ve folder() bileşen fonksiyonunun içindeki yerel fonksiyonlardır.

Büyük listeler

Bir sayfaya binlerce satır tek seferde girdiğinde tarayıcı, satırların tamamı kurulup yerleştirilene kadar tepki veremez. MotifJS'te bunun için iki araç vardır:

Dilimli kurulum — x-chunk

Direktif satırları alan elemana değersiz yazılır. Arşiv sayfası bütün arşiv notlarını tek tabloda listeler: ilk 100 satır sayfayla birlikte gelir, kalanı sonraki karelere yayılır.

import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class ArchivePage extends Component {
  notes = this.getService(NotesService)!;

  override view() {
    return (
      <table class="archive">
        <tbody x-chunk>
          {this.notes.list("archive").map((note) => (
            <tr key={note.id}>
              <td>{note.title}</td>
              <td>{() => (note.pinned ? "Sabit" : "")}</td>
            </tr>
          ))}
        </tbody>
      </table>
    );
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function ArchivePage() {
  const notes = FromService(NotesService)!;

  return (
    <table class="archive">
      <tbody x-chunk>
        {notes.list("archive").map((note) => (
          <tr key={note.id}>
            <td>{note.title}</td>
            <td>{() => (note.pinned ? "Sabit" : "")}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const ArchivePage = () => motifOptions({
  el: "table",

  view() {
    const notes = FromService(NotesService)!;
    this.class.add("archive");
    return (
      <tbody x-chunk>
        {notes.list("archive").map((note) => (
          <tr key={note.id}>
            <td>{note.title}</td>
            <td>{() => (note.pinned ? "Sabit" : "")}</td>
          </tr>
        ))}
      </tbody>
    );
  },
});
import { Component, ComponentBase, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export class ArchivePage extends Component {
  notes = this.getService(NotesService)!;

  override view() {
    return motifComponent("table", (table: ComponentBase) => {
      table.class.add("archive");
      table.controls.add(motifComponent("tbody", {
        options: { chunk: true },
        initializeComponent: (body: ComponentBase) => body.controls.add(motifComponent({
          initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => this.notes.list("archive"), (note: Note) => this.row(note)),
        })),
      }));
    });
  }

  private row(note: Note) {
    return motifComponent("tr", {
      indexkey: () => note.id,
      initializeComponent: (tr: ComponentBase) => {
        const title = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => note.title))));
        const pinned = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
        tr.controls.add(title, pinned);
      },
    });
  }
}
import { ComponentBase, FromService, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export function ArchivePage() {
  const notes = FromService(NotesService)!;

  const row = (note: Note) =>
    motifComponent("tr", {
      indexkey: () => note.id,
      initializeComponent: (tr: ComponentBase) => {
        const title = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => note.title))));
        const pinned = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
        tr.controls.add(title, pinned);
      },
    });

  return motifComponent("table", (table: ComponentBase) => {
    table.class.add("archive");
    table.controls.add(motifComponent("tbody", {
      options: { chunk: true },
      initializeComponent: (body: ComponentBase) => body.controls.add(motifComponent({
        initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("archive"), (note: Note) => row(note)),
      })),
    }));
  });
}
import { ComponentBase, FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";

export const ArchivePage = () => motifOptions({
  el: "table",

  view() {
    const notes = FromService(NotesService)!;
    const row = (note: Note) =>
      motifComponent("tr", {
        indexkey: () => note.id,
        initializeComponent: (tr: ComponentBase) => {
          const title = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => note.title))));
          const pinned = motifComponent("td", (td: ComponentBase) => td.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
          tr.controls.add(title, pinned);
        },
      });

    this.class.add("archive");
    return motifComponent("tbody", {
      options: { chunk: true },
      initializeComponent: (body: ComponentBase) => body.controls.add(motifComponent({
        initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => notes.list("archive"), (note: Note) => row(note)),
      })),
    });
  },
});
import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";

export class ArchivePage extends Component {
  notes = this.getService(NotesService);

  view() {
    return (
      <table class="archive">
        <tbody x-chunk>
          {this.notes.list("archive").map((note) => (
            <tr key={note.id}>
              <td>{note.title}</td>
              <td>{() => (note.pinned ? "Sabit" : "")}</td>
            </tr>
          ))}
        </tbody>
      </table>
    );
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";

export function ArchivePage() {
  const notes = FromService(NotesService);

  return (
    <table class="archive">
      <tbody x-chunk>
        {notes.list("archive").map((note) => (
          <tr key={note.id}>
            <td>{note.title}</td>
            <td>{() => (note.pinned ? "Sabit" : "")}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
import { FromService, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const ArchivePage = () => motifOptions({
  el: "table",

  view() {
    const notes = FromService(NotesService);
    this.class.add("archive");
    return (
      <tbody x-chunk>
        {notes.list("archive").map((note) => (
          <tr key={note.id}>
            <td>{note.title}</td>
            <td>{() => (note.pinned ? "Sabit" : "")}</td>
          </tr>
        ))}
      </tbody>
    );
  },
});
import { Component, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export class ArchivePage extends Component {
  notes = this.getService(NotesService);

  view() {
    return motifComponent("table", (table) => {
      table.class.add("archive");
      table.controls.add(motifComponent("tbody", {
        options: { chunk: true },
        initializeComponent: (body) => body.controls.add(motifComponent({
          initializeComponent: (holder) => holder.bindings.list(() => this.notes.list("archive"), (note) => this.row(note)),
        })),
      }));
    });
  }

  row(note) {
    return motifComponent("tr", {
      indexkey: () => note.id,
      initializeComponent: (tr) => {
        const title = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => note.title))));
        const pinned = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
        tr.controls.add(title, pinned);
      },
    });
  }
}
import { FromService, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";

export function ArchivePage() {
  const notes = FromService(NotesService);

  const row = (note) =>
    motifComponent("tr", {
      indexkey: () => note.id,
      initializeComponent: (tr) => {
        const title = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => note.title))));
        const pinned = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
        tr.controls.add(title, pinned);
      },
    });

  return motifComponent("table", (table) => {
    table.class.add("archive");
    table.controls.add(motifComponent("tbody", {
      options: { chunk: true },
      initializeComponent: (body) => body.controls.add(motifComponent({
        initializeComponent: (holder) => holder.bindings.list(() => notes.list("archive"), (note) => row(note)),
      })),
    }));
  });
}
import { FromService, motifComponent, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";

export const ArchivePage = () => motifOptions({
  el: "table",

  view() {
    const notes = FromService(NotesService);
    const row = (note) =>
      motifComponent("tr", {
        indexkey: () => note.id,
        initializeComponent: (tr) => {
          const title = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => note.title))));
          const pinned = motifComponent("td", (td) => td.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", () => (note.pinned ? "Sabit" : "")))));
          tr.controls.add(title, pinned);
        },
      });

    this.class.add("archive");
    return motifComponent("tbody", {
      options: { chunk: true },
      initializeComponent: (body) => body.controls.add(motifComponent({
        initializeComponent: (holder) => holder.bindings.list(() => notes.list("archive"), (note) => row(note)),
      })),
    });
  },
});

Her karede çerçeve iki süreyi ölçer: çocukları kurmanın süresini ve tarayıcının onları yerleştirip çizmesinin süresini. Sonraki karede, ikisinin toplamı yaklaşık 50 ms tutacak kadar çocuk kurulur. Küçük bir liste birkaç karede tamamlanır; kap büyüdükçe kare başına daha çok çocuk kurulur. Kurulan sayının bir alt sınırı olsun istiyorsanız değeri verin: x-chunk={50} ilk turda tam 50 çocuk kurar, sonraki her karede en az 50 çocuk kurar, karenin süresi yetiyorsa daha fazlasını kurar.

Tarayıcı içine çocuk eklenen kabı her karede yeniden yerleştirir ve bu iş kabın boyuyla büyür. En pahalı olduğu yer tablodur: satır eklendikçe tablo sütunlarını yeniden ölçer. Kabın yerleştirilmesi tek başına 50 ms'yi aşınca kareler uzar. Çerçeve o noktada kare başına daha çok çocuk kurarak bedeli daha az kareye böler, ama kareleri kısaltamaz. Masaüstü bir tarayıcıda bu sınır tablo satırlarında 10–30 bin, düz satırlarda birkaç on bin satır civarındadır; yavaş cihazlarda daha erken gelir. Bu boydaki listeler için sanal kaydırma kullanın.

Sayım elemanın içine giren her çocuk için yapılır: doğrudan yazılan etiketler, .map satırları, koşul ya da fragment içinden gelenler aynı bütçeyi paylaşır. .map satırları sıraları gelene kadar oluşturulmaz: satır fonksiyonu çağrılmaz, bileşen kurulmaz. Bu, örnekteki gibi {notes.list("archive").map(...)} ile yazılan liste için geçerlidir. Satırlar {() => notes.list("archive").map(...)} biçiminde bir getter içinde yazılırsa bütün satır bileşenleri baştan oluşturulur, yalnız kurulumları dilimlenir. O sırada diziden çıkan öğe için satır hiç kurulmaz; dizi yeniden sıralanırsa satır yeni yerinde kurulur ve index o anki konumdur. Kendi x-wait'i olan çocuk kendi koşuluna uyar, dilimlemeye girmez. Sırası gelen çocuk olağan yaşam döngüsünü izler (onConfigured, onBuilt, onMounted). Sekme arka plandayken dilimler yine akar. Bir çocuğun kurulumu hata fırlatırsa MJX130 ile raporlanır, dilimin öteki çocukları kurulmaya devam eder. Hata veren .map satırının yerinde izi kalır ve satır, listenin bir sonraki güncellemesinde yeniden oluşturulur.

İlk dilim elemana aittir: birlikte eklenen çocuklar (aynı çalıştırmada, bir await'e kadar) ilk 100'ü paylaşır; sonradan push ile gelen satırlar yeni bir ilk dilimle başlar. Aynı sayfada dilimli birden çok eleman varsa her biri her karede payını alır ve kare süresi hepsi için birlikte ölçülür. JSX'siz yazımda options: { chunk: true } ya da sayı verilir veya kurulumdan önce motif.options.chunk ayarlanır; 0 ya da false dilimlemeyi kapatır. Değer yerine işlev verilirse kurulumda bir kez çağrılır, sonradan izlenmez. Dilimli bir sayfada kaydırma hafızası konumu, satırlar gelirken hedefe ulaşana ya da settleMs dolana kadar her karede yeniden dener — bkz. Kaydırma hafızası.

Sonraki adım

Olay yönetimini öğrenmek için Olaylar bölümüne geçin.