Tüm yazılara dön
·4 dk okuma

React'ta asenkron arayüzleri durum makinesi gibi tasarlamak

Bir React ekranı sunucudan veri aldığında ilk tasarım çoğu zaman birkaç boolean ile başlar: isLoading, hasError, belki bir de isSaving. Ekran büyüdükçe bu bayraklar birbiriyle çelişmeye başlar. Veri görünürken arka planda yenileniyor mu? İlk istek mi başarısız oldu, yoksa kullanıcı mevcut veriyi düzenlerken mi hata oluştu? Boş sonuç, izin yokluğu ve geçici ağ hatası aynı görünmeli mi?

Bu soruların zorlaşması bir bileşen sorunu değildir. Asıl sorun, arayüzün geçebileceği durumların açıkça modellenmemesidir. Durum makinesi yaklaşımı, kullanıcıya görünen olası durumları ve bu durumlar arasındaki geçişleri bir sözleşme olarak ele alır. Amaç akademik bir diyagram üretmek değil; imkânsız kombinasyonları koddan çıkarmak ve kullanıcı deneyimini öngörülebilir hale getirmektir.

Boolean bayraklar neden yetersiz kalır?

Bağımsız bayraklar bir araya geldiğinde uygulama mantıksız görünen kombinasyonlara izin verir. Örneğin isLoading: true, data: [] ve error aynı anda bulunabilir. Bunun hangi ekrana karşılık geldiği belirsizdir. Bir geliştirici spinner gösterirken diğeri hata paneli gösterebilir; üçüncüsü boş durum bileşenini render edebilir.

Sorun yalnızca görünüm değildir. Her yeni bayrak test edilecek kombinasyon sayısını artırır. Üç boolean sekiz ihtimal, beş boolean otuz iki ihtimal üretir. Bunların çoğu ürün açısından anlamlı değildir ama kod bunları mümkün sayar.

Bunun yerine durumları birbirini dışlayan isimli seçenekler olarak yazmak daha güvenlidir:

type ResourceState<T> =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "ready"; data: T; isRefreshing: boolean }
  | { kind: "empty" }
  | { kind: "error"; message: string; canRetry: boolean };

Bu modelde bir kaynak hem empty hem ready olamaz. “Veri ekranda, arka planda yenileniyor” durumu da kaybolmaz; ready durumunun açık bir niteliğidir.

Önce kullanıcıya görünen durumları listeleyin

Kod yazmadan önce ekranın yolculuğunu metinle anlatmak faydalıdır. Bir liste sayfası için şu durumlar gerekebilir:

  • İlk açılışta içerik henüz yokken yüklenme,
  • Başarılı ve dolu sonuç,
  • Başarılı fakat boş sonuç,
  • Mevcut veri görünürken sessiz yenileme,
  • Geri denenebilir hata,
  • Yetki gerektiren veya erişimin kaldırıldığı durum,
  • Kullanıcının başlattığı bir kaydetme işlemi.

Bu liste, “boş durum”un hata olmadığını ve yenilemenin ilk yükleme gibi davranmaması gerektiğini ortaya çıkarır. Kullanıcının her durumda yapabileceği bir sonraki eylemi de not etmek gerekir: beklemek, yeniden denemek, filtreyi kaldırmak, giriş yapmak ya da düzenlemeye devam etmek.

Durumları ürün diliyle adlandırmak önemlidir. phase2 veya isBusy yerine refreshing, permissionDenied ya da savingDraft gibi adlar, hem tasarım hem test konuşmasını kolaylaştırır.

Geçişleri olaylarla tanımlayın

Bir durum makinesi yalnızca durum listesi değildir; hangi olayın hangi geçişe izin verdiğini de söyler. Örneğin loading durumunda gelen başarılı yanıt, sonuç doluysa ready, boşsa empty durumuna geçer. ready durumundaki yenileme isteği, mevcut veriyi silmeden isRefreshing: true yapar. Yenileme başarısız olursa eski veri korunur ve kullanıcıya küçük bir geri bildirim gösterilir.

Bu ayrım özellikle ağ gecikmesinde değerlidir. Kullanıcı zaten gördüğü listeyi tekrar görmek için tam ekran spinner beklememelidir. Aynı şekilde eski bir isteğin geç gelen yanıtı, daha yeni bir filtre sonucunun üzerine yazmamalıdır. İstek kimliği, iptal sinyali veya güncel parametre kontrolü; yalnızca geçerli yanıtın durumu değiştirmesini sağlar.

Bu kurallar bir reducer, bir custom hook veya veri getirme kütüphanesinin durum nesnesiyle uygulanabilir. Araçtan bağımsız olarak kritik nokta, geçişlerin tek yerde okunabilir olmasıdır.

Kaynak durumu ile mutasyon durumunu ayırın

Listeyi yüklemek ile bir kaydı kaydetmek aynı yaşam döngüsü değildir. Tek bir isLoading bayrağı ikisini de temsil ettiğinde, kullanıcı bir formu gönderirken sayfanın tamamı gereksizce kilitlenebilir. Bunun yerine kaynak durumu ve kullanıcı eylemleri ayrı modellenmelidir.

Örneğin profil verisi ready kalırken, “kaydet” düğmesi submitting, succeeded veya failed durumundan geçebilir. Hata form alanına aitse alanın yanında gösterilir; ağ genelinde bir sorun varsa form verisi korunur ve tekrar deneme sunulur. Bu tasarım, hatanın bağlamını kullanıcıdan saklamaz.

İyimser güncelleme kullanılıyorsa geri alma yolu da geçiş modelinin parçası olmalıdır. Kullanıcı beğeni eklediğinde sayı anında artabilir, ancak istek reddedilirse önceki değer geri gelir ve gerekirse açıklama görünür. Başarı varsayımı, başarısızlık senaryosunu ortadan kaldırmaz.

Testi ekran görüntüsünden geçişlere taşıyın

Durum makinesi yaklaşımının en pratik getirisi testtedir. Her durumu rastgele UI koşullarından üretmek yerine, “şu olaydan sonra hangi durum beklenir?” sorusunu test edebilirsiniz. Boş sonuç, yeniden deneme, ardışık iki yenileme ve geç gelen yanıt gibi senaryolar bileşen ağacından bağımsız test edilebilir.

Bileşen testleri yine değerlidir; ancak onlar durumun kullanıcıya nasıl göründüğünü doğrulamalıdır. Geçiş testi ise durumun doğru olup olmadığını kanıtlar. Bu ayrım, kullanıcıların nadiren gördüğü ama hata anında kritik olan akışları güvence altına alır.

Her ekranın tam bir makineye ihtiyacı yoktur

Durum makinesi düşüncesi, her buton için büyük bir kütüphane veya görsel şema kullanmak anlamına gelmez. Basit bir etkileşimde iyi adlandırılmış iki durum yeterli olabilir. Karmaşıklık; eşzamanlı istekler, geri alma, taslak, yetki, çok adımlı form veya gerçek zamanlı güncellemeler eklendiğinde ortaya çıkar.

Ölçüt şudur: Kullanıcıya açıklaması zor bir ekran durumuyla karşılaşılıyor mu? Cevap evetse, yeni bir boolean eklemek yerine durumları ve geçişleri isimlendirmek genellikle daha iyi çözümdür. Arayüz, ağın belirsizliğini tamamen yok edemez; fakat belirsizliği kullanıcı için anlaşılır, ekip için de test edilebilir hale getirebilir.