Takip et

React-Async Kütüphanesi ile Deklaratif Veri Çekme: Modern React Uygulamalarında Asenkron İşlemleri Yönetmek

React-Async Kütüphanesi ile Deklaratif Veri Çekme: Modern React Uygulamalarında Asenkron İşlemleri Yönetmek React uygulamaları geliştirirken

React-Async Kütüphanesi ile Deklaratif Veri Çekme: Modern React Uygulamalarında Asenkron İşlemleri Yönetmek

React uygulamaları geliştirirken karşılaşılan en yaygın ve bazen en karmaşık görevlerden biri, dış kaynaklardan veri çekme işlemidir. API çağrıları yapmak, yükleme durumlarını yönetmek, hataları ele almak ve verileri bileşenler arasında doğru bir şekilde senkronize etmek, uygulamanın genel performansını ve kullanıcı deneyimini doğrudan etkiler. Geleneksel olarak, bu tür asenkron işlemler useEffect hook’u ve useState hook’u kombinasyonuyla manuel olarak yönetilir. Ancak bu yaklaşım, özellikle karmaşık senaryolarda (örneğin, bağımlılıkların değişmesiyle yeniden çekme, yarış koşulları, isteği iptal etme, önbelleğe alma) hızlıca karmaşık, hataya açık ve okunması zor bir hal alabilir.

İşte tam bu noktada, React-Async gibi kütüphaneler devreye girerek asenkron işlemleri daha deklaratif, öngörülebilir ve yönetilebilir bir şekilde ele almamızı sağlar. React-Async, React’in doğasına uygun, bileşen tabanlı bir yaklaşımla, promise tabanlı asenkron işlemleri React bileşenlerinizin içinde kolayca yönetmenize olanak tanıyan hafif ve esnek bir kütüphanedir. Bu makalede, React-Async kütüphanesinin temel prensiplerini, sunduğu özellikleri, kullanım senaryolarını ve modern React uygulamalarında nasıl güçlü bir araç olabileceğini derinlemesine inceleyeceğiz.

Neden Deklaratif Veri Çekme?

React, kullanıcı arayüzlerini deklaratif bir şekilde tanımlama felsefesi üzerine kurulmuştur. Bu, uygulamanızın belirli bir anda nasıl görünmesi gerektiğini “nasıl” değil, “ne” olması gerektiğini açıklamanız anlamına gelir. Ancak veri çekme gibi yan etkiler (side effects) genellikle imperatif (emredici) bir şekilde yönetilir. Örneğin, useEffect içinde bir fetch çağrısı yapmak, belirli bir olay zincirini (isteği başlat, cevabı bekle, durumu güncelle) adım adım tanımlar.

Bu imperatif yaklaşımın bazı dezavantajları vardır:

* Karmaşıklık: Yükleme durumları, hata durumları, başarılı durumlar için ayrı ayrı useState tanımları ve bunların useEffect içinde manuel olarak güncellenmesi gerekir.
* Yarış Koşulları: useEffect bağımlılıkları değiştiğinde veya bileşen demonte edildiğinde devam eden bir isteğin sonucu, güncel olmayan veya hatalı bir duruma yol açabilir. Bu durumları önlemek için manuel temizleme (cleanup) fonksiyonları yazmak zorunludur.
* Tekrar Kullanılabilirlik: Aynı veri çekme mantığını farklı bileşenlerde kullanmak istediğinizde, genellikle özel hook’lar yazmak veya mantığı kopyalamak zorunda kalırsınız.
* Okunabilirlik ve Bakım: Kod tabanı büyüdükçe, asenkron mantığı takip etmek ve hataları ayıklamak zorlaşır.

Deklaratif veri çekme, bu sorunlara çözüm sunar. React-Async gibi kütüphaneler, bir promise’ı veya bir asenkron fonksiyonu alır ve onun yaşam döngüsünü (beklemede, başarılı, reddedildi) otomatik olarak yönetir. Siz sadece bu durumların her birinde UI’ın nasıl görünmesi gerektiğini belirtirsiniz. Bu, kodunuzu daha okunabilir, daha az hataya açık ve daha sürdürülebilir hale getirir. React-Async, React’in deklaratif doğasıyla mükemmel bir uyum sağlayarak, asenkron işlemleri de UI gibi deklaratif bir şekilde yönetmemize olanak tanır.

React-Async’i Anlamak

React-Async, React bileşenleri içinde promise tabanlı asenkron işlemleri yönetmek için tasarlanmış esnek bir araçtır. Amacı, asenkron operasyonların durumlarını (yükleniyor, tamamlandı, hata) otomatik olarak izlemek ve bu durumlara göre UI’ı render etmek için basit ve deklaratif bir API sağlamaktır. Kütüphane, hem render props API’si aracılığıyla bileşeni hem de React Hooks API’si aracılığıyla useAsync ve useFetch hook’ları sunar, bu da onu modern React uygulamaları için oldukça uygun kılar.

React-Async‘in temel özellikleri şunlardır:

* Otomatik Durum Yönetimi: Bir promise’ın pending (beklemede), fulfilled (başarılı) ve rejected (reddedildi) durumlarını otomatik olarak izler ve ilgili bilgileri bileşene sağlar.
* Deklaratif API: Hem bileşeni hem de useAsync hook’u, asenkron işlemleri React’in deklaratif doğasına uygun bir şekilde tanımlamanıza olanak tanır.
* Esneklik: Herhangi bir promise tabanlı işlemi (veri çekme, dosya yükleme, zamanlayıcılar vb.) yönetebilir.
* Önbelleğe Alma (Caching): Aynı promise için tekrar tekrar API çağrısı yapmaktan kaçınmak için basit bir önbelleğe alma mekanizması sunar.
* Debounce ve Throttle: Aşırı API çağrılarını önlemek için istekleri geciktirme veya sınırlama yeteneği.
* Tekrar Deneme (Retries): Geçici ağ hataları gibi durumlarda istekleri otomatik olarak yeniden deneme.
* İptal Etme (Aborting): Devam eden asenkron işlemleri bileşen demonte edildiğinde veya bağımlılıklar değiştiğinde iptal etme mekanizması.
* Sunucu Taraflı İşleme (SSR) Desteği: Sunucu tarafında veri çekme ve istemci tarafında hidrasyon için kolay entegrasyon.
* TypeScript Desteği: Güçlü tür çıkarımı ve tip güvenliği sağlar.

Bu özellikler, React-Async‘i sadece basit veri çekme işlemleri için değil, aynı zamanda daha karmaşık asenkron akışları yönetmek için de güçlü bir araç haline getirir.

React-Async ile Başlangıç

React-Async‘i projenize dahil etmek oldukça basittir.

Kurulum:

npm install react-async

veya

yarn add react-async

Temel Kullanım: Bileşeni ile

bileşeni, render props desenini kullanarak asenkron işlemlerin durumunu yönetir. promiseFn prop’u, çözümlenmesini istediğiniz bir promise döndüren bir fonksiyon alır. Bileşenin çocukları ise, farklı durumları (yükleniyor, başarılı, hata) işleyen fonksiyonlar veya doğrudan JSX olabilir.

import React from 'react';
import { Async } from 'react-async';

const fetchUsers = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  if (!response.ok) throw new Error('Kullanıcılar yüklenirken bir hata oluştu.');
  return response.json();
};

function UserList() {
  return (
    
      {({ data, error, isLoading }) => {
        if (isLoading) return 
Kullanıcılar yükleniyor...
; if (error) return
Hata: {error.message}
; if (data) return (

Kullanıcı Listesi

    {data.map(user => (
  • {user.name}
  • ))}
); return null; // Başlangıç durumu veya veri yoksa }}
); } export default UserList;

Bu örnekte, fetchUsers fonksiyonu bir promise döndürür. bileşeni bu promise’ı otomatik olarak çalıştırır ve durumunu izler. isLoading, error ve data gibi render prop’ları aracılığıyla, uygulamanın mevcut durumuna göre farklı UI elemanları render edebiliriz.

Temel Kullanım: useAsync Hook’u ile

React Hooks, bileşenler arasında durum bilgisi olan mantığı yeniden kullanmayı kolaylaştırdı. useAsync hook’u, bileşeninin sunduğu tüm özellikleri hook tabanlı bir API ile sunar.

import React from 'react';
import { useAsync } from 'react-async';

const fetchPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  if (!response.ok) throw new Error('Gönderiler yüklenirken bir hata oluştu.');
  return response.json();
};

function PostList() {
  const { data, error, isLoading, reload } = useAsync({ promiseFn: fetchPosts });

  if (isLoading) return 
Gönderiler yükleniyor...
; if (error) return
Hata: {error.message}
; if (data) return (

Gönderi Listesi

    {data.map(post => (
  • {post.title}

    {post.body}

  • ))}
); return null; } export default PostList;

useAsync hook’u, promiseFn prop’u yerine bir konfigürasyon nesnesi alır. Bu nesneye promiseFn (veya deferFn gibi diğer seçenekler) tanımlanır. Hook, data, error, isLoading, isResolved, isRejected, reload, run, cancel gibi bir dizi değer döndürür. reload fonksiyonu, promise’ı yeniden çalıştırmak için kullanılabilir, bu da kullanıcıların veriyi manuel olarak yenilemesine olanak tanır.

Gelişmiş Özellikler ve En İyi Uygulamalar

React-Async, temel veri çekmenin ötesinde birçok gelişmiş özellik sunar.

1. Durum Yönetimi

React-Async, asenkron işlemin farklı aşamalarını temsil eden dört ana durum sağlar:

* initial: Promise henüz başlamadı veya sıfırlandı.
* pending (isLoading): Promise çözümleniyor (veri çekiliyor).
* fulfilled (isResolved): Promise başarıyla çözümlendi (veri geldi).
* rejected (isRejected): Promise bir hata ile reddedildi.

Bu durumlar, bileşeninin render prop’ları veya useAsync hook’unun döndürdüğü değerler aracılığıyla kolayca erişilebilir. Bu sayede, kullanıcıya anlamlı geri bildirimler (yükleme göstergeleri, hata mesajları vb.) sunmak çok kolaylaşır.

// useAsync örneği:
const { data, error, isLoading, isResolved, isRejected } = useAsync({ promiseFn: fetchData });

if (isLoading) return ;
if (isRejected) return ;
if (isResolved) return ;

2. Çalıştırmayı Erteleme (Deferring Execution)

Varsayılan olarak, promiseFn veya deferFn ile tanımlanan promise, bileşen monte edildiğinde otomatik olarak çalışır. Ancak bazen bir API çağrısını bir kullanıcı eylemine (örneğin, bir düğmeye tıklama) bağlamak isteyebilirsiniz. Bunun için deferFn prop’unu veya useAsync hook’unun run metodunu kullanırız.

import React from 'react';
import { useAsync } from 'react-async';

const searchAPI = async (query) => {
  if (!query) return [];
  const response = await fetch(https://api.example.com/search?q=${query});
  if (!response.ok) throw new Error('Arama sırasında bir hata oluştu.');
  return response.json();
};

function SearchComponent() {
  const [searchQuery, setSearchQuery] = React.useState('');
  const { data, error, isLoading, run } = useAsync({
    deferFn: searchAPI, // Otomatik çalışmaz
  });

  const handleSearch = () => {
    run(searchQuery); // Kullanıcı eylemiyle çalıştır
  };

  return (
    
setSearchQuery(e.target.value)} placeholder="Aramak için yazın..." /> {error &&
Hata: {error.message}
} {data && (
    {data.map((item) => (
  • {item.name}
  • ))}
)}
); }

Bu örnekte, searchAPI deferFn olarak tanımlandığı için bileşen ilk monte edildiğinde çalışmaz. run metodu, handleSearch fonksiyonu içinde çağrılarak arama işlemini başlatır ve searchQuery parametresi searchAPI fonksiyonuna iletilir.

3. Veri Bağımlılıkları ve Yeniden Çekme

Bir API çağrısının belirli bir bileşenin prop’larına veya durumuna bağlı olduğu yaygın bir senaryodur. Bu bağımlılıklar değiştiğinde verinin yeniden çekilmesi gerekebilir. React-Async, bu durumu watch prop’u (veya useAsync konfigürasyonunda watch seçeneği) ile ele alır.

import React from 'react';
import { useAsync } from 'react-async';

const fetchUserDetails = async (userId) => {
  if (!userId) return null;
  const response = await fetch(https://jsonplaceholder.typicode.com/users/${userId});
  if (!response.ok) throw new Error('Kullanıcı detayları yüklenirken bir hata oluştu.');
  return response.json();
};

function UserDetails({ userId }) {
  const { data, error, isLoading } = useAsync({
    promiseFn: fetchUserDetails,
    args: [userId], // Promise fonksiyonuna argüman olarak geçirilir
    watch: userId, // userId değiştiğinde yeniden çeker
  });

  if (isLoading) return 
Kullanıcı {userId} yükleniyor...
; if (error) return
Hata: {error.message}
; if (data) return (

Kullanıcı Detayları: {data.name}

Email: {data.email}

Telefon: {data.phone}

); return
Lütfen bir kullanıcı seçin.
; } function App() { const [selectedUserId, setSelectedUserId] = React.useState(1); return (
); }

watch: userId sayesinde, userId prop’u her değiştiğinde fetchUserDetails fonksiyonu otomatik olarak yeniden çalıştırılır ve yeni veriler çekilir. args seçeneği ise, promiseFn‘e hangi argümanların iletileceğini belirtir.

4. Önbelleğe Alma ve Debounce

* Önbelleğe Alma (Caching): React-Async, aynı promiseFn ve watch değerleri için önceki başarılı sonuçları önbelleğe alabilir. Bu, aynı verinin tekrar tekrar çekilmesini önler. cache prop’u ile kontrol edilir (true varsayılan olarak, false kapatır veya özel bir anahtar).

// cache: true (varsayılan) ile aynı promiseFn ve watch için tekrar çekmez
    
    // cache: false ile her zaman yeniden çeker
    
    // Özel bir anahtar ile önbellek kontrolü
    user-${userId}} />

* Debounce: Özellikle arama kutuları gibi hızlı kullanıcı girişlerinde, her tuş vuruşunda API çağrısı yapmak gereksiz yük oluşturabilir. debounce prop’u, belirli bir süre boyunca yeni bir istek gelmediğinde promise’ın çalışmasını sağlar.

import React from 'react';
    import { useAsync } from 'react-async';

    const searchProducts = async (query) => {
      if (!query) return [];
      const response = await fetch(https://api.example.com/products?q=${query});
      return response.json();
    };

    function ProductSearch() {
      const [searchTerm, setSearchTerm] = React.useState('');
      const { data, isLoading, error } = useAsync({
        deferFn: searchProducts,
        args: [searchTerm],
        watch: searchTerm,
        debounce: 500, // 500ms gecikme
      });

      return (
        
setSearchTerm(e.target.value)} placeholder="Ürün ara..." /> {isLoading &&
Aranıyor...
} {error &&
Hata: {error.message}
} {data && (
    {data.map((product) => (
  • {product.name}
  • ))}
)}
); }

Bu örnekte, searchTerm değiştiğinde searchProducts fonksiyonu hemen çalışmaz, 500 milisaniye beklenir. Bu süre içinde searchTerm tekrar değişirse, önceki bekleyen çağrı iptal edilir ve yeni bir 500ms’lik gecikme başlar.

5. Hata Yönetimi ve Tekrar Deneme (Retries)

Ağ hataları veya sunucu tarafındaki sorunlar nedeniyle API çağrıları başarısız olabilir. React-Async, bu durumları ele almak için güçlü mekanizmalar sunar. error durumuyla birlikte, retryFn prop’u ile otomatik tekrar deneme mekanizması tanımlayabiliriz.

import React from 'react';
import { useAsync } from 'react-async';

let attemptCount = 0; // Deneme sayısını takip etmek için
const unreliableFetch = async () => {
  attemptCount++;
  if (attemptCount < 3) {
    throw new Error(Yükleme başarısız oldu (Deneme: ${attemptCount}));
  }
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  return response.json();
};

function UnreliableData() {
  const { data, error, isLoading, reload, isRejected } = useAsync({
    promiseFn: unreliableFetch,
    retryFn: (failCount, error) => {
      // İlk 3 denemeden sonra dur
      if (failCount >= 3) return false;
      // 1 saniye sonra tekrar dene
      return 1000;
    },
    // persist: true, hata durumunda bile önceki veriyi tutar
    // bu sayede kullanıcı eski veriyi görmeye devam ederken tekrar denemeler yapılır
    persist: true,
  });

  if (isLoading) return 
Veri yükleniyor... (Deneme {attemptCount})
; if (isRejected) return (
Hata: {error.message}{' '}
); if (data) return
Veri başarıyla yüklendi: {data.title}
; return null; }

Bu örnekte, unreliableFetch fonksiyonu ilk iki çağrıda hata fırlatır. retryFn sayesinde, React-Async otomatik olarak 1 saniye bekleyip isteği yeniden deneyecektir. failCount parametresi kaç kez başarısız olunduğunu gösterir. persist: true prop’u, bir hata oluşsa bile data state’inin son başarılı değeri tutmasını sağlar, bu da kullanıcı deneyimini iyileştirebilir.

6. İstekleri İptal Etme (Aborting Requests)

Özellikle tek sayfa uygulamalarında, kullanıcılar bir sayfadan diğerine hızla geçebilir veya bir işlemi iptal edebilir. Bu durumlarda, devam eden API çağrılarının iptal edilmesi hem gereksiz ağ trafiğini önler hem de yarış koşullarını engeller. useAsync hook’unun döndürdüğü cancel metodu, devam eden bir promise’ı iptal etmek için kullanılabilir. React-Async, dahili olarak AbortController API’sini kullanır.

import React from 'react';
import { useAsync } from 'react-async';

const longRunningFetch = async (signal) => {
  const response = await fetch('https://api.example.com/long-task', { signal });
  if (!response.ok) throw new Error('Uzun süren işlem başarısız oldu.');
  return response.json();
};

function LongTaskComponent() {
  const { data, error, isLoading, run, cancel, isPending } = useAsync({
    deferFn: longRunningFetch,
  });

  React.useEffect(() => {
    // Bileşen demonte edildiğinde veya başka bir nedenle iptal etmek istediğinizde
    return () => {
      cancel();
    };
  }, [cancel]);

  return (
    
{isLoading &&
İşlem sürüyor...
} {error &&
Hata: {error.message}
} {data &&
Sonuç: {JSON.stringify(data)}
}
); }

deferFn olarak tanımlanan longRunningFetch fonksiyonuna otomatik olarak bir AbortSignal objesi geçirilir. Bu sinyal, fetch API’sine iletilerek isteğin iptal edilebilir olmasını sağlar. cancel() çağrıldığında, sinyal tetiklenir ve fetch isteği iptal edilir.

7. useFetch ile Entegrasyon

React-Async, fetch API’sini kullanarak veri çekme işlemleri için özel olarak tasarlanmış useFetch adında bir hook da sunar. Bu hook, useAsync üzerine inşa edilmiştir ve fetch API’sinin tipik kullanım durumlarını basitleştirir.

import React from 'react';
import { useFetch } from 'react-async';

function GitHubUser({ username }) {
  const { data, error, isLoading } = useFetch(https://api.github.com/users/${username}, {
    watch: username, // username değiştiğinde yeniden çek
  });

  if (isLoading) return 
Kullanıcı {username} yükleniyor...
; if (error) return
Hata: {error.message}
; if (data) return (

{data.name} ({data.login})

{data.login}

Takipçi: {data.followers}

Repo Sayısı: {data.public_repos}

); return null; } function AppWithGitHub() { const [user, setUser] = React.useState('gaearon'); return (
setUser(e.target.value)} placeholder="GitHub kullanıcı adı" />
); }

useFetch hook’u, doğrudan bir URL ve isteğe bağlı RequestInit (fetch API seçenekleri) nesnesi alır. watch seçeneği, URL’deki bağımlılıkları izlemek için hala kullanılabilir.

8. Sunucu Taraflı İşleme (SSR)

React-Async, sunucu taraflı işleme (Server-Side Rendering – SSR) senaryolarında da kullanılabilir. Bu, uygulamanın ilk yüklemesinde daha iyi performans ve SEO sağlamak için sunucuda veri çekme ve HTML’i önceden oluşturma anlamına gelir.

React-Async ile SSR yapmak için genellikle şu adımlar izlenir:

1. Sunucuda, bileşen ağacınızı render etmeden önce Async.preload() veya Async.initialState() gibi yöntemler kullanarak gerekli verileri önceden çekin.
2. Çekilen verileri initialState olarak istemciye gönderin (genellikle

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.