Yönlendirme

Rota Kancaları ve Guard'lar

Rota yaşam döngüsü kancaları, uygulama düzeyi guard'lar, middleware, router'ı yeniden başlatma ve kapatma.

Bir gezinme adres değişip sayfa ekrana gelene kadar birkaç adımdan geçer. Bu adımlara üç yerden müdahale edilir: rotanın kendi kancaları (onEntering, onEnter, onLeave, onUpdate), her gezinmede çalışan uygulama düzeyi guard'lar (useGuard) ve adres çözülmeden önce çalışan middleware (app.use).

Rota yaşam döngüsü kancaları

RouteItem üzerinde, gezinme yaşam döngüsüne bağlı kancalar tanımlanabilir:

Kanca Ne zaman
onEntering(ctx) Rota bileşeni oluşturulmadan önce (guard'lardan sonra; sayfa henüz DOM'da değil); aynı rotada parametre değişiminde onUpdate'ten sonra.
onEnter(ctx) Rota bileşeni mount edildikten ve onShow çalıştıktan sonra.
onLeave(ctx) Rotadan çıkarken (aynı rotada parametre değişiminde de), useGuard guard'larından önce. false veya { cancel: true, reason } döndürerek gezinmeyi iptal eder.
onUpdate(ctx) Aynı rotada yalnızca parametreler değiştiğinde; ardından onEntering çalışır ve sayfa yeni parametrelerle yeniden kurulur. keepAlive: true ile aynı örnek korunur, yalnızca kancalar çalışır.

Sıra: onLeave (ayrılan rota) → useGuard → onUpdate (yalnızca parametre değişiminde) → onEntering → bileşen oluşturma ve yerleştirme → onShow → onEnter → onRouterChanged → yeni sayfa ekrana geldikten sonra ayrılan sayfanın serbest bırakılması (bkz. Ayrılan sayfa).

Bağlam nesneleri: onEntering/onEnter { path, params, meta, to: { path, params, meta } }; onLeave { from: { path, params, meta }, to: { path, params, meta, redirectedFrom?, direction?, options?, state? } }; onUpdate { from: { path, params }, to: { path, params }, meta }. Kancalar async olabilir; router sonucunu bekler.

Bu kancaların fırlattığı hata (async kancada reddedilen promise dahil) ve onShow'un fırlattığı hata (async onShow'un reddedilen promise'i dahil) MJX306 koduyla raporlanır; gezinme devam eder. Rota bileşeni oluşturulamazsa (yapıcı hata fırlatır, import başarısız olur) MJX304 raporlanır ve fallbacks.error sayfası gösterilir.

useRouter({ hooks }) ile aynı dört kanca (onEntering, onEnter, onLeave, onUpdate) uygulama genelinde tanımlanır; genel kanca yalnızca kendi kancası olmayan rotalar için çalışır, rotanın kendi kancası genel olanın yerine geçer.

onLeave ayrılan rotanın bağlamını alır; aynı rotada parametre değişiminde de çalıştığı için from.params her zaman ayrılan adresin parametreleridir. Kanca servislere useApplication().services ile ulaşır. Örnekte ayrılan not boşsa silinir:

import { useApplication, type RouteItem } from "@motifx/core";
import NotePage from "./pages/NotePage";
import { NotesService } from "./services/notes";

const note: RouteItem = {
  path: "/{id(\\d+)}",
  control: NotePage,
  onLeave: ({ from }) => {
    const notes = useApplication().services.get(NotesService);
    const left = notes.find(from.params.id);
    if (left && !left.title.trim() && !left.body.trim()) notes.remove(left.id);
  },
};
import { useApplication } from "@motifx/core";
import NotePage from "./pages/NotePage";
import { NotesService } from "./services/notes";

const note = {
  path: "/{id(\\d+)}",
  control: NotePage,
  onLeave: ({ from }) => {
    const notes = useApplication().services.get(NotesService);
    const left = notes.find(from.params.id);
    if (left && !left.title.trim() && !left.body.trim()) notes.remove(left.id);
  },
};

Gezinmeyi durdurmak için kanca false ya da { cancel: true, reason: "…" } döndürür; app.navigate'in sonucu { ok: false, cancelled: true, reason } olur.

Gezinme guard'ları ve kancaları (uygulama düzeyi)

Application üzerinde global guard'lar ve kancalar tanımlanır. useGuard her gezinmeden önce çalışır ve kararını next ile verir: next() devam eder, next("/yol") başka bir adrese yönlendirir, next(false) gezinmeyi iptal eder; next hiç çağrılmazsa gezinme engellenir. onRouterChanged her gezinmeden sonra çalışır (açılış ve bulunamayan adres dahil); açılışı ayırmak için yükteki initial alanı okunur.

Guard kararını hedefin to.params ve to.meta değerlerine bakarak verir; onRouterChanged gezinme bittikten sonra yükteki değerlerle çalışır. Örnekte guard hedef not arşivdeyse adresi değiştirir, onRouterChanged sekme başlığını yazar:

const notes = app.provider.get(NotesService);

app.useGuard(({ to }, next) => {
  const note = to.params.id ? notes.find(to.params.id) : undefined;
  if (note?.archived && to.meta.folder !== "archive") next(`/archive/${note.id}`);
  else next();
});

app.onRouterChanged(({ meta }) => {
  document.title = meta.folder ? `${FOLDERS[meta.folder as Folder].title} · Notlar` : "Notlar";
});
const notes = app.provider.get(NotesService);

app.useGuard(({ to }, next) => {
  const note = to.params.id ? notes.find(to.params.id) : undefined;
  if (note?.archived && to.meta.folder !== "archive") next(`/archive/${note.id}`);
  else next();
});

app.onRouterChanged(({ meta }) => {
  document.title = meta.folder ? `${FOLDERS[meta.folder].title} · Notlar` : "Notlar";
});

Guard'lar açılış gezinmesi dahil her gezinmede, eklendikleri sırayla çalışır; ilk yönlendiren ya da iptal eden guard zinciri bitirir.

Guard'ın next('/login') yönlendirmesi, adres çubuğu engellenen yolu gösteriyorsa (ilk yükleme, geri/ileri) geçmiş kaydını replace ile değiştirir; "geri" tuşu tekrar engellenen yola dönüp aynı guard'a takılmaz. Programatik gezinmede (başka bir sayfadan) hedef normal biçimde geçmişe eklenir.

Guard hata fırlatırsa (ya da döndürdüğü promise reddedilirse) gezinme iptal edilir ve hata MJX306 koduyla (The guard hook threw.) raporlanır; app.navigate'in sonucu { ok: false, cancelled: true, reason: 'guard' } olur. Geri/ileri gezinmesinde adres, diğer iptallerde olduğu gibi geri alınır.

Geri/ileri iptal edilirse adres geri alınır. Tarayıcının geri/ileri tuşu (Android'de sistem geri hareketi de) adresi gezinme başlamadan değiştirir. Bir guard, onLeave ya da middleware bu gezinmeyi iptal ederse router adresi history.go ile ekranda duran sayfanın kaydına geri döndürür; adres ve ekran ayrışmaz, geçmiş de bozulmaz. "Kaydedilmemiş değişiklikler var" onayı bu sayede geri tuşunda da doğru çalışır. Bunun için router her geçmiş kaydının history.state'ine ad alanlı bir sıra damgası (__motifHistory) yazar; başka bir kod history.pushState'i doğrudan çağırırsa sıra hesabı kayabilir.

Middleware — app.use

Gezinme çözümleme zincirine ara katman eklemek için:

app.use(async (ctx, next) => {
  // ctx: RouteResolveContext — { uri, context (Application), rewritePath(uri) }
  if (ctx.uri === "/arsiv") ctx.rewritePath("/archive");   // hedefi değiştir
  await next();  // zinciri devam ettir
});
app.use(async (ctx, next) => {
  // ctx: RouteResolveContext — { uri, context (Application), rewritePath(uri) }
  if (ctx.uri === "/arsiv") ctx.rewritePath("/archive");   // hedefi değiştir
  await next();  // zinciri devam ettir
});

Middleware'ler eklendikleri sırayla, yönlendirme (redirect), onLeave ve guard'lardan önce çalışır. next() çağırmayan middleware gezinmeyi iptal eder. rewritePath ile verilen adres gidilen ve adres çubuğuna yazılan adres olur. Router'ın açılış gezinmesinde (run() ve restartRouter()) middleware'ler çalışmaz; guard'lar çalışır.

Router'ı yeniden başlatma ve kapatma

app.restartRouter(uri?) yeni bir router kurar ve eskisini serbest bırakır. run()'dan sonra useRouter yeniden çağrıldıysa en son yapılandırma, çağrılmadıysa mevcut yapılandırma kullanılır; run()'dan sonra verilen yeni yapılandırma ancak restartRouter() ile devreye girer; o ana kadar çalışan router ekrandaki sayfayı, gezinmeyi ve rota alanlarını eski tabloyla sürdürür. Yeni router kökten (/) başlar; eski tablonun adresi yeni tabloya taşınmaz. Başka bir adresten başlaması istenirse adres verilir: app.restartRouter("/notes/7"). Adres çubuğu değişiyorsa mevcut geçmiş kaydının yerine yazılır, geçmişe kayıt eklenmez; kaydın state değeri adres aynı kalınca korunur, değişince silinir. shell kipinde adres çubuğuna dokunulmaz.

Rota tablosu yeniden kurulur, keepAlive önbelleği ve yığında tutulan sayfalar atılır; scrollMemory konumları korunur. Ekrandaki sayfa ve layout ayrılan sayfa gibi ayrılır: bağları hemen durur, çıkış geçişleri oynar, yeni sayfa ondan sonra kurulur ve temizlik işleri yeni sayfa ekrana geldikten sonra çalışır. Hazır örnek ya da singleton olarak verilen bir layout yeni tabloda aynı yerdeyse yerinde kalır; onDeactivated / onActivated çalışmaz. Rota alanları, ayrılan sayfa ekrandan kalktıktan sonra tek adımda yeni rotaya geçer; yerinde kalan layout eski rotadan yeni rotaya arada boş bir değer görmeden geçer.

Guard'lar yeniden başlatmanın açılış gezinmesinde de çalışır (from null, to.direction 'initial') ve o sırada app.router eski rotayı gösterir. Yönlendirme (next("/login")) uygulanır; adres yine mevcut kaydın yerine yazılır. İptal (next(false), çağrılmayan next ya da hata fırlatan guard) yeniden başlatmayı geri almaz, yalnız sayfayı göstermez: eski sayfa çıkış geçişiyle ayrılır, ardından rota alanları boşalır (uri '', ok false), adres çubuğu değişmez ve onRouterChanged gelmez; sonraki gezinme yeni tabloyla normal çalışır. onLeave çalışmaz. onRouterChanged initial: true ve direction: 'initial' ile çalışır. Dönüş değeri açılış gezinmesinin sonucudur (bkz. Programatik gezinme); yeniden başlatılan router o gezinme bitmeden yeni bir restartRouter() ya da app.dispose() ile bitmişse reason 'restart' ya da 'dispose' olur. run()'dan önce çağrılırsa hiçbir şey yapmaz ve undefined döndürür.

app.dispose() router'ı da serbest bırakır (dinleyiciler, keepAlive önbelleği, yığındaki sayfalar) ve adresi yalnız router onu kurmuşsa geri alır: history kipinde history.replaceState ile / yapar, hash/file kipinde yalnız # kısmını siler (belgenin yolu ve sorgusu kalır), shell kipinde ve router yokken adrese dokunmaz; geçmişe kayıt eklenmez. Route sayfaları, RouterView, kabuk ve ardından servisler serbest bırakılınca çözülen bir promise döndürür; run()'a verilen host elemanı sayfada kalır, aynı host yeni bir run() ile kullanılabilir.

Sonraki adım

Mobil tarzı geri gezinmesi için Yığın Gezinmesi bölümüne geçin.