Referans
Koleksiyon Sorguları (Query)
Query.from ile where/select/orderBy/groupBy zincirleri, sonuç üreten metotlar ve reaktivite ile kullanım.
Query<T>, bir diziyi ya da herhangi bir yinelenebilir (iterable) kaynağı zincirleme adımlarla sorgulamak için kullanılan küçük bir sarmalayıcıdır. Filtreleme, dönüştürme, sıralama ve gruplama adımları tek bir zincirde okunur biçimde yazılır:
import { Query } from "@motifx/core";
const titles = Query.from(notes.state.items)
.where((note) => !note.archived)
.orderBy((note) => note.title)
.select((note) => note.title)
.toArray();
import { Query } from "@motifx/core";
const titles = Query.from(notes.state.items)
.where((note) => !note.archived)
.orderBy((note) => note.title)
.select((note) => note.title)
.toArray();
Query isteğe bağlıdır: yalnızca import ettiğinizde pakete girer ve Array.prototype dahil global hiçbir nesneyi değiştirmez. Sorgu her zaman Query.from(...) ile başlar.
Sorgu oluşturma
| Yazım | Açıklama |
|---|---|
Query.from(kaynak) |
Kaynaktan sorgu üretir. |
new Query(kaynak) |
Query.from ile aynıdır. |
kaynak bir dizi ya da herhangi bir Iterable<T> olabilir: Set, Map.keys(), Map.values(), üreteç (generator) fonksiyonlar, reaktif diziler.
Query.from(new Set(["notes", "pinned"]));
Query.from(drafts.values());
Query.from(notes.state.items); // reaktif dizi
Query.from(new Set(["notes", "pinned"]));
Query.from(drafts.values());
Query.from(notes.state.items); // reaktif dizi
Kaynak bir diziyse kopyalanmaz, doğrudan okunur; diğer yinelenebilirler Query.from anında bir diziye kopyalanır. Kaynak dizi hiçbir adımda değiştirilmez.
Zincirlenen adımlar
Bu metotlar yeni bir Query döndürür; arkasına başka bir adım eklenebilir.
| Metot | Açıklama |
|---|---|
where(predicate) |
Koşulu sağlayan öğeleri bırakır. predicate(öğe, indeks). |
select(selector) |
Her öğeyi dönüştürür. selector(öğe, indeks). |
orderBy(keySelector) |
Anahtara göre artan sıralar. |
orderByDescending(keySelector) |
Anahtara göre azalan sıralar. |
distinctBy(keySelector) |
Aynı anahtara sahip öğelerden yalnızca ilkini bırakır. |
groupBy(keySelector) |
Öğeleri { key, items } gruplarına ayırır; sonuç Query<Group<TKey, T>> olur. |
Sonuç üreten metotlar
Bu metotlar zinciri bitirir ve bir değer döndürür.
| Metot | Dönüş | Açıklama |
|---|---|---|
toArray() |
T[] |
Sonucu yeni bir diziye kopyalar. |
first() |
T |
İlk öğe; sorgu boşsa MotifError (MJX601) fırlatır. |
firstOrDefault() |
T \| undefined |
İlk öğe; sorgu boşsa undefined. |
any(predicate?) |
boolean |
Koşul verilmezse "en az bir öğe var mı", verilirse "koşulu sağlayan öğe var mı". |
all(predicate) |
boolean |
Tüm öğeler koşulu sağlıyor mu. |
aggregate(seed, func) |
TResult |
seed başlangıç değeriyle öğeleri tek değere indirger. |
Query yinelenebilir olduğu için toArray() çağırmadan da doğrudan dolaşılabilir:
for (const title of Query.from(notes.state.items).select((note) => note.title)) {
console.log(title);
}
const titles = [...Query.from(notes.state.items).select((note) => note.title)];
for (const title of Query.from(notes.state.items).select((note) => note.title)) {
console.log(title);
}
const titles = [...Query.from(notes.state.items).select((note) => note.title)];
Örnekler
Sıralama
const latest = Query.from(notes.state.items)
.orderByDescending((note) => note.updatedAt)
.toArray();
const latest = Query.from(notes.state.items)
.orderByDescending((note) => note.updatedAt)
.toArray();
Sıralama kararlıdır: anahtarı eşit olan öğeler kaynaktaki sıralarını korur. Anahtarlar < ve > ile karşılaştırılır; sayı, dizge ve Date anahtarları beklendiği gibi çalışır. Dizgeler karakter koduna göre sıralanır; Türkçe alfabe sırası gerekiyorsa sonucu kendiniz sıralayın:
const byTitle = Query.from(notes.state.items).toArray().sort((a, b) => a.title.localeCompare(b.title, "tr"));
const byTitle = Query.from(notes.state.items).toArray().sort((a, b) => a.title.localeCompare(b.title, "tr"));
Gruplama
const perDay = Query.from(notes.state.items)
.groupBy((note) => new Date(note.updatedAt).toDateString())
.select((group) => ({ day: group.key, count: group.items.length }))
.toArray();
const perDay = Query.from(notes.state.items)
.groupBy((note) => new Date(note.updatedAt).toDateString())
.select((group) => ({ day: group.key, count: group.items.length }))
.toArray();
Gruplar, anahtarın kaynakta ilk göründüğü sırayla gelir. Anahtar eşitliği Map kurallarıyla belirlenir; nesne anahtarları değere göre değil referansa göre gruplanır.
Toplama
const characters = Query.from(notes.state.items).aggregate(0, (sum, note) => sum + note.body.length);
const hasPinned = Query.from(notes.state.items).any((note) => note.pinned && !note.archived);
const characters = Query.from(notes.state.items).aggregate(0, (sum, note) => sum + note.body.length);
const hasPinned = Query.from(notes.state.items).any((note) => note.pinned && !note.archived);
Reaktivite ile kullanım
Query reaktif dizileri sıradan dizi metotlarıyla okur; bu yüzden bir getter, createComputed ya da effect içinde kullanıldığında kaynağa bağımlılık kurulur ve kaynak değiştiğinde sonuç yeniden hesaplanır.
Listede sorgu bir getter'a konur ve satırlar liste bağıyla çizilir. JSX'te derleyici this.recent.map(...) (fonksiyonda recent().map(...)) ifadesini bindings.list'e çevirir, JSX'siz yazımda bindings.list doğrudan çağrılır. Satırlar öğe nesnesiyle eşleşir, aynı nesnenin DOM düğümleri yeniden kullanılır; key bu eşleşmeye katılmaz, öğeyi tanımlar (bkz. Koşullu Gösterim ve Listeler). Örnekteki liste son düzenlenen beş notu gösterir:
import { Component, Query } from "@motifx/core";
import { NotesService } from "../services/notes";
export class RecentNotes extends Component<HTMLUListElement> {
notes = this.getService(NotesService)!;
get recent() {
return Query.from(this.notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
}
override view() {
return <>{this.recent.map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</>;
}
}
import { Component, ComponentBase, Query, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class RecentNotes extends Component<HTMLUListElement> {
static override elementTag = "ul";
notes = this.getService(NotesService)!;
get recent() {
return Query.from(this.notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
}
override view() {
return motifComponent({
initializeComponent: (holder: ComponentBase) =>
holder.bindings.list(() => this.recent, (note: Note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => li.bindings.text(() => note.title || "Adsız"),
})),
});
}
}
import { FromService, Query } from "@motifx/core";
import { NotesService } from "../services/notes";
export function RecentNotes() {
const notes = FromService(NotesService)!;
const recent = () =>
Query.from(notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
return <ul>{recent().map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</ul>;
}
import { ComponentBase, FromService, Query, motifComponent } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function RecentNotes() {
const notes = FromService(NotesService)!;
const recent = () =>
Query.from(notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
return motifComponent("ul", (ul: ComponentBase) => {
const rows = motifComponent({
initializeComponent: (holder: ComponentBase) =>
holder.bindings.list(() => recent(), (note: Note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => li.bindings.text(() => note.title || "Adsız"),
})),
});
ul.controls.add(rows);
});
}
import { Query, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const RecentNotes = () => motifOptions({
el: "ul",
get recent(): Note[] {
return Query.from(this.getService(NotesService)!.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
},
view() {
return <>{this.recent.map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</>;
},
});
import { ComponentBase, Query, motifComponent, motifOptions } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const RecentNotes = () => motifOptions({
el: "ul",
get recent(): Note[] {
return Query.from(this.getService(NotesService)!.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
},
view() {
return motifComponent({
initializeComponent: (holder: ComponentBase) =>
holder.bindings.list(() => this.recent, (note: Note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li: ComponentBase) => li.bindings.text(() => note.title || "Adsız"),
})),
});
},
});
import { Component, Query } from "@motifx/core";
import { NotesService } from "../services/notes";
export class RecentNotes extends Component {
static elementTag = "ul";
notes = this.getService(NotesService);
get recent() {
return Query.from(this.notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
}
view() {
return <>{this.recent.map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</>;
}
}
import { Component, Query, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";
export class RecentNotes extends Component {
static elementTag = "ul";
notes = this.getService(NotesService);
get recent() {
return Query.from(this.notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
}
view() {
return motifComponent({
initializeComponent: (holder) =>
holder.bindings.list(() => this.recent, (note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => li.bindings.text(() => note.title || "Adsız"),
})),
});
}
}
import { FromService, Query } from "@motifx/core";
import { NotesService } from "../services/notes";
export function RecentNotes() {
const notes = FromService(NotesService);
const recent = () =>
Query.from(notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
return <ul>{recent().map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</ul>;
}
import { FromService, Query, motifComponent } from "@motifx/core";
import { NotesService } from "../services/notes";
export function RecentNotes() {
const notes = FromService(NotesService);
const recent = () =>
Query.from(notes.state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
return motifComponent("ul", (ul) => {
const rows = motifComponent({
initializeComponent: (holder) =>
holder.bindings.list(() => recent(), (note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => li.bindings.text(() => note.title || "Adsız"),
})),
});
ul.controls.add(rows);
});
}
import { Query, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
export const RecentNotes = () => motifOptions({
el: "ul",
get recent() {
return Query.from(this.getService(NotesService).state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
},
view() {
return <>{this.recent.map((note) => <li key={note.id}>{() => note.title || "Adsız"}</li>)}</>;
},
});
import { Query, motifComponent, motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
export const RecentNotes = () => motifOptions({
el: "ul",
get recent() {
return Query.from(this.getService(NotesService).state.items)
.where((note) => !note.archived)
.orderByDescending((note) => note.updatedAt)
.toArray()
.slice(0, 5);
},
view() {
return motifComponent({
initializeComponent: (holder) =>
holder.bindings.list(() => this.recent, (note) => motifComponent("li", {
indexkey: () => note.id,
initializeComponent: (li) => li.bindings.text(() => note.title || "Adsız"),
})),
});
},
});
Bir not düzenlenip updatedAt değiştiğinde ya da arşivlendiğinde zincir baştan çalışır ve liste yalnızca farkı DOM'a uygular. Çok büyük listelerde Sanal Listeler bölümündeki Virtualization bileşenini kullanın.
Bilinmesi gerekenler
- Değerlendirme hemen yapılır. Her zincir adımı kendi ara dizisini üretir; tembel (lazy) değerlendirme yoktur. Birkaç bin öğeye kadar bunun maliyeti önemsizdir.
- Kaynak değişmez.
orderByveorderByDescendingkaynağın kopyasını sıralar;toArray()her çağrıda yeni bir dizi döndürür. - Sonuç anlık görüntüdür. Her adım çağrıldığı anda çalışır; kaynağa sonradan eklenen öğeler daha önce hesaplanmış adım sonuçlarına yansımaz. (Adım uygulanmamış
Query.from(dizi)diziyi kopyalamadığı için dizinin güncel hâlini okur; dizi olmayan kaynak isefromanında kopyalanır.) Güncel sonuç için zinciri bir getter içinde kurun ve her okumada yeniden çalıştırın. - Zincir
Querydöndürür.where,select,groupBygibi adımların sonucu dizi değilQuery'dir (groupBy→Query<Group<TKey, T>>); dizi gereken yerde sonunatoArray()ekleyin.
Sonraki adım
List, Dictionary ve LinkedList yapıları için Koleksiyonlar bölümüne geçin.