Gelişmiş
Sanal Listeler (Virtualization)
Virtualization bileşeni; prop'lar, dataRequest sözleşmesi, otomatik yenileme, şablonlar, filtreleme, değişken yükseklik, çizim modeli ve yöntemler.
Binlerce öğelik listelerde tüm öğeleri DOM'a koymak performansı düşürür. MotifJS'in Virtualization bileşeni yalnızca görünür öğeleri (ve iki yanlarında az sayıda tampon satırı) DOM'da tutarak satır yüksekliği sabit ya da önceden hesaplanabilen listeleri verimli gösterir. Veriyi sayfa sayfa kendisi ister.
Temel kullanım
Örnekte klasörün not listesi sanal listeyle çizilir. Veri servisteki notlardan sayfa sayfa dilimlenir; her satır not satırı bileşenidir:
import { Component, Virtualization } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export class NoteList extends Component<any, { folder: Folder; state: { query: string } }> {
override view() {
const notes = this.getService(NotesService)!;
const { folder, state } = this.props;
return (
<Virtualization<Note>
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
}
}
import { Component, ComponentBase, Virtualization, motifComponent } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export class NoteList extends Component<any, { folder: Folder; state: { query: string } }> {
override view() {
const notes = this.getService(NotesService)!;
const { folder, state } = this.props;
return new Virtualization<Note>({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => new NoteRow({ note, folder }),
emptyTemplate: () => motifComponent("p", (p: ComponentBase) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
}
}
import { FromService, Virtualization } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export function NoteList(props: { folder: Folder; state: { query: string } }) {
const notes = FromService(NotesService)!;
const { folder, state } = props;
return (
<Virtualization<Note>
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
}
import { ComponentBase, FromService, Virtualization, motifComponent } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export function NoteList(props: { folder: Folder; state: { query: string } }) {
const notes = FromService(NotesService)!;
const { folder, state } = props;
return new Virtualization<Note>({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
emptyTemplate: () => motifComponent("p", (p: ComponentBase) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
}
import { Virtualization, motifOptions } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export const NoteList = (props: { folder: Folder; state: { query: string } }) => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService)!;
const { folder, state } = props;
return (
<Virtualization<Note>
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
},
});
import { ComponentBase, Virtualization, motifComponent, motifOptions } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService, type Folder, type Note } from "../services/notes";
export const NoteList = (props: { folder: Folder; state: { query: string } }) => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService)!;
const { folder, state } = props;
return new Virtualization<Note>({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
emptyTemplate: () => motifComponent("p", (p: ComponentBase) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
},
});
import { Component, Virtualization } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export class NoteList extends Component {
view() {
const notes = this.getService(NotesService);
const { folder, state } = this.props;
return (
<Virtualization
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
}
}
import { Component, Virtualization, motifComponent } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export class NoteList extends Component {
view() {
const notes = this.getService(NotesService);
const { folder, state } = this.props;
return new Virtualization({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => new NoteRow({ note, folder }),
emptyTemplate: () => motifComponent("p", (p) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
}
}
import { FromService, Virtualization } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export function NoteList(props) {
const notes = FromService(NotesService);
const { folder, state } = props;
return (
<Virtualization
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
}
import { FromService, Virtualization, motifComponent } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export function NoteList(props) {
const notes = FromService(NotesService);
const { folder, state } = props;
return new Virtualization({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
emptyTemplate: () => motifComponent("p", (p) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
}
import { Virtualization, motifOptions } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export const NoteList = (props) => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService);
const { folder, state } = props;
return (
<Virtualization
itemHeight={64}
pageSize={50}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
emptyTemplate={() => <p class="list__empty">{FOLDERS[folder].empty}</p>}
/>
);
},
});
import { Virtualization, motifComponent, motifOptions } from "@motifx/core";
import { NoteRow } from "./NoteRow";
import { FOLDERS, NotesService } from "../services/notes";
export const NoteList = (props) => motifOptions({
el: null,
view() {
const notes = this.getService(NotesService);
const { folder, state } = props;
return new Virtualization({
itemHeight: 64,
pageSize: 50,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return {
items: found.slice(start, start + pageSize),
totalCount: found.length,
hasMore: start + pageSize < found.length,
};
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
emptyTemplate: () => motifComponent("p", (p) => {
p.class.add("list__empty");
p.setText(FOLDERS[folder].empty);
}),
});
},
});
Klasör düzeni listeyi arama durumuyla birlikte <NoteList folder={this.folder} state={this.state} /> ile yerleştirir.
Klasör düzeni listeyi arama durumuyla birlikte new NoteList({ folder: this.folder, state: this.state }) ile yerleştirir.
Klasör düzeni listeyi arama durumuyla birlikte motifComponent(NoteList, { folder: this.folder, state: this.state }) ile yerleştirir.
mainTemplate verilmediğinde liste kök div'ini overflow-y: auto; height: 100%; position: relative; overflow-anchor: none ile kurar ve kendi içinde kayar; bu yüzden onu taşıyan kabın bir yüksekliği olmalıdır ya da yükseklik style ile listeye verilir. dataRequest arama metnini ve notları okuduğu için arama kutusuna yazıldıkça liste kendiliğinden yenilenir (bkz. Otomatik yenileme).
Prop'lar (VirtualizationProps<T>)
| Prop | Tip | Açıklama |
|---|---|---|
dataRequest |
(req) => Promise<VirtualizationDataResponse<T>> |
Zorunlu. Sayfa verisini getiren fonksiyon; bkz. dataRequest sözleşmesi. |
itemTemplate |
(item: T, index: number) => any |
Zorunlu. Her öğenin satırını kurar. Bileşen döndürmezse dönen değer bir bileşene sarılır. Öğe veride nasıl verildiyse öyle gelir (bkz. Veri verildiği gibi kullanılır). |
itemHeight |
number \| (item: T, index: number) => number |
Zorunlu. Her öğenin piksel yüksekliği. Sayı: tüm satırlar aynı; fonksiyon: satır başına bilinen yükseklik (bkz. Değişken yükseklik). |
pageSize |
number |
Her istekte kaç öğe istenecek (varsayılan 50). |
mainTemplate |
(content, state) => any |
Listeyi saran yerleşim; bkz. Özel kapsayıcı. |
loadingTemplate |
(state) => any |
İlk yükleme sürerken gösterilen görünüm. |
emptyTemplate |
() => any |
Gösterilecek öğe kalmadığında (veri boş ya da filtre hepsini eledi) gösterilen görünüm. |
errorTemplate |
(error) => any |
İlk yükleme, refresh() ya da otomatik yenileme isteği hata verince satırların yerine gösterilen görünüm; verilmezse hata MJX207 olarak raporlanır. Sonraki sayfa isteğinin hatası her durumda MJX207 olarak raporlanır ve getState().error'a yazılır; yüklenmiş satırlar ekranda kalır, hata görünümü gösterilmez, bir sonraki kaydırmada istek yeniden yapılır ve başarılı yüklemede error temizlenir. |
filter |
(items: T[]) => T[] |
Yüklü veriye uygulanır; boşluklar ve indeksler filtrelenmiş uzunluğa göre hesaplanır (bkz. Filtreleme). |
overscan |
number |
Görünür alanın üstünde ve altında ek çizilen satır sayısı (varsayılan: görünür satır sayısı). |
cacheSize |
number |
Önbellekte tutulan satır bileşeni üst sınırı, çizili satırlar dahil (varsayılan max(200, 3 × çizili satır sayısı)). Aşılınca ekran dışındaki satırlardan en uzun süredir görünmeyenler serbest bırakılır. |
autoRefresh |
boolean |
dataRequest içinde okunan reaktif veri değişince yüklü aralığı tek istekte yeniden alır (varsayılan açık); bkz. Otomatik yenileme. false verilirse liste yalnız refresh() ile yenilenir. |
watch |
() => unknown |
Otomatik yenileme için ek bağımlılık: burada okunan reaktif alanlar da yeniden yüklemeyi tetikler. |
autoLoad |
boolean |
Dibe yaklaşınca sonraki sayfayı ister (varsayılan açık). Çizilen pencere yüklü verinin sonuna overscan kadar yaklaştığında da (ör. ilk sayfa görünür alanı doldurmuyorsa) sonraki sayfa istenir. |
autoLoadThreshold |
number |
Yüklü verinin alt sınırına kaç piksel kala sonraki sayfa istensin (varsayılan 100). Sabit yükseklikte alt sınır data.length × itemHeight'tır. |
className |
string |
Kök div'e eklenen sınıf. |
style |
Partial<CSSStyleDeclaration> |
Kök div'e satır içi stil. Listenin kendi overflow-y, height ve position değerlerinden sonra uygulanır; aynı özelliği verirseniz sizinki geçerli olur. |
overscanPages, renderMode, pageBuffer |
Ayrılmış; okunmazlar. |
dataRequest sözleşmesi
dataRequest bir istek nesnesi (VirtualizationDataRequest) alır:
| Alan | Tip | Anlamı |
|---|---|---|
page |
number |
0 tabanlı sayfa sırası. |
pageSize |
number |
İstenen öğe sayısı. Otomatik yenilemede yüklü aralık tek istekte istendiği için page 0, pageSize ise pageSize × yüklü sayfa sayısı olur. |
scrollTop |
number |
İstek anındaki kaydırma konumu (ilk yüklemede 0). |
ve bir yanıt nesnesi (VirtualizationDataResponse<T>) döndüren Promise verir:
| Alan | Tip | Anlamı |
|---|---|---|
items |
T[] |
Bu sayfadaki öğeler. |
totalCount |
number |
Tüm veri setinin öğe sayısı. state.totalCount'a yazılır (ör. başlıkta toplamı göstermek için) ve hasMore sürdükçe kaydırma alanının boyunu belirler. |
hasMore |
boolean |
Sonraki sayfa var mı. false döndüğünde otomatik yükleme durur. |
Kaydırma alanı totalCount'un tamamını kapsar: yüklenmemiş öğeler alt boşlukta yer tutar, sabit itemHeight'ta tam yükseklikleriyle, fonksiyonla verilen yükseklikte yüklü satırların ortalamasıyla. Yüklenmemiş bölgeye kaydırılınca sayfalar oraya ulaşana kadar sırayla istenir. hasMore false olunca ya da totalCount yüklü öğelerden azsa alan yalnız yüklü veriden hesaplanır. filter yüklü öğelere uygulanır; yüklenmemiş kısım süzülmeden sayılır, süzgecin attığı öğeler yüklendikçe alan kısalır.
Otomatik yenileme — autoRefresh
autoRefresh (varsayılan açık) ilk yüklemede ve refresh()'te dataRequest'i bir effect içinde çağırır. Bu çağrıda okunan reaktif alanlardan biri değişince yüklü aralık (sayfa 0'dan geçerli sayfaya) tek istekte yeniden alınır; yanıtta yine bulunan öğelerin satırları korunur, bulunmayanlarınki serbest bırakılır (bkz. Çizim modeli). Sonraki sayfa istekleri izlenmez; setData() izlemeyi durdurur. Örnekte notes.list(folder, state.query) not dizisini, arama metnini ve notların arşiv, sabitleme ve tarih alanlarını (arama doluyken başlık ve metni de) okur; bunlardan biri değişince liste yenilenir.
Etikete yazılan async dataRequest fonksiyonundaki await'leri derleyici sarar: await sonrasında okunan reaktif alanlar da bağımlılık olur (başka yerlerde await sonrası okumalar izlenmez; bkz. await sonrası okumalar). dataRequest etiketin dışında tanımlandıysa ya da bağımlılık başka bir yerden geliyorsa watch={() => state.query} ile ek bağımlılık verilir.
Derleyici await sarmasını yalnız <Virtualization> etiketine yazılan dataRequest'e uygular; JSX'siz yazımda yalnız ilk await'ten önce okunan reaktif alanlar bağımlılık olur (bkz. await sonrası okumalar). await sonrasında okunan ya da başka bir yerden gelen bağımlılık watch: () => state.query ile verilir.
watch izleme kurulurken dataRequest ile aynı effect içinde bir kez çağrılır; okuduğu alanlar bağımlılığa eklenir, fırlattığı hata MJX205 olarak raporlanır. Yeniden yükleme değişimden sonraki mikro görevde yapılır; içerik yanıt gelene kadar yerinde kalır. Bir saniyede 30'dan fazla yeniden yükleme tetiklenirse (döngü koruması) izleme durdurulur ve geliştirme modunda MJX206 uyarısı yazılır; refresh() izlemeyi yeniden kurar.
Şablonlar ve liste durumu
mainTemplate ve loadingTemplate listenin durumunu (VirtualizationState<T>) alır. Durum alanları reaktiftir; şablonda getter ile okunan alan güncel kalır. data alanı yüklenen ya da setData ile verilen dizinin değiştiğini bildirir, öğeleri ise sarmaz (bkz. Veri verildiği gibi kullanılır).
| Alan | Tip | Anlamı |
|---|---|---|
isLoading |
boolean |
İlk yükleme ya da refresh() isteği sürüyor. |
isInitialized |
boolean |
İlk yanıt geldi ya da setData() çağrıldı. |
currentPage |
number |
Son yüklenen sayfa. |
totalCount |
number |
Son yanıttaki totalCount. |
hasMore |
boolean |
Son yanıttaki hasMore. |
error |
Error \| undefined |
Son isteğin hatası; başarılı yüklemede temizlenir. |
data |
T[] |
Yüklü öğeler (filtre uygulanmamış), verildikleri gibi. |
Veri verildiği gibi kullanılır
Liste veriyi reaktif nesneye çevirmez. dataRequest'in döndürdüğü ya da setData'ya verilen öğeler itemTemplate, itemHeight ve filter'a olduğu gibi gelir; setData'ya verilen dizinin kendisi state.data ve getState().data olarak okunur. Reaktif veri reaktif, düz veri düz kalır.
Düz nesneyle kurulan satır, nesneye sonradan yazılan değeri kendiliğinden göstermez. Satırın değişikliği izlemesi gerekiyorsa veri reactive ile verilir; düz veride değişiklik, satırın bağları setState() ile yeniden çalıştırılarak ekrana yansıtılır (bkz. Reaktivite ile DOM'un buluşması). Yeni bir dizi gelmişse setData çağrılır.
Sonraki sayfalar ilk sayfanın dizisine eklenmez: liste yüklü öğeleri kendi dizisinde toplar, dataRequest'in döndürdüğü diziler değişmez.
Özel kapsayıcı — mainTemplate
mainTemplate listeyi saran yerleşimi kurar. İlk parametresi (content) satırları taşıyan bileşendir; yerleşimde nereye konursa satırlar oraya çizilir. Onu saran en yakın eleman kaydırma kabı olur: liste ona overflow-y: auto; position: relative; overflow-anchor: none verir ve kaydırmasını dinler, yüksekliğini ise siz verirsiniz. Kök div'e bu durumda stil yazılmaz. Klasör adını ve toplam not sayısını gösteren başlıklı liste:
<Virtualization<Note>
itemHeight={64}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return { items: found.slice(start, start + pageSize), totalCount: found.length, hasMore: start + pageSize < found.length };
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
mainTemplate={(content, status) => (
<div class="list">
<div class="list__head">
<h2>{FOLDERS[folder].title}</h2>
<span class="badge">{() => status.totalCount}</span>
</div>
<div class="rows">
<content></content>
</div>
</div>
)}
/>
const list = new Virtualization<Note>({
itemHeight: 64,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return { items: found.slice(start, start + pageSize), totalCount: found.length, hasMore: start + pageSize < found.length };
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
mainTemplate: (content, status) => motifComponent("div", (box: ComponentBase) => {
box.class.add("list");
const head = motifComponent("div", (head: ComponentBase) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2: ComponentBase) => h2.setText(FOLDERS[folder].title));
const badge = motifComponent("span", (span: ComponentBase) => {
span.class.add("badge");
span.bindings.text(() => `${status.totalCount}`);
});
head.controls.add(title, badge);
});
const rows = motifComponent("div", (rows: ComponentBase) => {
rows.class.add("rows");
rows.controls.add(content);
});
box.controls.add(head, rows);
}),
});
<Virtualization
itemHeight={64}
dataRequest={async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return { items: found.slice(start, start + pageSize), totalCount: found.length, hasMore: start + pageSize < found.length };
}}
itemTemplate={(note) => <NoteRow note={note} folder={folder} />}
mainTemplate={(content, status) => (
<div class="list">
<div class="list__head">
<h2>{FOLDERS[folder].title}</h2>
<span class="badge">{() => status.totalCount}</span>
</div>
<div class="rows">
<content></content>
</div>
</div>
)}
/>
const list = new Virtualization({
itemHeight: 64,
dataRequest: async ({ page, pageSize }) => {
const found = notes.list(folder, state.query);
const start = page * pageSize;
return { items: found.slice(start, start + pageSize), totalCount: found.length, hasMore: start + pageSize < found.length };
},
itemTemplate: (note) => motifComponent(NoteRow, { note, folder }),
mainTemplate: (content, status) => motifComponent("div", (box) => {
box.class.add("list");
const head = motifComponent("div", (head) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2) => h2.setText(FOLDERS[folder].title));
const badge = motifComponent("span", (span) => {
span.class.add("badge");
span.bindings.text(() => `${status.totalCount}`);
});
head.controls.add(title, badge);
});
const rows = motifComponent("div", (rows) => {
rows.class.add("rows");
rows.controls.add(content);
});
box.controls.add(head, rows);
}),
});
<content></content> etiketi content parametresini yerleştirir.
content bir bileşen olduğu için kaydırma kabının controls'una eklenir.
mainTemplate listenin yapıcısında bir kez çağrılır; ilk istek, content kurulmaya başladığında yapılır.
Filtreleme
Liste iki yerde süzülebilir:
dataRequestiçinde. Örnekteki arama böyle çalışır:notes.list(folder, state.query)yalnız eşleşen notları döndürür,totalCountvehasMoresüzülmüş veriye göre hesaplanır. Süzgeç reaktif bir alanı okuyorsa otomatik yenileme listeyi kendiliğinden günceller.filterprop'uyla. Fonksiyon her yüklemede (ilk yükleme, sonraki sayfa, yenileme,setData) yüklü verinin tamamına uygulanır; boşluklar, indeksler veitemHeighttoplamları süzülmüş listeye göre hesaplanır,state.datasüzülmemiş kalır. Süzgeç yalnız yüklemede çalıştığı için okuduğu bir alan sonradan değişirse liste kendiliğinden yenilenmez;refresh()çağrılır ya da koşuldataRequest'e taşınır. Süzgeç yüklü öğelerin hepsini elerseemptyTemplategösterilir, verilmemişse liste alanı boş kalır; sunucuda başka sayfa olsa da sonraki sayfa istenmez. Sonraki sayfalarda eşleşen öğe olabiliyorsa süzmedataRequest'te yapılır.
Değişken yükseklik ve boyut değişimi
Satırların yüksekliği farklıysa ama önceden hesaplanabiliyorsa itemHeight'a fonksiyon verilir; örneğin metni olan notun satırı daha uzunsa (note) => (note.body ? 72 : 48). Yükseklikler veri her değiştiğinde (yükleme, setData, filter) bir kez toplanır; görünür aralık ikili aramayla bulunur, scrollToIndex de bu toplamları kullanır. Geçersiz (NaN, negatif) değerler 0 sayılır.
Liste yükseklikleri izlemez: itemHeight'ın okuduğu değer reaktif olsa da değişimi kendiliğinden görmez. Veri aynı kalıp bir ya da birkaç satırın yüksekliği değişince (satır genişledi, yoğunluk ayarı değişti, sabit itemHeight sayısı başka bir sayıyla değiştirildi) updateHeights() çağrılır. Yöntem yükseklikleri baştan okur ve pencereyi yeniden çizer; satırlar olduğu gibi kalır, filter yeniden çalışmaz, yükleme istenmez. Ekranın üstündeki satır veri değişiminde olduğu gibi ekrandaki yerini korur: üstteki bir satır uzarsa kaydırma konumu aynı farkla kayar. Yükseklik yalnızca çizimden sonra ölçülerek bilinebiliyorsa (serbest metinli akış) bu yöntem uygun değildir.
Liste kabının boyutu değişince (ekran döndürme, mobil klavyenin açılması, pencere boyutu) görünür satırlar kaydırma beklenmeden yeniden hesaplanır. Bunun için ResizeObserver kullanılır; ortamda yoksa bu adım atlanır, liste serbest bırakılınca gözlemci bırakılır.
Çizim modeli ve yöntemler
- DOM'da yalnızca pencere kadar satır bulunur:
[anchor − overscan, anchor + görünür + overscan),anchor = floor(scrollTop / itemHeight). Satırlar iki boşlukdiv'i arasında düz kardeşlerdir; boşluk yükseklikleri hiç negatif olmaz veüst + satırlar + alt = filtrelenmiş uzunluk × itemHeight. - Pencereden çıkan satır serbest bırakılmaz, DOM'dan sökülür; geri gelince aynı bileşen (bağları,
input durumu, öğe nesnesi) sıralı konumuna takılır. Yer tutucu yorum birikmez.
Sökülen satır
onDeactivated, geri takılan satıronActivatedalır.cacheSizeaşılınca en uzun süredir görünmeyen satırlar serbest bırakılır. - Satırlar öğe nesnesinin kimliğiyle (ilkel öğelerde sırayla) önbelleğe alınır. Aynı nesne veride birden çok kez bulunabilir; her geçiş kendi satırını alır.
setData, otomatik yenileme ve sonraki sayfanın gelişi satırları bu kimlikle eşler: yeni görünümde yine bulunan nesnenin satırı, ekranda da önbellekte de olsa korunur ve yeni sırasına taşınır; yeni görünümde bulunmayan nesnenin satırı (sonraki sayfaylafilter'ın eledikleri dahil) serbest bırakılır. Bir nesne ham hâliyle de reaktif proxy'siyle de verilse aynı satırı bulur. İlkel öğede aynı sıradaki aynı değer satırını korur. Her yüklemede yeni nesneler geliyorsa (ör. sunucudan taze liste) aynıid'yi taşısalar bile satırlar yeniden kurulur. Korunan satırınitemTemplate'ten aldığıindexsatırın kurulduğu andaki sıradır; sonradan güncellenmez.- Veri değişince (
setData, otomatik yenileme, sonraki sayfa) ekranda görünen satırlardan yeni veride kalan ilk satır ekrandaki yerini korur: üstüne öğe eklenir ya da üstünden öğe silinirse (sıralayan birfilter'la sonraki sayfanın öğeleri üste girdiğinde de) kaydırma konumu o kadar kayar, sıra değişince kaydırma o satırı yeni yerine götürür. İlkel öğede aynı değer, eski sırasına en yakın konumunda aranır. Görünen satırların hiçbiri kalmamışsa önce ekranın üstünde, sonra altında çizili duran satırlardan ekrana en yakın kalan kullanılır; onlar da kalmamışsa kaydırma konumu olduğu gibi korunur. Ekranın üstünden taşan satır kısalırsa bir kısmı görünür kalır. Liste en üstteyken en üstte kalır, üste eklenen öğe görünür. Yeni sıralamadan sonra listenin başına dönmek içinscrollToIndex(0)çağrılır. Konum ekranda çizili satırlardan okunduğu için dizi yerinde değiştirilip (splice,sort) aynı dizi yeniden verilse de doğru kalır. Kaydırma kabınaoverflow-anchor: noneverilir: konumu tarayıcı değil liste korur; tarayıcının kendi kaydırma çapası liste satırları çizerken kaydırma konumunu değiştirip görünür alanda boşluk bırakabilir. - Her kaydırma olayında pencere yeniden hesaplanır (
hasMorefalseiken de); yalnızca eski ve yeni pencere arasındaki fark işlenir. - İlk istek liste kurulurken yapılır;
x-waitile bekletilen liste kurulmadığı için ilk istek de bekleme bitene kadar ertelenir. - İlk çizim yerleşimi bekler: kaydırma kabı henüz
document'ta değilse çizimonMounted'a ertelenir; belgede olup yüksekliği 0 olan kapta çizim, kabın boyutu değişince yapılır.
| Yöntem | Etki |
|---|---|
refresh() |
Veriyi sıfırlar, sayfa 0'ı dataRequest ile yeniden ister; önbellekteki tüm satırlar serbest bırakılır. Otomatik yenileme izlemesini yeniden kurar. Promise döner. |
setData(items) |
İstek yapmadan veriyi değiştirir; hasMore false, sayfa 0 olur ve otomatik yenileme durur. Yeni veride yine bulunan öğelerin satırları korunur, bulunmayanlarınki serbest bırakılır. |
updateHeights() |
Veri aynı kalıp yükseklikler değişince yükseklikleri yeniden okur ve pencereyi çizer; satırlar korunur, ekranın üstündeki satır yerinde kalır (bkz. Değişken yükseklik). |
scrollToIndex(i) |
i'nci satırın konumuna (sabit yükseklikte i × itemHeight) kaydırır ve hemen çizer; aralık dışındaki indeks sınıra çekilir. |
getState() |
Liste durumunun sığ kopyası. |
Yöntemler liste bileşeninin kendisindedir; JSX'te ona ref ile ulaşılır (bkz. Bileşen içinde eleman erişimi).
Yöntemler new Virtualization(...) ile kurulan bileşenin üzerinden çağrılır.
Ne zaman kullanmalı?
| Durum | Öneri |
|---|---|
| Az öğe (< ~100), değişken yükseklik | Normal liste bağı (Koşullu Gösterim ve Listeler). |
| Çok öğe, sabit ya da önceden hesaplanabilen satır yüksekliği | Virtualization (itemHeight sayı ya da fonksiyon). |
| Sonsuz kaydırma / sayfalı API | Virtualization + autoLoad. |
Sonraki adım
Bellek yönetimini ve serbest bırakma desenini öğrenmek için Bellek Yönetimi ve Serbest Bırakma bölümüne geçin.