İ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 okumaSı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ölgelerigroupsolarak döndürüngroupKeys: ['Berlin']→ Berlin'in kategorilerinigroupsolarak döndürüngroupKeys: ['Berlin', 'Toys']→ gerçek veri satırlarınırowsolarak 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:
comparatorveexternalFilterJavaScript fonksiyonları, ve bir fonksiyon sunucunuza gönderilemez. Uzak bir kaynakta grid bunları hiç uygulamaz: sıra da satırlar daloadne 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.