Skip to content

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 ​

tsx
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:

ts
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):

AlanTürAçıklama
stateReadonlySignal<T | null>Elde edilen veri; yükleme sırasında veya hata sonrasında ise null
pendingReadonlySignal<boolean>Veri getirme işlemi devam ederken true değerini alır
errorReadonlySignal<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.

tsx
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:

İşleyiciAçı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)
tsx
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çenekTürAçıklama
initialTBaşlangıç ​​verisi. İlk render sırasında pending değerini false tutar (Suspense geri dönüşü/fallback gösterilmez)
timeoutnumberBelirtilen milisaniye (ms) sonunda veri çekme işlemini iptal eder ve kaynağı başarısız olarak işaretler
keystringÖnbelleğe alma ve farklı tüketiciler arasında paylaşım için kullanılan benzersiz kimlik
keepPreviousStatebooleanYeniden 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:

tsx
const [user] = resource(fetchUser, { initial: cachedUser });

timeout ​

tsx
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:

tsx
const [a] = resource(fetchUser, { key: 'user' });
const [b] = resource(fetchUser, { key: 'user' }); // shares the cache entry

invalidate() 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 ​

tsx
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:

ts
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 AbortController oluş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 bir AbortController oluş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:

ts
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:

tsx
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:

tsx
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 script
  • preload() öncesinde state.value değeri null, pending ise true durumundadı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 ​

tsx
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>
    );
};

Apache Lisansı 2.0 altında yayınlanmıştır