Rehberler
Üçüncü Parti Kütüphane Sarmalama
Kendi DOM'unu yöneten kütüphaneleri bileşene dönüştürme kuralları; onBuilt kurulumu, setDisposable temizliği, untracked yazma, DOM sahipliği çakışması, kök element ve tek kopya kuralı.
Gerçek uygulamalarda her şey MotifJS ile yazılmaz. Grafik, zengin metin editörü, harita, sürükle-bırak gibi işler kendi DOM'unu yöneten olgun kütüphanelere bırakılır. Bu bölüm o kütüphaneleri MotifJS bileşenine dönüştürmenin kurallarını anlatır; örneklerde Chart.js ve SortableJS kullanılır.
Önce karar: sarmalamak gerekiyor mu?
Sarmalayın:
- Kütüphane kendi DOM'unu üretiyorsa (
canvasçiziyor, ağaç kuruyor, düğüm taşıyor). - İmperatif bir yaşam döngüsü varsa (
new X()/x.destroy()). - İşi MotifJS'in ince taneli bağlarıyla yapmak mantıksızsa (grafik çizimi, metin editörü).
Sarmalamayın:
- Saf hesaplama yapan kütüphaneler (tarih, para, doğrulama). Onları doğrudan çağırın.
- Başka bir arayüz çerçevesinin bileşen ağacına bağlı paketler. MotifJS'te çalışmazlar; çerçeveden bağımsız (headless) bir sürüm arayın.
- MotifJS'in zaten yaptığı işler (liste çizimi, koşullu gösterim, sanallaştırma).
Dört kural
Kurallar aynı örnek üzerinde anlatılır: klasörlerdeki not sayılarını Chart.js ile çizen bir grafik.
1. Kurulum onBuilt ya da initializeComponent içinde
Kütüphane örneği eleman oluşturulduğu anda kurulur: onBuilt ya da initializeComponent kancasında. Eleman o anda bileşende vardır; belgeye bağlı olması gerekmez. onMounted yalnız kütüphanenin elemanı belgede istediği durumlar içindir: ölçü alan, yerleşim okuyan ya da canlı DOM isteyen kütüphaneler. onBuilt element hâlâ kopuk bir parça içindeyken tetiklenebilir (üçlü operatör ve liste dalları detached fragment'ta kurulur); bu kütüphaneler orada yanlış sonuç üretir. onMounted element document'a bağlandıktan sonraki görevde bir kez çalışır; birden çok sarmalayıcının kurulumu dilimlere bölünür ve sayfayı kilitlemez.
import { Component, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService, type Folder } from "../services/notes";
const folders = Object.keys(FOLDERS) as Folder[];
export class FolderChart extends Component<HTMLCanvasElement> {
static override elementTag = "canvas";
notes = this.getService(NotesService)!;
override onBuilt() {
const chart = new Chart(this.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] as number[] }] },
});
this.motif.setDisposable(() => chart.destroy());
this.bindings.watch(() => {
const counts = folders.map((folder) => this.notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
}
}
import { FromService, untracked, type Component } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService, type Folder } from "../services/notes";
const folders = Object.keys(FOLDERS) as Folder[];
export function FolderChart() {
const notes = FromService(NotesService)!;
const setup = (canvas: Component<HTMLCanvasElement>) => {
const chart = new Chart(canvas.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] as number[] }] },
});
canvas.motif.setDisposable(() => chart.destroy());
canvas.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
};
return <canvas onBuilt={setup} />;
}
import { FromService, motifComponent, untracked, type Component } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService, type Folder } from "../services/notes";
const folders = Object.keys(FOLDERS) as Folder[];
export function FolderChart() {
const notes = FromService(NotesService)!;
return motifComponent("canvas", {
onBuilt: (canvas: Component<HTMLCanvasElement>) => {
const chart = new Chart(canvas.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] as number[] }] },
});
canvas.motif.setDisposable(() => chart.destroy());
canvas.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
},
});
}
import { motifOptions, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService, type Folder } from "../services/notes";
const folders = Object.keys(FOLDERS) as Folder[];
export const FolderChart = () => motifOptions({
el: "canvas",
onBuilt() {
const notes = this.getService(NotesService)!;
const chart = new Chart(this.element as HTMLCanvasElement, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] as number[] }] },
});
this.motif.setDisposable(() => chart.destroy());
this.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
},
});
import { Component, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService } from "../services/notes";
const folders = Object.keys(FOLDERS);
export class FolderChart extends Component {
static elementTag = "canvas";
notes = this.getService(NotesService);
onBuilt() {
const chart = new Chart(this.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] }] },
});
this.motif.setDisposable(() => chart.destroy());
this.bindings.watch(() => {
const counts = folders.map((folder) => this.notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
}
}
import { FromService, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService } from "../services/notes";
const folders = Object.keys(FOLDERS);
export function FolderChart() {
const notes = FromService(NotesService);
const setup = (canvas) => {
const chart = new Chart(canvas.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] }] },
});
canvas.motif.setDisposable(() => chart.destroy());
canvas.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
};
return <canvas onBuilt={setup} />;
}
import { FromService, motifComponent, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService } from "../services/notes";
const folders = Object.keys(FOLDERS);
export function FolderChart() {
const notes = FromService(NotesService);
return motifComponent("canvas", {
onBuilt: (canvas) => {
const chart = new Chart(canvas.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] }] },
});
canvas.motif.setDisposable(() => chart.destroy());
canvas.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
},
});
}
import { motifOptions, untracked } from "@motifx/core";
import { Chart } from "chart.js/auto";
import { FOLDERS, NotesService } from "../services/notes";
const folders = Object.keys(FOLDERS);
export const FolderChart = () => motifOptions({
el: "canvas",
onBuilt() {
const notes = this.getService(NotesService);
const chart = new Chart(this.element, {
type: "bar",
data: { labels: folders.map((folder) => FOLDERS[folder].title), datasets: [{ label: "Not", data: [] }] },
});
this.motif.setDisposable(() => chart.destroy());
this.bindings.watch(() => {
const counts = folders.map((folder) => notes.count(folder));
untracked(() => {
chart.data.datasets[0].data = counts;
chart.update();
});
});
},
});
Grafik <FolderChart /> ile yerleşir.
Grafik new FolderChart() ile yerleşir.
Grafik motifComponent(FolderChart) ile yerleşir.
2. Temizlik motif.setDisposable ile
Kütüphanenin destroy() / disconnect() çağrısı bileşenin ömrüne bağlanır; örnekte chart.destroy() böyle bağlanmıştır. Bileşen serbest bırakılınca kütüphane de gider, ayrıca bir şey çağrılmaz. Zamanlayıcı, ResizeObserver, WebSocket gibi yan kaynaklar da aynı yere kaydedilir (bkz. Bellek Yönetimi ve Serbest Bırakma).
3. Okuma izlenir, yazma untracked içinde olur
Reaktif veriyi kütüphaneye aktarırken iki tuzak var:
- Veriyi doğrudan kütüphaneye verirseniz, kütüphane kendi güncellemesi sırasında o nesneyi okur. Bu okuma izlenen bir bölgedeyse effect kendi kendini tetikler ve döngü oluşur.
- Okumayı tümüyle
untrackediçine alırsanız bu kez iç alanlar hiç izlenmez ve veri değişince güncelleme olmaz.
Doğru biçim örnekteki gibidir: bindings.watch içinde, izlenen bölgede düz bir kopya çıkarılır (counts), kütüphaneye yazma untracked içinde yapılır. Kopya derin değişiklikleri de yakalar: bir not sabitlenince count("pinned") notların pinned alanını okuduğu için effect yeniden çalışır ve grafik güncellenir.
4. Kurulum hatası raporlanır
onBuilt içinden fırlatılan istisna (ya da async onBuilt'ın reddedilen promise'i) yutulmaz: MotifJS onu MJX122 (The component onBuilt hook threw.) koduyla bir MotifError olarak errorHandler'a raporlar, asıl hata cause alanındadır. Rapor üretim kipinde de konsola yazılır (app.useLogging(false) kapatır) ve errorHandler.addListener(fn) dinleyicilerine ulaşır. Aynı rapor onMounted dahil diğer yaşam döngüsü kancaları için de üretilir. Sarmalayıcının ayrıca console.error yazması gerekmez.
Kurulum hatasında bileşeni tutarlı bir durumda bırakmak (yarım kalan örneği yıkmak, yedek görünüm vermek) sarmalayıcının işidir; bunun için hata yakalanıp işlendikten sonra yeniden fırlatılır, rapor yine oluşur. config grafiğin ayar nesnesidir:
let chart: Chart;
try {
chart = new Chart(this.element as HTMLCanvasElement, config);
} catch (error) {
this.class.add("chart--failed");
throw error;
}
this.motif.setDisposable(() => chart.destroy());
let chart: Chart;
try {
chart = new Chart(canvas.element, config);
} catch (error) {
canvas.class.add("chart--failed");
throw error;
}
canvas.motif.setDisposable(() => chart.destroy());
let chart;
try {
chart = new Chart(this.element, config);
} catch (error) {
this.class.add("chart--failed");
throw error;
}
this.motif.setDisposable(() => chart.destroy());
let chart;
try {
chart = new Chart(canvas.element, config);
} catch (error) {
canvas.class.add("chart--failed");
throw error;
}
canvas.motif.setDisposable(() => chart.destroy());
DOM sahipliği çakışması
En zor durum, kütüphanenin MotifJS'in çizdiği düğümleri taşıdığı hâldir. Sürükle-bırak kütüphaneleri tam olarak bunu yapar: bırakma anında DOM'u kendisi yeniden sıralar. Oysa o düğümlerin sahibi liste bağıdır; iki sahip çakışırsa bağ bir daha doğru hesap yapamaz.
Çözüm şablonu:
- Kütüphanenin DOM değişikliğini geri alın.
- Aynı değişikliği modele uygulayın.
- DOM'u yeniden MotifJS çizsin.
Effect kuyruğu mikro-görevde boşaldığı için geri alma ile yeniden çizim aynı karede tamamlanır; ekranda titreme olmaz.
SortableJS ile tek listelik, en sade hâli:
import { Component, read, type Bind } from "@motifx/core";
import Sortable from "sortablejs";
export class SortableList<T> extends Component<HTMLDivElement, { list: Bind<T[]> }> {
static override elementTag = "div";
override onBuilt() {
const sortable = Sortable.create(this.element, { animation: 150, onEnd: (evt) => this.dropped(evt) });
this.motif.setDisposable(() => sortable.destroy());
}
private dropped(evt: Sortable.SortableEvent) {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(this.props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
}
}
import { read, type Bind, type Component, type ComponentBase } from "@motifx/core";
import Sortable from "sortablejs";
export function SortableList<T>(props: { list: Bind<T[]>; childs?: ComponentBase[] }) {
const dropped = (evt: Sortable.SortableEvent) => {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
};
const setup = (box: Component<HTMLDivElement>) => {
const sortable = Sortable.create(box.element, { animation: 150, onEnd: dropped });
box.motif.setDisposable(() => sortable.destroy());
};
return <div onBuilt={setup}>{props.childs}</div>;
}
import { motifComponent, read, type Bind, type Component, type ComponentBase } from "@motifx/core";
import Sortable from "sortablejs";
export function SortableList<T>(props: { list: Bind<T[]>; childs?: ComponentBase[] }) {
const dropped = (evt: Sortable.SortableEvent) => {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
};
return motifComponent("div", {
initializeComponent: (box: ComponentBase) => box.controls.add(...(props.childs ?? [])),
onBuilt: (box: Component<HTMLDivElement>) => {
const sortable = Sortable.create(box.element, { animation: 150, onEnd: dropped });
box.motif.setDisposable(() => sortable.destroy());
},
});
}
import { motifOptions, read, type Bind } from "@motifx/core";
import Sortable from "sortablejs";
export const SortableList = <T>(props: { list: Bind<T[]> }) => motifOptions({
el: "div",
onBuilt() {
const sortable = Sortable.create(this.element as HTMLElement, { animation: 150, onEnd: (evt) => this.dropped(evt) });
this.motif.setDisposable(() => sortable.destroy());
},
dropped(evt: Sortable.SortableEvent) {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
},
});
import { Component, read } from "@motifx/core";
import Sortable from "sortablejs";
export class SortableList extends Component {
static elementTag = "div";
onBuilt() {
const sortable = Sortable.create(this.element, { animation: 150, onEnd: (evt) => this.dropped(evt) });
this.motif.setDisposable(() => sortable.destroy());
}
dropped(evt) {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(this.props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
}
}
import { read } from "@motifx/core";
import Sortable from "sortablejs";
export function SortableList(props) {
const dropped = (evt) => {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
};
const setup = (box) => {
const sortable = Sortable.create(box.element, { animation: 150, onEnd: dropped });
box.motif.setDisposable(() => sortable.destroy());
};
return <div onBuilt={setup}>{props.childs}</div>;
}
import { motifComponent, read } from "@motifx/core";
import Sortable from "sortablejs";
export function SortableList(props) {
const dropped = (evt) => {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
};
return motifComponent("div", {
initializeComponent: (box) => box.controls.add(...(props.childs ?? [])),
onBuilt: (box) => {
const sortable = Sortable.create(box.element, { animation: 150, onEnd: dropped });
box.motif.setDisposable(() => sortable.destroy());
},
});
}
import { motifOptions, read } from "@motifx/core";
import Sortable from "sortablejs";
export const SortableList = (props) => motifOptions({
el: "div",
onBuilt() {
const sortable = Sortable.create(this.element, { animation: 150, onEnd: (evt) => this.dropped(evt) });
this.motif.setDisposable(() => sortable.destroy());
},
dropped(evt) {
const { item, from, oldIndex, newIndex } = evt;
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
// 1. SortableJS'in taşımasını geri al
item.remove();
const last = from.lastElementChild;
const ref = from.children[oldIndex] ?? (last ? last.nextSibling : from.lastChild);
from.insertBefore(item, ref);
// 2. Aynı taşımayı modele uygula; 3. DOM'u liste bağı yeniden sıralar
const list = read(props.list);
const [moved] = list.splice(oldIndex, 1);
list.splice(newIndex, 0, moved);
},
});
Kullanımda list'e, satırları çizilen reaktif dizinin kendisi verilir ve her satır key taşır. Örnekte sıralanan dizi, klasörlerin kenar çubuğundaki sırasını tutan yerel bir reaktif nesnededir: const order = reactive({ folders: ["notes", "pinned", "archive"] as Folder[] }).
<SortableList list={order.folders}>
{order.folders.map((folder) => <div key={folder} class="row">{FOLDERS[folder].title}</div>)}
</SortableList>
const rows = motifComponent({
initializeComponent: (holder: ComponentBase) =>
holder.bindings.list(() => order.folders, (folder: Folder) => motifComponent("div", {
indexkey: () => folder,
initializeComponent: (row: ComponentBase) => {
row.class.add("row");
row.setText(FOLDERS[folder].title);
},
})),
});
const folderList = new SortableList({ list: order.folders, childs: [rows] });
const rows = motifComponent({
initializeComponent: (holder: ComponentBase) =>
holder.bindings.list(() => order.folders, (folder: Folder) => motifComponent("div", {
indexkey: () => folder,
initializeComponent: (row: ComponentBase) => {
row.class.add("row");
row.setText(FOLDERS[folder].title);
},
})),
});
const folderList = motifComponent(SortableList, { list: order.folders, childs: [rows] });
<SortableList list={order.folders}>
{order.folders.map((folder) => <div key={folder} class="row">{FOLDERS[folder].title}</div>)}
</SortableList>
const rows = motifComponent({
initializeComponent: (holder) =>
holder.bindings.list(() => order.folders, (folder) => motifComponent("div", {
indexkey: () => folder,
initializeComponent: (row) => {
row.class.add("row");
row.setText(FOLDERS[folder].title);
},
})),
});
const folderList = new SortableList({ list: order.folders, childs: [rows] });
const rows = motifComponent({
initializeComponent: (holder) =>
holder.bindings.list(() => order.folders, (folder) => motifComponent("div", {
indexkey: () => folder,
initializeComponent: (row) => {
row.class.add("row");
row.setText(FOLDERS[folder].title);
},
})),
});
const folderList = motifComponent(SortableList, { list: order.folders, childs: [rows] });
Geri almadaki ref hesabı bilinçli olarak from.children[oldIndex] ?? null değildir. MotifJS listeyi bir parça içinde çizer; kapsayıcının son çocuğu genellikle listenin kapanış işaretçisi olan bir yorum düğümüdür. Öğe sondan sürüklendiyse insertBefore(item, null) onu o işaretçinin dışına, yani listenin dışına koyar ve liste bağı bir sonraki güncellemede öğeyi bulamaz. Son elementin nextSibling'i ise işaretçinin önünü verir; öğe listenin içinde kalır.
Bu sade hâl tek liste içindir. Kütüphane kendi kapsayıcısının içini tamamen yönetiyorsa (grafik, editör) çakışma yoktur: bileşen yalnızca boş bir kapsayıcı verir, içine MotifJS hiçbir şey çizmez.
Kök element
Kütüphanelerin çoğu gerçek bir element ister (canvas, div).
Sınıfta kök element view() ile değil, sınıfın kendisiyle belirlenir. TypeScript'te Component<HTMLCanvasElement> yazıldığında @motifx/compiler element türünü generic'ten çıkarır ve kök <canvas> olarak kendiliğinden oluşur. JavaScript'te ve derleyiciden geçmeyen dosyada static elementTag = "canvas" yazılır. Kurucudaki super çağrısının ilk parametresine etiket adı ya da hazır bir element de verilebilir: super("canvas", props), super(document.createElement("canvas"), props); bu biçim generic'e bağlı olmadığı için @motifx/compiler ile derlenmeyen bir pakette de çalışır.
Fonksiyon bileşeninde kök, döndürülen elemandır: JSX'te <canvas … />, JSX'siz yazımda motifComponent("canvas", …).
Options'ta kök el alanıdır: el: "canvas".
Kök element bildirilmezse kök bir fragment (yorum düğümü) olur ve içerik bu fragmentin yerine yerleşir. Fragment gerçek bir element olmadığı için bir kütüphaneye kapsayıcı olarak verilemez; sarmalayıcıda kök elementi mutlaka bildirin.
view() yazılmayan bir bileşende çocuklar kök elemanın içine eklenir: sınıfta kök generic ya da static elementTag ile bildirildiyse, Options'ta el ile. SortableList satırlarını bu yüzden kendi elemanının içinde taşır. Kökü super("div", props) ile kuran sınıfta çocukları siz yerleştirirsiniz.
Prop adlarında dikkat
options prop'u çerçevenindir: bileşenin kendi ayar nesnesidir, yapıcıda okunur ve tanınan anahtarlar motif.options'a kopyalanır: hideStrategy, disableDisposal (bkz. Gizleme stratejisi) ve kök elementi SVG ad alanında oluşturan isSvg. Kütüphane seçenekleri bu prop'la geçilmez; chartOptions, sortableOptions gibi açık bir ad seçilir:
<FolderChart chartOptions={{ responsive: true, animation: false }} />
const chart = new FolderChart({ chartOptions: { responsive: true, animation: false } });
const chart = motifComponent(FolderChart, { chartOptions: { responsive: true, animation: false } });
<FolderChart chartOptions={{ responsive: true, animation: false }} />
const chart = new FolderChart({ chartOptions: { responsive: true, animation: false } });
const chart = motifComponent(FolderChart, { chartOptions: { responsive: true, animation: false } });
Bileşen bu değeri kurulumda prop'larından okuyup Chart.js'e verir: new Chart(canvas, { type: "bar", data, options: chartOptions }).
on ile başlayan prop'lar prop'larda kalır ve geri çağrı olarak kullanılabilir; DOM olay adı taşıyanlar bunun dışındadır. onChange, onCopy, onResize, onSelect, onReset, onContextMenu gibi bir prop bileşen etiketinde kök elemana DOM dinleyicisi olarak bağlanır ve prop'lara hiç ulaşmaz; derleyici camelCase yazımda MJX002 uyarısı verir. Alan adını belirginleştirin (onChartUpdate, onCodeCopy).
Tek kopya kuralı
Sarmalayıcı ayrı bir paketse, sarmalanan kütüphane ve @motifx/core uygulamada tek kopya olmalıdır. İki kopya olursa:
- Kütüphanenin genel kayıt defteri (örneğin
Chart.registry) ikiye ayrılır; sizin kaydınız paketin gördüğü kopyaya işlemez. - İki ayrı reaktivite motoru oluşur;
untrackedkarşı taraftaki effect'i tanımaz.
Her iki hata da sessizdir. Vite'ta çözüm tek satır:
resolve: { dedupe: ['@motifx/core', 'chart.js', 'sortablejs'] }
resolve: { dedupe: ['@motifx/core', 'chart.js', 'sortablejs'] }
Temizliği kanıtlayın
Sarmalayıcının doğruluğu "çalışıyor görünüyor" ile ölçülmez. Bileşeni defalarca kurup söken bir tur testi yazın: tur sonunda canlı bileşen sayısı ve canlı dış örnek sayısı başladığı değere dönmelidir. Ölçüm kancaları için Bellek Yönetimi ve Serbest Bırakma bölümündeki disposableTracker bölümüne bakın.
Sonraki adım
Sarmalayıcıyı ya da bir bileşen setini npm'de yayımlamak için MotifJS için npm Kütüphanesi Yayımlama bölümüne geçin.