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… vex-biçimleri) ileinitializeComponentkanca olarak kaydedilir vepropssözlüğünden ayıklanır. JSX'siz yazımdamotifComponent(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).refde bileşene uygulanır vethis.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 veprops'a hiç ulaşmaz; elemanı olmayan sınıfta bu dinleyici içeriğin üst düzey elemanlarını dinler.onmouseenter,onpointerdown,onfocusin,ontransitionendgibi adları derleyici tanımaz: dinleyici kurulmaz, değer uyarısız olarakprops'ta kalır; bu olayları bileşen etiketindeon:mouseenter={…}yazımıyla dinleyin (bkz.on:adyazımı). Bunların dışındakion...prop'ları (onSave,onChoose,onValueChange) sıradan geri çağrı prop'udur,this.propsiçinde kalır; adı derleyicinin tanıdığı bir olayla çakışmamalıdır (onSearch→searcholayı,MJX002uyarı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ı:
- Yerleştirme statiktir:
childskurulum sırasında bir kez gelen düz bir dizidir (this.childs, fonksiyon bileşenindeprops.childs). Bu alana sonradan yeni dizi atamak, diziye eleman eklemek ya dasetState()çağırmak yuvadaki içeriği değiştirmez;{() => this.childs}biçiminde de böyledir: getter birFrameüretir ama yalnız içinde okunan reaktif bir değer değişince yeniden çalışır. Çocukların kendi bağları ise canlıdır:disabled={() => notes.count("archive") === 0}gibi bir bağ, yuva hangi biçimde olursa olsun sayı değişince güncellenir. - Kural tektir:
view()yazan sınıf çocuklarını kendisi yerleştirir,view()yazmayan sınıfta çocuklar içeriğin kendisidir. Yapıcıya eleman verilmeyen veview()yazmayan sınıfta (eleman bildirmeyen sınıf ya daComponent<HTMLDivElement>/static elementTagile bildirilen eleman)childsköke otomatik eklenir.view()yazan sınıfta otomatik ekleme yoktur: çocukları{this.childs}(ya da{() => this.childs}) ile ya dacontrols.add(...this.childs)ile nereye ve ne zaman isterseniz yerleştirirsiniz; çocuklar yalnız yerleştirildikleri yuvada kurulur. Yuvax-wait/x-displayarkasında ya da iç içe bir elemandaysa çocuklar da onunla birlikte bekler; çağrınınview()gövdesinde ya da yuvanın kurulum fonksiyonunda olması fark etmez.super("div", props)gibi somut etiket verilen sınıflarda ve etiket adıyla yaratılan bileşende (new Component("div", { childs })) yerleştirmeyi siz yaparsınız. Options nesnesi de aynı kurala uyar:view()yazmayan Options bileşenindechildselelemanına otomatik eklenir,view()yazan Options onları kendisi yerleştirir. - Hiç yerleştirilmeyen çocuklar (yuva hiç açılmadıysa ya da bileşen onları hiç yerleştirmediyse), onları alan bileşen serbest bırakılırken serbest bırakılır.
- Yalnızca birebir
childsözeldir;{childsCount}gibi isimler normal metin bağlaması olur.
Sonraki adım
Bileşenin alt kontrollerini yönetmek için controls ve Bileşen Ağacı bölümüne geçin.