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:

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

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.