Temel kavramlar
Bileşen Oluşturma
Bir bileşeni baştan sona oluşturmak: tanım, eleman, içerik, props ve kullanım; sınıf, fonksiyon ve Options biçimleriyle.
Bu sayfa not listesindeki satırı, NoteRow bileşenini baştan sona oluşturur. Kodlar sayfadaki kod tercihlerine göre gösterilir: yazım biçimi, bileşen stili ve dil. Note, Folder ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındandır.
Bileşeni yazmak
Bir bileşenin dört parçası vardır:
- Tanım:
Component'ten türeyen bir sınıf, bir bileşen döndüren fonksiyon ya damotifOptionsile yazılmış nesneyi döndüren bir fonksiyon. - Eleman: Satırın elemanı bir
<a>'dır. Sınıfta eleman sınıfa bildirilir: TypeScript'teComponent<HTMLAnchorElement>generic'iyle, JavaScript'testatic elementTag = "a"ile. Fonksiyonda eleman döndürülen köktür, Options'taelalanıdır. Eleman bildirilmezse bileşen elemansız olur (bkz. Kök eleman). - İçerik: Sınıfta ve Options'ta
view()elemanın içine girecek içeriği döndürür. Elemanın kendi sınıfı ve özniteliklerionConfigurediçindethis.classvethis.attrile verilir. Fonksiyon ise kökü içeriğiyle birlikte kendisi kurar. - Veri: Not ve klasör props ile gelir: sınıfta
this.props, fonksiyonda parametre, Options'ta fabrikanın parametresi.() => note.title …gibi bir fonksiyonun içinde yapılan okuma canlıdır; not değişince yalnız o metin ya da sınıf güncellenir.
Satır, açık notu router'ın parametresinden okur ve o satırı active sınıfıyla işaretler:
import { Component, useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export class NoteRow extends Component<HTMLAnchorElement, { note: Note; folder: Folder }> {
nav = useNavigation();
get open() {
return this.nav.params.id === String(this.props.note.id);
}
override onConfigured() {
const { note, folder } = this.props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
}
override view() {
const { note } = this.props;
return (
<>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</>
);
}
}
import { Component, ComponentBase, motifComponent, motifFragment, useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export class NoteRow extends Component<HTMLAnchorElement, { note: Note; folder: Folder }> {
static override elementTag = "a";
nav = useNavigation();
get open() {
return this.nav.params.id === String(this.props.note.id);
}
override onConfigured() {
const { note, folder } = this.props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
}
override view() {
const { note } = this.props;
const title = motifComponent("b", (b: ComponentBase) => {
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": "Sabitlendi" });
},
});
const label = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time: ComponentBase) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p: ComponentBase) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
return motifFragment({ childs: [title, when, preview] });
}
}
import { useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export function NoteRow(props: { note: Note; folder: Folder }) {
const nav = useNavigation();
const { note, folder } = props;
const open = () => nav.params.id === String(note.id);
return (
<a
rel="router"
href={`/${folder}/${note.id}`}
class={() => `row${open() ? " active" : ""}${note.title.trim() ? "" : " untitled"}`}
aria-current={() => (open() ? "page" : null)}
>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</a>
);
}
import { ComponentBase, motifComponent, useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export function NoteRow(props: { note: Note; folder: Folder }) {
const nav = useNavigation();
const { note, folder } = props;
const open = () => nav.params.id === String(note.id);
return motifComponent("a", (row: ComponentBase) => {
row.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (open() ? "page" : null) });
row.class.add(() => `row${open() ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
const title = motifComponent("b", (b: ComponentBase) => {
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": "Sabitlendi" });
},
});
const label = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time: ComponentBase) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p: ComponentBase) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
row.controls.add(title, when, preview);
});
}
import { motifOptions, useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export const NoteRow = (props: { note: Note; folder: Folder }) => motifOptions({
el: "a",
nav: useNavigation(),
get open() {
return this.nav.params.id === String(props.note.id);
},
onConfigured() {
const { note, folder } = props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
},
view() {
const { note } = props;
return (
<>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</>
);
},
});
import { ComponentBase, motifComponent, motifFragment, motifOptions, useNavigation } from "@motifx/core";
import type { Folder, Note } from "../services/notes";
export const NoteRow = (props: { note: Note; folder: Folder }) => motifOptions({
el: "a",
nav: useNavigation(),
get open() {
return this.nav.params.id === String(props.note.id);
},
onConfigured() {
const { note, folder } = props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
},
view() {
const { note } = props;
const title = motifComponent("b", (b: ComponentBase) => {
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": "Sabitlendi" });
},
});
const label = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time: ComponentBase) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p: ComponentBase) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
return motifFragment({ childs: [title, when, preview] });
},
});
import { Component, useNavigation } from "@motifx/core";
export class NoteRow extends Component {
static elementTag = "a";
nav = useNavigation();
get open() {
return this.nav.params.id === String(this.props.note.id);
}
onConfigured() {
const { note, folder } = this.props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
}
view() {
const { note } = this.props;
return (
<>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</>
);
}
}
import { Component, motifComponent, motifFragment, useNavigation } from "@motifx/core";
export class NoteRow extends Component {
static elementTag = "a";
nav = useNavigation();
get open() {
return this.nav.params.id === String(this.props.note.id);
}
onConfigured() {
const { note, folder } = this.props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
}
view() {
const { note } = this.props;
const title = motifComponent("b", (b) => {
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned && !note.archived),
initializeComponent: (i) => {
i.class.add("pin");
i.attr.add({ "aria-label": "Sabitlendi" });
},
});
const label = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
return motifFragment({ childs: [title, when, preview] });
}
}
import { useNavigation } from "@motifx/core";
export function NoteRow(props) {
const nav = useNavigation();
const { note, folder } = props;
const open = () => nav.params.id === String(note.id);
return (
<a
rel="router"
href={`/${folder}/${note.id}`}
class={() => `row${open() ? " active" : ""}${note.title.trim() ? "" : " untitled"}`}
aria-current={() => (open() ? "page" : null)}
>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</a>
);
}
import { motifComponent, useNavigation } from "@motifx/core";
export function NoteRow(props) {
const nav = useNavigation();
const { note, folder } = props;
const open = () => nav.params.id === String(note.id);
return motifComponent("a", (row) => {
row.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (open() ? "page" : null) });
row.class.add(() => `row${open() ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
const title = motifComponent("b", (b) => {
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned && !note.archived),
initializeComponent: (i) => {
i.class.add("pin");
i.attr.add({ "aria-label": "Sabitlendi" });
},
});
const label = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
row.controls.add(title, when, preview);
});
}
import { motifOptions, useNavigation } from "@motifx/core";
export const NoteRow = (props) => motifOptions({
el: "a",
nav: useNavigation(),
get open() {
return this.nav.params.id === String(props.note.id);
},
onConfigured() {
const { note, folder } = props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
},
view() {
const { note } = props;
return (
<>
<b>
<i class="pin" aria-label="Sabitlendi" x-display={() => note.pinned && !note.archived}></i>
<span>{() => note.title.trim() || "Adsız"}</span>
</b>
<time>{() => new Date(note.updatedAt).toLocaleDateString("tr-TR")}</time>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
</>
);
},
});
import { motifComponent, motifFragment, motifOptions, useNavigation } from "@motifx/core";
export const NoteRow = (props) => motifOptions({
el: "a",
nav: useNavigation(),
get open() {
return this.nav.params.id === String(props.note.id);
},
onConfigured() {
const { note, folder } = props;
this.attr.add({ rel: "router", href: `/${folder}/${note.id}`, "aria-current": () => (this.open ? "page" : null) });
this.class.add(() => `row${this.open ? " active" : ""}${note.title.trim() ? "" : " untitled"}`);
},
view() {
const { note } = props;
const title = motifComponent("b", (b) => {
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned && !note.archived),
initializeComponent: (i) => {
i.class.add("pin");
i.attr.add({ "aria-label": "Sabitlendi" });
},
});
const label = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
b.controls.add(pin, label);
});
const when = motifComponent("time", (time) => time.bindings.text(() => new Date(note.updatedAt).toLocaleDateString("tr-TR")));
const preview = motifComponent("p", (p) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
return motifFragment({ childs: [title, when, preview] });
},
});
Bileşeni kullanmak
Bileşen kullanıldığı yerde, props'uyla birlikte yaratılır. JSX'te etiketi yazmak yeter; derleyici etiketi motifComponent(NoteRow, { note, folder }) çağrısına çevirir. JSX'siz yazımda sınıf new ile, fonksiyon ve Options bileşeni motifComponent ile yaratılır; motifComponent sınıfı da kabul eder.
<NoteRow note={note} folder={this.folder} />
<NoteRow note={note} folder={folder()} />
new NoteRow({ note, folder: this.folder });
motifComponent(NoteRow, { note, folder: this.folder });
motifComponent(NoteRow, { note, folder: folder() });
<NoteRow note={note} folder={this.folder} />
<NoteRow note={note} folder={folder()} />
new NoteRow({ note, folder: this.folder });
motifComponent(NoteRow, { note, folder: this.folder });
motifComponent(NoteRow, { note, folder: folder() });
Fonksiyon bileşeninde folder() klasörü döndüren yerel fonksiyondur.
Yaratılan bileşen bir yere yerleşince görünür:
- JSX'te etiketin yazıldığı yere.
- Kodda bir ebeveynin
controls.add(...)çağrısıyla (bkz.controls). app.run("#app", bileşen)ile uygulamanın kökü olarak.- Rotanın
controlalanında: router sayfa bileşenini adres eşleşince kendisi yaratır (bkz. Rota Tablosu).
Yaratılma ve kurulma
Yaratmak ile kurmak ayrı adımlardır. Bileşenin elemanı yaratılırken hazırlanır: sınıfta yapıcıda, fonksiyon ve Options bileşeninde fonksiyon çağrıldığında. İçerik ise bileşen kurulurken (build()) bir kez gelir: view(), initializeComponent ve çocuklar o sırada çalışır. Kurulmuş bir ebeveyne eklenen bileşen hemen kurulur, henüz kurulmamış bir ebeveyne eklenen bileşen ebeveyniyle birlikte kurulur.
Bu yüzden alan başlatıcısında ve yapıcıda this.element vardır ama içi boştur. İçeriğe dokunan kod onBuilt içine, belgeye bağlı olmasını bekleyen kod (odak, ölçü) onMounted içine yazılır (bkz. Yaşam Döngüsü).
JSX'siz yazımda new Component ve motifComponent'in aldığı bütün argüman biçimleri, elemansız bileşenler ve props nesnesinin anahtarları JSX'siz Görünümler sayfasındadır.
Sonraki adım
Bileşene veri ve içerik vermek için Props ve Çocuklar bölümüne geçin.