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

Sonraki adım

List, Dictionary ve LinkedList yapıları için Koleksiyonlar bölümüne geçin.