Bağımlılık Enjeksiyonu
Servis Kaydı
ServiceCollection kayıt biçimleri, başlangıçta oluşturulan singleton'lar, @Injectable ile otomatik kayıt ve seçenekleri.
Servis kaydı
Servisler Application.CreateBuilder()'ın döndürdüğü kurucunun services koleksiyonuna (ServiceCollection), build()'den önce kaydedilir. Her kayıt bir token'ı (çoğunlukla sınıfın kendisi; dizge ya da sembol de olabilir) bir uygulamaya ve bir yaşam süresine bağlar; yaşam süreleri Yaşam Süreleri ve Kapsamlar sayfasındadır:
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, NotesService); // uygulama boyunca tek örnek
builder.services.addScoped(NoteDraft, NoteDraft); // gezinme başına bir örnek
builder.services.addTransient(SearchIndex, SearchIndex); // her çözümlemede yeni örnek
const app = builder.build();
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, NotesService); // uygulama boyunca tek örnek
builder.services.addScoped(NoteDraft, NoteDraft); // gezinme başına bir örnek
builder.services.addTransient(SearchIndex, SearchIndex); // her çözümlemede yeni örnek
const app = builder.build();
Kayıt biçimleri
add*(token, impl) metotlarında impl şunlardan biri olabilir:
// 1) Sınıf (useClass)
builder.services.addSingleton(NotesService, NotesService);
// 2) Hazır değer (useValue): sınıf olmayan bir nesne olduğu gibi kaydedilir
builder.services.addSingleton("config", { apiUrl: "/api/notes" });
// 3) Fabrika / descriptor nesnesi: `deps` verilirse fabrika (…bağımlılıklar, sağlayıcı) ile çağrılır
builder.services.addSingleton(SearchIndex, {
useFactory: (notes: NotesService) => new SearchIndex(notes),
deps: [NotesService],
});
// deps'siz fabrika yalnızca sağlayıcıyı alır
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) });
// 4) Async değer (Promise): async fabrikaya sarılır; `get` değil `getAsync` ile çözümlenir
builder.services.addSingleton(SyncService, import("./services/sync").then((m) => new m.SyncService()));
const sync = await app.provider.getAsync(SyncService);
// 1) Sınıf (useClass)
builder.services.addSingleton(NotesService, NotesService);
// 2) Hazır değer (useValue): sınıf olmayan bir nesne olduğu gibi kaydedilir
builder.services.addSingleton("config", { apiUrl: "/api/notes" });
// 3) Fabrika / descriptor nesnesi: `deps` verilirse fabrika (…bağımlılıklar, sağlayıcı) ile çağrılır
builder.services.addSingleton(SearchIndex, {
useFactory: (notes) => new SearchIndex(notes),
deps: [NotesService],
});
// deps'siz fabrika yalnızca sağlayıcıyı alır
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) });
// 4) Async değer (Promise): async fabrikaya sarılır; `get` değil `getAsync` ile çözümlenir
builder.services.addSingleton(SyncService, import("./services/sync").then((m) => new m.SyncService()));
const sync = await app.provider.getAsync(SyncService);
deps dizisi hem sınıf hem fabrika kayıtlarında geçerlidir: sıralı olarak çözümlenir ve yapıcıya/fabrikaya aynı sırada geçirilir; fabrikada son parametre her zaman ServiceProvider'dır.
Sağlayıcı yalnız sınıf kaydını (useClass) new ile kurar. useValue ile verilen değer ve fabrikanın döndürdüğü değer olduğu gibi verilir; get ve getAsync aynı değeri döndürür. Bir fonksiyonu ya da sınıfın kendisini servis olarak vermek için { useValue: fn } veya onu döndüren bir fabrika kullanın; örnek isteyen fabrika new'i kendisi yazar.
add*(token, impl)'e doğrudan verilen bir fonksiyon sınıf olarak kurulur. Kurulamayan bir fonksiyon (ok fonksiyonu, async fonksiyon, nesne metodu) TypeScript'te derleme hatası verir, çalışma zamanında da kayıt satırında MJX413 fırlatır:
builder.services.addSingleton('formatter', (x: number) => x.toFixed(2)); // hata: MJX413
builder.services.addSingleton('formatter', { useValue: (x: number) => x.toFixed(2) }); // fonksiyonun kendisi
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) }); // fabrika
builder.services.addSingleton('formatter', (x) => x.toFixed(2)); // hata: MJX413
builder.services.addSingleton('formatter', { useValue: (x) => x.toFixed(2) }); // fonksiyonun kendisi
builder.services.addSingleton(SearchIndex, { useFactory: (sp) => new SearchIndex(sp.get(NotesService)) }); // fabrika
ServiceCollection metotları:
| Metot | Açıklama |
|---|---|
addSingleton/addScoped/addTransient(token, impl) |
Kayıt. |
tryAddSingleton/tryAddScoped/tryAddTransient(token, impl) |
Yalnızca kayıtlı değilse ekler; boolean döner. |
replace(token, impl, lifetime?) |
Var olan kaydı değiştirir. |
remove(token) / has(token) |
Kaldır / kontrol et. |
reset() |
Tümünü temizle. |
buildServiceProvider() |
ServiceProvider üretir (build() bunu otomatik yapar). |
Başlangıçta oluşturulan singleton'lar
Bir singleton'ın hiçbir yerden istenmeden uygulama başlarken oluşturulması için kayda startup: true verilir. Kurucusu kendi başına iş yapan servisler (belgeye öznitelik yazan, dinleyici takan, bağlantı açan) böyle kaydedilir. Örnekteki servis kurulurken bir effect açar ve seçili temayı <html> elemanına yazar; hiçbir bileşen onu istemese de çalışması gerekir:
@Injectable({ lifetime: "singleton", startup: true })
export class SettingsService {
readonly state = reactive({ theme: "system" as Theme });
constructor() {
effect(() => {
const root = document.documentElement;
if (this.state.theme === "system") delete root.dataset.theme;
else root.dataset.theme = this.state.theme;
});
}
}
@Injectable({ lifetime: "singleton", startup: true })
export class SettingsService {
state = reactive({ theme: "system" });
constructor() {
effect(() => {
const root = document.documentElement;
if (this.state.theme === "system") delete root.dataset.theme;
else root.dataset.theme = this.state.theme;
});
}
}
Açık kayıtta startup nesne biçiminde verilir; useClass ve useFactory ile çalışır, tryAddSingleton aynı biçimi alır:
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
builder.services.addSingleton(SearchIndex, {
useFactory: (notes: NotesService) => new SearchIndex(notes),
deps: [NotesService],
startup: true,
});
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
builder.services.addSingleton(SearchIndex, {
useFactory: (notes) => new SearchIndex(notes),
deps: [NotesService],
startup: true,
});
- Servisler
app.run()içinde, kabuk ve ilk sayfa kurulmadan önce oluşturulur;build()oluşturmaz.run()'dan önce çağrılanuseDevelopmentveuseLoggingayarları kurucuda geçerlidir. - Oluşturma sırası kayıt sırasıdır:
build()'den öncekiadd*çağrıları yazıldıkları sırayla, ardından@Injectablesınıfları. Bağımlılıklar her zamanki gibi önce kurulur. run()'dan öncegetile çözülmüş bir servis yeniden oluşturulmaz; bileşenler,FromServiceveinjectaynı örneği alır.- Kurucu hata fırlatırsa
run()aynı hatayı fırlatır ve uygulama başlamaz. - Servisler
getile, senkron oluşturulur: async fabrika ya da Promise ile kaydedilmiş bir servisterun()MJX402fırlatır. Bu servislerestartupvermeyin,await app.provider.getAsync(token)ile başlatın. startupyalnızsingletonkayıtta geçerlidir.transientvescopedkayıtta yok sayılır, geliştirme modundaMJX415uyarısı yazılır; servis ilk çözümlemede oluşturulur.@Injectablesınıflarındanrun()anında modülü yüklenmiş olanlar oluşturulur. Modülürun()'dan sonra yüklenen bir sınıf (yalnız tembel bir sayfanın içe aktardığı servis) yüklendiğinde oluşturulmaz, ilk çözümlemede oluşturulur.replace(token, impl)yeni kayıttastartupyazılmamışsa önceki kaydınstartupdeğerini korur;startup: falsekapatır.- Uygulama çalışırken
builder.rebuild()yeni bir sağlayıcı kurar vestartupsingleton'larını o sağlayıcıda hemen yeniden oluşturur; kurucu hata fırlatırsarebuild()aynı hatayı fırlatır. Bileşenler,app.providerveServiceProvidertoken'ı yeni sağlayıcıyı verir. Eski sağlayıcı serbest bırakılır;rebuild()bu serbest bırakmayı beklemez.run()'dan önce çağrılanrebuild()servisleri oluşturmaz,run()oluşturur.
Dekoratörlerle otomatik kayıt
@Injectable, bir sınıfı otomatik kayıt için işaretler; açık bir add* satırı gerekmez. Uygulama build edilirken (builder.build() içinde autoRegisterInjectables çağrılır) işaretli sınıflar seçenekleriyle kaydedilir.
Modülü build()'den sonra yüklenen sınıflar da (yalnızca tembel bir sayfanın içe aktardığı servisler) kaydedilir: tanımlayıcı bulunamadığında kayıt ilk çözümleme anında yapılır. Açık kayıt (addSingleton/addScoped/addTransient) her zaman dekoratörü ezer, dekoratör açık kaydı ezemez.
import { Injectable, inject, reactive } from "@motifx/core";
@Injectable({ lifetime: "singleton" })
export class NotesService {
readonly state = reactive({ items: [] as Note[] });
}
@Injectable() // varsayılan lifetime: 'transient'
export class SearchIndex {
private notes = inject(NotesService);
count() {
return this.notes.state.items.length;
}
}
import { Injectable, inject, reactive } from "@motifx/core";
@Injectable({ lifetime: "singleton" })
export class NotesService {
state = reactive({ items: [] });
}
@Injectable() // varsayılan lifetime: 'transient'
export class SearchIndex {
notes = inject(NotesService);
count() {
return this.notes.state.items.length;
}
}
@Injectable standart (TC39) bir sınıf dekoratörüdür; tsconfig.json'da ya da Vite'ın esbuild ayarında ek bir ayar gerektirmez. @motifx/compiler dekoratörleri .tsx dosyalarında olduğu gibi .ts ve .js dosyalarında da derler; vite dev ve vite build aynı sonucu verir. En yakın tsconfig.json'unda experimentalDecorators: true olan dosyalar TypeScript'in eski dekoratör dönüşümüne bırakılır; @Injectable o biçimde de çalışır.
@Injectable seçenekleri
| Seçenek | Anlam |
|---|---|
lifetime |
'singleton', 'scoped' ya da 'transient'; verilmezse 'transient'. |
deps |
Yapıcı parametreleri olarak sırayla çözülen token'lar (bkz. deps). |
startup |
Yalnız singleton'da: servis run() içinde, istenmeden oluşturulur (bkz. Başlangıçta oluşturulan singleton'lar). |
Sonraki adım
Bir servisin ne kadar yaşadığını öğrenmek için Yaşam Süreleri ve Kapsamlar bölümüne geçin.