Kaynaklar (Resources)
resource, PreffX'in asenkron veri ilkelidir (primitive). Bir getiriciyi (asenkron bir fonksiyon) sarmalar ve veri getirme yaşam döngüsünü kontrol etmek için reaktif durumları (state, error, pending) ve çeşitli işleyicileri (refetch, mutate, cancel, invalidate) kullanıma sunar.
Kaynaklar izomorfiktir (isomorphic); yani aynı bileşen kodu, herhangi bir dallanmaya gerek kalmaksızın hem sunucuda (SSR ön yükleme) hem de istemcide (hidratasyon + reaktivite) çalışır.
Temel kullanım
import type { PC } from 'preffx';
export const UserCard: PC = (_props, { resource, computed }) => {
const [user] = resource(async () => {
const res = await fetch('/api/user');
return res.json();
});
const display = computed(() => user.state.value?.name ?? 'loading');
return (
<article className="user">
<h1>{display}</h1>
</article>
);
};resource(fetcher) bir demet (tuple) döndürür:
const [res, handlers] = resource(fetcher, options?);Durum (State)
İlk öğe olan res, üç sinyal içeren reaktif bir anlık görüntüdür (snapshot):
| Alan | Tür | Açıklama |
|---|---|---|
state | ReadonlySignal<T | null> | Elde edilen veri; yükleme sırasında veya hata sonrasında ise null |
pending | ReadonlySignal<boolean> | Veri getirme işlemi devam ederken true değerini alır |
error | ReadonlySignal<E | null> | Oluşan son hata; hata yoksa null |
state ve error bilinçli olarak birbirinden ayrı tutulmuştur: hata, veri durumunun bir parçası değildir. Bu yaklaşım, söz konusu iki kavramın birbirinden bağımsız kalmasını sağlar.
const [res] = resource<string, ApiError>(fetchUser);
const view = computed(() => {
if (res.pending.value) return 'loading…';
if (res.error.value) return `Error: ${res.error.value.message}`;
return res.state.value;
});Hata türü E, fetcher işlevinin döndürdüğü hata türünden çıkarılır veya açıkça belirtilebilir: resource<string, ApiError>(fetcher).
İşleyiciler (Handlers)
İkinci öğe bir kontrol nesnesidir:
| İşleyici | Açıklama |
|---|---|
refetch() | fetcher işlevini yeniden çalıştırır (önce devam eden bir istek varsa onu iptal eder) |
mutate(v) | fetcher işlevini tekrar çalıştırmadan, elde edilen değeri iyimser (optimistic) bir şekilde günceller |
cancel() | Devam eden isteği iptal eder ve pending durumunu sıfırlar. Önbelleğe dokunmaz |
invalidate() | Bu kaynağın key değerine karşılık gelen önbellek kaydını geçersiz kılar (yalnızca açıkça tanımlanmış anahtarlar için) |
const [user, { refetch, mutate, cancel }] = resource(fetchUser);
return (
<div>
<button onClick={refetch}>Refresh</button>
<button onClick={() => mutate({ name: 'Optimistic' })}>Set</button>
<button onClick={cancel}>Cancel</button>
</div>
);Seçenekler
resource(fetcher, options?) şu seçenekleri kabul eder:
| Seçenek | Tür | Açıklama |
|---|---|---|
initial | T | Başlangıç verisi. İlk render sırasında pending değerini false tutar (Suspense geri dönüşü/fallback gösterilmez) |
timeout | number | Belirtilen milisaniye (ms) sonunda veri çekme işlemini iptal eder ve kaynağı başarısız olarak işaretler |
key | string | Önbelleğe alma ve farklı tüketiciler arasında paylaşım için kullanılan benzersiz kimlik |
keepPreviousState | boolean | Yeniden veri çekme veya bağımlılıkların tekrar çalıştırılması sürecinde önceki state değerini korur |
initial
initial sağlandığında veri halihazırda mevcut demektir; bu nedenle pending başlangıçta false değerini alır ve Suspense herhangi bir geri dönüş (fallback) göstermez:
const [user] = resource(fetchUser, { initial: cachedUser });timeout
const [user] = resource(fetchUser, { timeout: 5000 });Zaman aşımı durumunda, devam eden istek iptal edilir ve error değeri, "Kaynak zaman aşımına uğradı" (Resource timed out) şeklinde bir hata ile ayarlanır.
key ve önbelleğe alma
Belirgin anahtarlar (keys), tüketiciler arasında paylaşılan, kök (root) bazlı bir önbellek mekanizması sağlar. Aynı key değerini kullanan iki bileşen, tek bir state/error kaydını paylaşır ve veriyi yalnızca bir kez getirir:
const [a] = resource(fetchUser, { key: 'user' });
const [b] = resource(fetchUser, { key: 'user' }); // shares the cache entryinvalidate() söz konusu girdiyi temizler, böylece bir sonraki tüketici veriyi yeniden getirir. Konumsal anahtarlar (yani bir key parametresi içermeyenler) önbelleğe alma sürecine dahil edilmez.
keepPreviousState
const [user, { refetch }] = resource(fetchUser, { keepPreviousState: true });Veri yeniden getirilirken (refetch), önceki state görünür kalmaya devam eder ("stale-while-revalidate" yaklaşımı) ve yalnızca error veya pending durumları güncellenir.
Abort signal
Veri getirme işlevi (fetcher), ilk argümanı olarak her zaman bir AbortSignal alır:
resource(async (signal) => {
const res = await fetch('/api/data', { signal });
return res.json();
});Bu durum hem sunucu hem de istemci tarafında tutarlıdır:
- İstemci (Client) — her çalıştırma için gerçek bir
AbortControlleroluşturulur.refetch(),cancel(), zaman aşımı veya bileşenin kaldırılması (unmount) durumlarında bu kontrolcü üzerinden işlem iptal edilir. Sinyal iptal edildiğinde, ne veri ne de hata durumu uygulanır. - Sunucu (SSR) —
preload()aşamasında her kaynak için gerçek birAbortControlleroluşturulur. Sinyal iptal edilirse, istemci tarafındaki işleyişle uyumlu olacak şekilde, ne veri ne de hata durumu uygulanır.
Not: Sinyalin iptal edilmesi (abort), isteği sonlandırır; ancak bu durum bir hata (error) oluştuğu anlamına gelmez. Bir başarısızlığı (örneğin "yetki yok" durumu gibi) bildirmek için, bunun yerine fetcher fonksiyonu içerisinden bir hata fırlatın:
resource(async () => {
if (!hasPermission()) throw new ForbiddenError('no access');
return fetchData();
});Bağımlılık odaklı yeniden veri çekme
Eğer veri çekici (fetcher), kendi gövdesi içerisinde bir sinyali okursa, söz konusu sinyal değiştiğinde kaynak (resource) otomatik olarak yeniden çalıştırılır:
const userId = signal(1);
const [user] = resource(async () => {
return fetchUser(userId.value); // re-runs when userId changes
});Sunucu Tarafında İşleme (SSR)
Sunucuda resource, bir yan etki (effect) çalıştırmaz; bunun yerine, getirme işlevini (fetcher) preload() için kaydeder. Aynı bileşen hiçbir değişikliğe gerek kalmadan çalışır:
import { createRoot } from 'preffx/server';
const server = createRoot();
const { preload, serialize } = server.renderToString(App, {});
await preload(); // resolve every registered resource
const html = serialize(); // markup + preload data scriptpreload()öncesindestate.valuedeğerinull,pendingisetruedurumundadır.preload()sonrasında, elde edilen değer işaretlemeye (markup) dahil edilir ve ön yükleme (preload) veri betiğine yazılır.- İstemci tarafında, "hydration" (hidrasyon) sürecinde kaynak, önceden yüklenmiş değeri okur ve veri getirme (fetch) işlemini atlar.
Tam örnek
import type { PC } from 'preffx';
type User = { id: number; name: string };
const UserCard: PC = (_props, { resource, computed }) => {
const [user, { refetch }] = resource<User>(
async (signal) => {
const res = await fetch('/api/user', { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{ timeout: 5000 }
);
const view = computed(() => {
if (user.pending.value) return 'Loading…';
if (user.error.value) return `Error: ${user.error.value.message}`;
return user.state.value?.name;
});
return (
<div>
<p>{view}</p>
<button onClick={refetch}>Refresh</button>
</div>
);
};