İstemci taraflı sıralama ve filtreleme nerede çalışmayı bırakıyor

Bir Web Worker size tarayıcıda etkileşimli bir milyon satır kazandırır. Size bir tarayıcı sekmesinden büyük bir veri kümesi, ya da bir kullanıcının satırların yalnızca bir kısmını görmesine izin verilen bir veri kümesi kazandırmaz. Bu farklı bir sözleşme gerektiren farklı bir problem.

KanuniLabs6 dk okuma

Sıralama ve filtrelemeyi ana iş parçacığından taşıyın ve bir milyon satır sayfayı dondurmaz olur. Ama bu düzeltmenin içine gömülü bir sınır var, ve bu sınır bir satır sayısı değil — "dizi" kelimesi. Bir Web Worker'ın yaptığı her şey hâlâ tarayıcının zaten sahip olduğu veriden başlıyor. İki durum, sıralamanın hızından bağımsız olarak sizi bu çizginin diğer tarafına koyuyor:

  • veri kümesi bir tarayıcı sekmesine sığmıyor — on milyon satır, backend'inizin kendi nedenleriyle sayfaladığı bir tablo, kimsenin tam olarak indirmeyi hiç denemediği bir rapor;
  • bir satır seviyesi izin, kullanıcının devtools açıp yanıtı okuyamayacağı bir yerde değerlendirilmek zorunda — bu da tüm satırları istemciye gönderip orada filtrelemeyi eler, filtre ne kadar hızlı çalışırsa çalışsın.

İkisi de şu anlama gelir: grid artık hangi satırların var olduğuna karar veren şey olamaz. Sunucunuzdaki bir şey bunu yapmak zorunda, ve grid ona her sorgu değiştiğinde sormak zorunda.

Sözleşmenin şekli

Bir dizi yerine, grid'e load metodu olan bir nesne veriyorsunuz:

interface DataSource<TRow> {
  load(options: LoadOptions): Promise<LoadResult<TRow>>;
  byKey?(key: RowKey): Promise<TRow | undefined>;
  loadDistinctValues?(options: DistinctValuesOptions): Promise<DistinctValue[]>;
}

Yalnızca load zorunlu. Grid onu sorgu her değiştiğinde, ya da henüz tutmadığı bir satır bloğuna kaydırıldığında çağırıyor:

import { EnterpriseDataGrid } from '@kanunilabs/datagrid-react-enterprise';

const orders = {
  async load({ skip = 0, take = 100, sort, filter, searchText, summaries }) {
    const res = await fetch('/api/orders', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ skip, take, sort, filter, searchText, summaries }),
    });
    const data = await res.json();
    return {
      rows: data.rows,
      totalCount: data.total, // tüm filtrelenmiş sonuç, bu sayfa değil
      summaries: data.totals, // opsiyonel — aşağıya bakın
    };
  },
};

<EnterpriseDataGrid dataSource={orders} rowKey="id" columns={columns} filterRow statusBar />;

totalCount'u erkenden doğru almak önemli: kaydırma çubuğunu boyutlandıran şey bu, yani burada yanlış bir sayı, kendinden emin bir şekilde boşluğa kayan bir grid olarak ortaya çıkar — ki bu, yalnızca belirtiden hata ayıklaması garip bir bug.

Endpoint'inizin aldığı filter, başlık filtre satırının ve filter builder'ın ürettiği aynı AST — önceden kurulmuş bir SQL string'i değil, çünkü { logic: 'and', nodes: [...] } gibi bir şeyi kendi şemanız için bir WHERE cümlesine nasıl çevireceğini bilen sizsiniz, ve bir AST, içinde fonksiyon olan bir sorgu nesnesinin yapamayacağı şekilde JSON.stringify'dan sağ çıkıyor.

Atlarsanız sessizce kırılan kısım: distinct değerler

Bir başlık filtre popup'ının içindeki checklist — "bu sütunun içerdiği her değer, istediklerinizi işaretleyin" — istemci taraflı bir grid için kolay: zaten her satırı tutuyor, yani benzersiz değerleri doğrudan onlardan okuyor. Sunucu taraflı bir grid'in bu seçeneği yok. Yalnızca hangi satır blokları yüklenmişse onları tutuyor, ve "önbellekte ne varsa" ondan bir checklist kurmak, hiç checklist sunmamaktan daha kötü — bir kullanıcı dört değeri işaretleyip bunun hepsi olduğuna inanıyor, ve kimsenin ona göstermediği beşinci bir değeri sessizce dışlayan bir filtre elde ediyor.

Bu yüzden popup yalnızca loadDistinctValues'ı uyguladığınızda görünüyor:

async loadDistinctValues({ columnId, limit }) {
  const rows = await db.selectDistinct(columnId).orderBy(columnId).limit(limit);
  return rows.map((r) => ({ value: r[columnId] }));
}

Atlarsanız, grid kısmi veriden sahte bir checklist uydurmaz — popup sunulmaz, o kadar. Filtre satırını açtıysanız o çalışmaya devam eder, çünkü bir değer listesi değil bir koşul gönderir.

Toplamlar, ve telin diğer ucuna geçemeyen tek agregasyon

Alt bilgi toplamları aynı şekilde çalışır: grid size sütun ve agregasyon tipine göre neye ihtiyacı olduğunu söyler, siz de az önce gönderdiğiniz sayfa değil, filtrelenmiş tüm sonuç üzerinde hesaplanmış sayıları döndürürsünüz.

// options.summaries → [{ columnId: 'revenue', type: 'sum' }]
return {
  rows,
  totalCount,
  summaries: { 'revenue:sum': 1_284_500, 'id:count': 8_412 },
};

Bir agregasyon tipi gerçekten size ulaşamıyor: type: 'custom' istemcide yaşayan bir JavaScript fonksiyonu, ve bir fonksiyonu JSON.stringify ile sunucunuzun çalıştırması için göndermenin bir yolu yok. Bir sütunda tek agregasyonu custom olan bir grid, onun için hiçbir şey göndermez ve uzak bir kaynakta boş bir alt bilgi hücresi gösterir. Bu dürüst cevap — orada bir sayı tahmin eden bir grid, hiçbir şey göstermeyenden daha kötü olurdu.

Sizi şaşırtmadan önce bilmeye değer diğer ayrıntı: toplamlar bir sayfaya değil, sorguya ait. Onları her yanıtta döndürmek uygun. Yalnızca skip === 0 olduğunda döndürmek de uygun. Kırılan şey, yeni bir filtreyi eski filtrenin toplamlarıyla yanıtlamak — bu yüzden grid, sorgu değiştiği an tuttuğu her şeyi atıyor, böylece ekranda bayat bir sayı asla taze satırların altında oturamıyor.

Ağacı tutmadan gruplama

Satır gruplaması, tüm ağacı bir kerede istemek yerine her seviyede aynı soruyu soruyor. group hangi sütunlara göre gruplanacağını söylüyor; groupKeys o ağaçtaki hangi düğümün açıldığını söylüyor, ve uzunluğu derinlik. group: [region, category] verildiğinde:

  • groupKeys: [] → bölgeleri groups olarak döndürün
  • groupKeys: ['Berlin'] → Berlin'in kategorilerini groups olarak döndürün
  • groupKeys: ['Berlin', 'Toys'] → gerçek veri satırlarını rows olarak döndürün
return {
  rows: [], // tip gereği zorunlu; bir grup seviyesi `groups` ile yanıtlar
  totalCount: 12, // BU seviyedeki çocuklar
  groups: [{ value: 'Berlin', count: 1_204, aggregates: { 'revenue:sum': 91_400 } }],
};

count ve totalCount farklı soruları yanıtlıyor ve yanlışlıkla birbirine karıştırmak kolay: count, herhangi bir derinlikte altında kaç veri satırı olduğu — grup başlığının gösterdiği sayı. totalCount, bu seviyenin kaç çocuğu olduğu, yani bir grubun ekrana sığandan fazla alt grubu varsa grid'in sayfaladığı şey. Bunları karıştırmak, gururla birkaç bin satır bildirip sonra yalnızca on ikisini kaydırabileceğiniz bir grup satırı üretiyor.

Hiçbir zaman bir isteğe dönüşmeyenler

Birkaç şey endpoint'inize hiç ulaşmıyor, ve hangileri olduğunu bilmeye değer:

  • comparator ve externalFilter JavaScript fonksiyonları, ve bir fonksiyon sunucunuza gönderilemez. Uzak bir kaynakta grid bunları hiç uygulamaz: sıra da satırlar da load ne döndürdüyse o. Bir izin kuralı zaten endpoint'e ait — oraya taşınmanın amacı buydu.
  • Seçim, odak, düzenleme durumu ve sütun düzeni görünüm durumudur. Sunucuya asla gidip gelmezler — gitmeleri için bir sebep yok.
  • Export ise ulaşıyor. Filtrelenmiş tüm sonucu kapsıyor, yani grid dosyayı kurmak için DataSource'unuzda sayfa sayfa ilerliyor, ve büyük bir export, o sorgu için endpoint'iniz ne kadar yavaşsa o kadar yavaş. Bir üst sınır var, varsayılanı 100.000 satır: onu aşan bir export, sessizce kırpılmış bir dosya yazmak yerine sınırı adıyla anan bir mesajla başarısız oluyor.

Filtre satırı ayrıca uzak bir kaynağa karşı debounce ediyor (varsayılan 200 ms), yani tek solukta yazılan bir kelime harf başına bir istek değil, tek bir istek oluyor. Grid yola çıkmış bir isteği iptal edemiyor — load'a bir abort sinyali verilmiyor — ama o arada değişmiş bir sorgu için gelen her cevabı atıyor; yani kazanan son gelen yanıt değil, son tuş vuruşu.

Aynı bileşen, sonraya bırakılmış bir karar

Bir diziden bir DataSource'a geçmek render ettiğiniz bileşeni değiştirmiyor: gruplama, filtreleme, toplamlar, seçim, tema ve olay yüzeyi her iki durumda da aynı prop'lar ve aynı olaylar; istisnaların listesi yukarıdaki farklar. Bu bilerek böyle — verinizin nerede yaşadığına dair kararı ilk günden vermek zorunda olmadığınız anlamına geliyor. Bir grid, veri kümesi küçükken bir dizi üzerinde başlayabilir, ve küçük olmayı bıraktığı gün, entegre edilmesi gereken farklı bir bileşen haline gelmeden sunucu taraflı sayfalamaya geçebilir.


Sunucu taraflı veri bir Enterprise özelliği; düz bir diziyi Community DataGrid'e geçirmek her durumda etkilenmez. Uzak sıralama önceliği ve uzak gruplama dahil tam DataSource sözleşmesi sunucu taraflı veri sayfasında.

Etiketlerperformanssunucu taraflı verienterprise