Temel kavramlar

Props ve Çocuklar

Bileşenler arası veri akışı: `props`, prop tipleri, canlı prop'lar ve `childs` ile içerik yerleştirme.

props ve bileşenler arası veri akışı

JSX'te bir bileşene yazdığınız öznitelikler o bileşenin props'u olur. JSX'siz yazımda aynı nesne yaratma çağrısına verilir: sınıfta new Bileşen(props), fonksiyon ve Options bileşeninde motifComponent(Bileşen, props).

Örnek, ayarlar sayfasındaki bir satırdır: solda ayarın adı ve altında kısa bir açıklama, sağda ayarı değiştiren denetim. Setting adı ve açıklamayı props ile alır, denetimi çocuk olarak alır:

import { Component, read, type Bind } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
}

export class Setting extends Component<HTMLDivElement, SettingProps> {
  override onConfigured() {
    this.class.add("setting");
  }

  override view() {
    return (
      <>
        <div class="setting__text">
          <span>{this.props.name}</span>
          <small>{() => read(this.props.hint)}</small>
        </div>
        {this.childs}
      </>
    );
  }
}
import { Component, ComponentBase, motifComponent, motifFragment, read, type Bind } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
}

export class Setting extends Component<HTMLDivElement, SettingProps> {
  static override elementTag = "div";

  override onConfigured() {
    this.class.add("setting");
  }

  override view() {
    const text = motifComponent("div", (div: ComponentBase) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span: ComponentBase) => span.setText(this.props.name));
      const hint = motifComponent("small", (small: ComponentBase) => small.bindings.text(() => read(this.props.hint)));
      div.controls.add(name, hint);
    });
    return motifFragment({ childs: [text, ...(this.childs ?? [])] });
  }
}
import { read, type Bind, type ComponentBase } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
  childs?: ComponentBase[];
}

export function Setting(props: SettingProps) {
  return (
    <div class="setting">
      <div class="setting__text">
        <span>{props.name}</span>
        <small>{() => read(props.hint)}</small>
      </div>
      {props.childs}
    </div>
  );
}
import { ComponentBase, motifComponent, read, type Bind } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
  childs?: ComponentBase[];
}

export function Setting(props: SettingProps) {
  return motifComponent("div", (setting: ComponentBase) => {
    setting.class.add("setting");
    const text = motifComponent("div", (div: ComponentBase) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span: ComponentBase) => span.setText(props.name));
      const hint = motifComponent("small", (small: ComponentBase) => small.bindings.text(() => read(props.hint)));
      div.controls.add(name, hint);
    });
    setting.controls.add(text, ...(props.childs ?? []));
  });
}
import { motifOptions, read, type Bind } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
}

export const Setting = (props: SettingProps) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("setting");
  },

  view() {
    return (
      <>
        <div class="setting__text">
          <span>{props.name}</span>
          <small>{() => read(props.hint)}</small>
        </div>
        {this.childs}
      </>
    );
  },
});
import { ComponentBase, motifComponent, motifFragment, motifOptions, read, type Bind } from "@motifx/core";

interface SettingProps {
  name: string;
  hint: Bind<string>;
}

export const Setting = (props: SettingProps) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("setting");
  },

  view() {
    const text = motifComponent("div", (div: ComponentBase) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span: ComponentBase) => span.setText(props.name));
      const hint = motifComponent("small", (small: ComponentBase) => small.bindings.text(() => read(props.hint)));
      div.controls.add(name, hint);
    });
    return motifFragment({ childs: [text, ...(this.childs ?? [])] });
  },
});
import { Component, read } from "@motifx/core";

export class Setting extends Component {
  static elementTag = "div";

  onConfigured() {
    this.class.add("setting");
  }

  view() {
    return (
      <>
        <div class="setting__text">
          <span>{this.props.name}</span>
          <small>{() => read(this.props.hint)}</small>
        </div>
        {this.childs}
      </>
    );
  }
}
import { Component, motifComponent, motifFragment, read } from "@motifx/core";

export class Setting extends Component {
  static elementTag = "div";

  onConfigured() {
    this.class.add("setting");
  }

  view() {
    const text = motifComponent("div", (div) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span) => span.setText(this.props.name));
      const hint = motifComponent("small", (small) => small.bindings.text(() => read(this.props.hint)));
      div.controls.add(name, hint);
    });
    return motifFragment({ childs: [text, ...(this.childs ?? [])] });
  }
}
import { read } from "@motifx/core";

export function Setting(props) {
  return (
    <div class="setting">
      <div class="setting__text">
        <span>{props.name}</span>
        <small>{() => read(props.hint)}</small>
      </div>
      {props.childs}
    </div>
  );
}
import { motifComponent, read } from "@motifx/core";

export function Setting(props) {
  return motifComponent("div", (setting) => {
    setting.class.add("setting");
    const text = motifComponent("div", (div) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span) => span.setText(props.name));
      const hint = motifComponent("small", (small) => small.bindings.text(() => read(props.hint)));
      div.controls.add(name, hint);
    });
    setting.controls.add(text, ...(props.childs ?? []));
  });
}
import { motifOptions, read } from "@motifx/core";

export const Setting = (props) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("setting");
  },

  view() {
    return (
      <>
        <div class="setting__text">
          <span>{props.name}</span>
          <small>{() => read(props.hint)}</small>
        </div>
        {this.childs}
      </>
    );
  },
});
import { motifComponent, motifFragment, motifOptions, read } from "@motifx/core";

export const Setting = (props) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("setting");
  },

  view() {
    const text = motifComponent("div", (div) => {
      div.class.add("setting__text");
      const name = motifComponent("span", (span) => span.setText(props.name));
      const hint = motifComponent("small", (small) => small.bindings.text(() => read(props.hint)));
      div.controls.add(name, hint);
    });
    return motifFragment({ childs: [text, ...(this.childs ?? [])] });
  },
});

Props'a sınıfta this.props ile, fonksiyonda parametreyle, Options'ta fabrikanın parametresiyle ulaşılır. TypeScript'te props'un tipi sınıfta Component'in ikinci generic argümanıdır (Component<HTMLDivElement, SettingProps>), fonksiyonda ve Options'ta parametrenin tipidir. Bu tip this.props'u tipler ve etiketi de, new Setting(…) çağrısını da denetler: zorunlu bir prop eksikse ya da tipi uymuyorsa derleme hata verir.

Arşiv ayarı Setting'i şöyle kullanır. Açıklama arşivdeki not sayısını gösterir, denetim arşivi boşaltan düğmedir:

<Setting name="Arşiv" hint={() => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`}>
  <button type="button" class="button--sm danger" disabled={() => this.notes.count("archive") === 0} onclick={() => this.notes.emptyArchive()}>
    Arşivi boşalt
  </button>
</Setting>
<Setting name="Arşiv" hint={() => `${notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`}>
  <button type="button" class="button--sm danger" disabled={() => notes.count("archive") === 0} onclick={() => notes.emptyArchive()}>
    Arşivi boşalt
  </button>
</Setting>
const empty = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button", disabled: () => this.notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => this.notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
new Setting({
  name: "Arşiv",
  hint: () => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});
const empty = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button", disabled: () => this.notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => this.notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
motifComponent(Setting, {
  name: "Arşiv",
  hint: () => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});
const empty = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button", disabled: () => notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
motifComponent(Setting, {
  name: "Arşiv",
  hint: () => `${notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});
<Setting name="Arşiv" hint={() => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`}>
  <button type="button" class="button--sm danger" disabled={() => this.notes.count("archive") === 0} onclick={() => this.notes.emptyArchive()}>
    Arşivi boşalt
  </button>
</Setting>
<Setting name="Arşiv" hint={() => `${notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`}>
  <button type="button" class="button--sm danger" disabled={() => notes.count("archive") === 0} onclick={() => notes.emptyArchive()}>
    Arşivi boşalt
  </button>
</Setting>
const empty = motifComponent("button", (button) => {
  button.attr.add({ type: "button", disabled: () => this.notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => this.notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
new Setting({
  name: "Arşiv",
  hint: () => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});
const empty = motifComponent("button", (button) => {
  button.attr.add({ type: "button", disabled: () => this.notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => this.notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
motifComponent(Setting, {
  name: "Arşiv",
  hint: () => `${this.notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});
const empty = motifComponent("button", (button) => {
  button.attr.add({ type: "button", disabled: () => notes.count("archive") === 0 });
  button.class.add("button--sm danger");
  button.motif.on("click", () => notes.emptyArchive());
  button.setText("Arşivi boşalt");
});
motifComponent(Setting, {
  name: "Arşiv",
  hint: () => `${notes.count("archive")} arşivlenmiş not. Silinen notlar geri gelmez.`,
  childs: [empty],
});

Fonksiyon bileşeninde notes, sayfanın FromService(NotesService) ile aldığı servistir.

Düz değer ve canlı değer

Props nesnesi reaktif değildir; bileşen yaratılırken bir kez verilir. name="Arşiv" düz bir değerdir: bir kez okunur ve yazılır. Sonradan değişecek bir değer fonksiyon olarak verilir. hint={() => …} böyledir: arşivdeki not sayısı değiştikçe açıklama da değişir.

Alıcı taraf böyle bir prop'u Bind<T> ile tipler ve bir fonksiyonun içinde read(…) ile okur: () => read(props.hint). read değer fonksiyonsa çağırır, değilse olduğu gibi döndürür; bu yüzden aynı prop'a hint="Sabit metin" de verilebilir. Okuma bir fonksiyonun içinde yapıldığı için bağ, fonksiyonun okuduğu reaktif değerleri izler (bkz. Reaktivite).

Fonksiyon olarak verilmeyen bir ifade, yaratılma anındaki değeriyle kalır: hint={`${notes.count("archive")} arşivlenmiş not`} sayı değişince güncellenmez. Tek istisna koşul ifadesidir: bileşen etiketindeki a ? b : c ifadesini derleyici kendisi bir fonksiyona sarar, alıcı onu da read ile okur (bkz. Özniteliklerde ternary).

Geri çağrı prop'ları

Çocuktan ebeveyne veri, ebeveynin verdiği bir fonksiyonla gider: ebeveyn onChoose={(theme) => settings.setTheme(theme)} gibi bir prop verir, çocuk seçim yapılınca props.onChoose(value) çağırır. Bu fonksiyon da sıradan bir prop'tur ve çocuğun props'unda kalır. Adı on ile başlıyorsa, derleyicinin tanıdığı bir DOM olayının adı olmamalıdır (aşağıdaki nota bakın).

Var olan bir bileşeni genişleten alt sınıfta props'un tipi declare ile daraltılır: declare props: DialogProps;. Bu satır yalnız tiptir, kod üretmez. props: DialogProps; ya da props!: DialogProps; ise sınıfta yeni bir alan açar ve taban sınıfın kurduğu props değerini undefined ile ezer.

Not: Yaşam döngüsü prop'ları (onConfig, onBuilt, onMounted… ve x- biçimleri) ile initializeComponent kanca olarak kaydedilir ve props sözlüğünden ayıklanır. JSX'siz yazımda motifComponent(Bileşen, { … }) ile verilen nesnede de aynıdır; fonksiyon bileşeninde kancalar döndürülen köke uygulanır ve fonksiyonun aldığı props'ta yer almaz (bkz. Etikete yazılan direktifler ve kancalar). ref de bileşene uygulanır ve this.props'ta yer almaz. Bileşen etiketinde derleyicinin tanıdığı bir DOM olay adı (onclick, onChange, oninput:once) bileşenin elemanına dinleyici olarak bağlanır ve props'a hiç ulaşmaz; elemanı olmayan sınıfta bu dinleyici içeriğin üst düzey elemanlarını dinler. onmouseenter, onpointerdown, onfocusin, ontransitionend gibi adları derleyici tanımaz: dinleyici kurulmaz, değer uyarısız olarak props'ta kalır; bu olayları bileşen etiketinde on:mouseenter={…} yazımıyla dinleyin (bkz. on:ad yazımı). Bunların dışındaki on... prop'ları (onSave, onChoose, onValueChange) sıradan geri çağrı prop'udur, this.props içinde kalır; adı derleyicinin tanıdığı bir olayla çakışmamalıdır (onSearch → search olayı, MJX002 uyarısı).

Çocuk bileşenler (childs)

Bir bileşenin açılış ve kapanış etiketi arasına yazılan JSX içeriği o bileşene childs olarak ulaşır (ComponentBase[]). JSX'siz yazımda aynı dizi childs anahtarıyla verilir. Setting arşiv düğmesini böyle alır. Bileşen çocukları görünümün istediği yerine koyar: JSX'te {this.childs} ya da {props.childs} ile, JSX'siz yazımda controls.add(...) ile ya da elemansız bir parçanın childs dizisine ekleyerek.

childs derleyici için rezerve bir isimdir: {this.childs}, {props.childs} ve {childs} biçimlerinin hepsi metin değil içerik yerleştirme olarak derlenir (controls.add). Aynı şey elinizdeki bileşen dizisi için de geçerlidir: {[kartA, kartB]}.

Birkaç ayrıntı:

Sonraki adım

Bileşenin alt kontrollerini yönetmek için controls ve Bileşen Ağacı bölümüne geçin.