Türkçe

React `use` hook'u için kapsamlı bir rehber. Promise ve Context kullanımındaki etkilerini, kaynak tüketimi, performans ve en iyi uygulamalarıyla derinlemesine inceleyin.

React'in `use` Hook'unu Anlamak: Promise'ler, Context ve Kaynak Yönetimine Derinlemesine Bir Bakış

React ekosistemi, geliştirici deneyimini sürekli iyileştirerek ve web'de nelerin mümkün olduğunun sınırlarını zorlayarak daimi bir evrim halindedir. Class'lardan Hook'lara kadar her büyük değişim, kullanıcı arayüzleri oluşturma şeklimizi temelden değiştirmiştir. Bugün, aldatıcı derecede basit görünen bir fonksiyonun müjdelediği başka bir dönüşümün eşiğindeyiz: `use` hook'u.

Yıllardır geliştiriciler, asenkron operasyonların ve durum yönetiminin karmaşıklığıyla boğuştu. Veri çekmek genellikle `useEffect`, `useState` ve yükleme/hata durumlarının birbirine geçmiş bir ağı anlamına geliyordu. Context'i kullanmak güçlü olsa da, her tüketici bileşende yeniden render'ları tetikleme gibi önemli bir performans dezavantajıyla geliyordu. `use` hook'u, React'in bu köklü zorluklara getirdiği zarif bir yanıttır.

Bu kapsamlı rehber, küresel bir profesyonel React geliştirici kitlesi için tasarlanmıştır. `use` hook'unun derinliklerine inecek, mekaniklerini parçalara ayıracak ve iki temel başlangıç kullanım senaryosunu keşfedeceğiz: Promise'lerin içindeki değeri almak ve Context'ten okuma yapmak. Daha da önemlisi, kaynak tüketimi, performans ve uygulama mimarisi üzerindeki derin etkilerini analiz edeceğiz. React uygulamalarınızda asenkron mantığı ve durumu nasıl ele aldığınızı yeniden düşünmeye hazır olun.

Temel Bir Değişim: `use` Hook'unu Farklı Kılan Nedir?

Promise'lere ve Context'e dalmadan önce, `use`'un neden bu kadar devrimsel olduğunu anlamak çok önemlidir. Yıllardır React geliştiricileri katı Hook Kuralları altında çalıştılar:

Bu kurallar, `useState` ve `useEffect` gibi geleneksel Hook'ların durumlarını korumak için her render sırasında tutarlı bir çağrı sırasına dayanması nedeniyle mevcuttur. `use` hook'u bu emsali yıkıyor. `use`'u koşulların (`if`/`else`), döngülerin (`for`/`map`) ve hatta erken `return` ifadelerinin içinde çağırabilirsiniz.

Bu sadece küçük bir ayarlama değil; bu bir paradigma değişimidir. Kaynakları tüketmek için daha esnek ve sezgisel bir yol sağlayarak, statik, üst düzey bir abonelik modelinden dinamik, isteğe bağlı bir tüketim modeline geçiş yapar. Teorik olarak çeşitli kaynak türleriyle çalışabilse de, ilk uygulaması React geliştirmesindeki en yaygın iki sıkıntı noktasına odaklanmaktadır: Promise'ler ve Context.

Temel Konsept: Değerlerin Sarmalını Açmak

Özünde, `use` hook'u bir kaynaktan bir değeri "sarmalını açmak" (unwrap) için tasarlanmıştır. Şöyle düşünebilirsiniz:

Bu iki güçlü yeteneği ayrıntılı olarak inceleyelim.

Asenkron Operasyonlarda Uzmanlaşmak: Promise'ler ile `use`

Veri çekme, modern web uygulamalarının can damarıdır. React'teki geleneksel yaklaşım işlevseldi ancak genellikle ayrıntılı ve gizli hatalara açıktı.

Eski Yöntem: `useEffect` ve `useState` Dansı

Kullanıcı verilerini çeken basit bir bileşen düşünün. Standart model şuna benzer:


import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;
    const fetchUser = async () => {
      try {
        setIsLoading(true);
        const response = await fetch(`https://api.example.com/users/${userId}`);
        if (!response.ok) {
          throw new Error('Ağ yanıtı uygun değildi');
        }
        const data = await response.json();
        if (isMounted) {
          setUser(data);
        }
      } catch (err) {
        if (isMounted) {
          setError(err);
        }
      } finally {
        if (isMounted) {
          setIsLoading(false);
        }
      }
    };

    fetchUser();

    return () => {
      isMounted = false;
    };
  }, [userId]);

  if (isLoading) {
    return <p>Profil yükleniyor...</p>;
  }

  if (error) {
    return <p>Hata: {error.message}</p>;
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <p>E-posta: {user.email}</p>
    </div>
  );
}

Bu kod oldukça fazla standart kod (boilerplate) içeriyor. Üç ayrı durumu (`user`, `isLoading`, `error`) manuel olarak yönetmemiz gerekiyor ve bir `isMounted` bayrağı kullanarak yarış koşulları (race conditions) ve temizlik konusunda dikkatli olmalıyız. Özel hook'lar bunu soyutlayabilse de, altta yatan karmaşıklık devam eder.

Yeni Yöntem: `use` ile Zarif Asenkronluk

`use` hook'u, React Suspense ile birleştiğinde, tüm bu süreci önemli ölçüde basitleştirir. Senkron kod gibi okunan asenkron kod yazmamızı sağlar.

Aynı bileşenin `use` ile nasıl yazılabileceği aşağıda verilmiştir:


// Bu bileşeni bir <Suspense> ve <ErrorBoundary> içine sarmalısınız
import { use } from 'react';
import { fetchUser } from './api'; // Bunun önbelleğe alınmış bir promise döndürdüğünü varsayın

function UserProfile({ userId }) {
  // `use` hook'u, promise çözümlenene kadar bileşeni askıya alacaktır
  const user = use(fetchUser(userId));

  // Kod buraya ulaştığında, promise çözümlenmiş ve `user` veriye sahip demektir.
  // Bileşenin kendisinde isLoading veya hata durumlarına gerek yoktur.
  return (
    <div>
      <h1>{user.name}</h1>
      <p>E-posta: {user.email}</p>
    </div>
  );
}

Fark şaşırtıcı. Yükleme ve hata durumları bileşen mantığımızdan kayboldu. Peki perde arkasında ne oluyor?

  1. `UserProfile` ilk kez render edildiğinde, `use(fetchUser(userId))` çağrılır.
  2. `fetchUser` fonksiyonu bir ağ isteği başlatır ve bir Promise döndürür.
  3. `use` hook'u bu beklemedeki Promise'i alır ve bu bileşenin render işlemini askıya almak için React'in render motoruyla iletişim kurar.
  4. React, en yakın `` sınırını bulmak için bileşen ağacında yukarı doğru gider ve `fallback` arayüzünü (örneğin bir yükleme göstergesi) gösterir.
  5. Promise çözümlendiğinde, React `UserProfile`'ı yeniden render eder. Bu kez, `use` aynı Promise ile çağrıldığında, Promise'in çözümlenmiş bir değeri vardır. `use` bu değeri döndürür.
  6. Bileşenin render işlemi devam eder ve kullanıcının profili görüntülenir.
  7. Eğer Promise reddedilirse, `use` hatayı fırlatır. React bunu yakalar ve bir yedek hata arayüzü göstermek için ağaçta en yakın ``'e kadar gider.

Kaynak Tüketimi Derinlemesine Bakış: Önbellekleme Zorunluluğu

`use(fetchUser(userId))`'nin basitliği kritik bir ayrıntıyı gizler: her render'da yeni bir Promise oluşturmamalısınız. Eğer `fetchUser` fonksiyonumuz basitçe `() => fetch(...)` olsaydı ve onu doğrudan bileşen içinde çağırsaydık, her render denemesinde yeni bir ağ isteği oluşturarak sonsuz bir döngüye yol açardık. Bileşen askıya alınır, promise çözümlenir, React yeniden render eder, yeni bir promise oluşturulur ve tekrar askıya alınır.

Bu, `use`'u promise'lerle kullanırken kavranması gereken en önemli kaynak yönetimi konseptidir. Promise, yeniden render'lar arasında kararlı ve önbelleğe alınmış olmalıdır.

React, bu konuda yardımcı olmak için yeni bir `cache` fonksiyonu sunar. Sağlam bir veri çekme aracı oluşturalım:


// api.js
import { cache } from 'react';

export const fetchUser = cache(async (userId) => {
  console.log(`Kullanıcı için veri çekiliyor: ${userId}`);
  const response = await fetch(`https://api.example.com/users/${userId}`);
  if (!response.ok) {
    throw new Error('Kullanıcı verisi çekilemedi.');
  }
  return response.json();
});

React'ten gelen `cache` fonksiyonu, asenkron fonksiyonu memoize eder. `fetchUser(1)` çağrıldığında, veri çekme işlemini başlatır ve sonuçta ortaya çıkan Promise'i saklar. Eğer başka bir bileşen (veya aynı bileşen bir sonraki render'da) aynı render geçişi içinde tekrar `fetchUser(1)`'i çağırırsa, `cache` tam olarak aynı Promise nesnesini döndürerek gereksiz ağ isteklerini önler. Bu, veri çekme işlemini etkisiz (idempotent) ve `use` hook'u ile kullanımı güvenli hale getirir.

Bu, kaynak yönetiminde temel bir değişimdir. Veri çekme durumunu bileşen içinde yönetmek yerine, kaynağı (veri promise'ini) bileşenin dışında yönetiriz ve bileşen sadece onu tüketir.

Durum Yönetiminde Devrim: Context ile `use`

React Context, "prop drilling"den (prop'ları birçok bileşen katmanı boyunca aşağıya geçirme) kaçınmak için güçlü bir araçtır. Ancak, geleneksel uygulaması önemli bir performans dezavantajına sahiptir.

`useContext` İkilemi

`useContext` hook'u bir bileşeni bir context'e abone eder. Bu, context'in değeri her değiştiğinde, o context için `useContext` kullanan her bir bileşenin yeniden render edileceği anlamına gelir. Bu durum, bileşen context değerinin sadece küçük, değişmemiş bir parçasıyla ilgilense bile geçerlidir.

Hem kullanıcı bilgilerini hem de mevcut temayı tutan bir `SessionContext` düşünün:


// SessionContext.js
const SessionContext = createContext({
  user: null,
  theme: 'light',
  updateTheme: () => {},
});

// Sadece kullanıcıyı önemseyen bileşen
function WelcomeMessage() {
  const { user } = useContext(SessionContext);
  console.log('WelcomeMessage render ediliyor');
  return <p>Hoş geldiniz, {user?.name}!</p>;
}

// Sadece temayı önemseyen bileşen
function ThemeToggleButton() {
  const { theme, updateTheme } = useContext(SessionContext);
  console.log('ThemeToggleButton render ediliyor');
  return <button onClick={updateTheme}>{theme === 'light' ? 'koyu' : 'açık'} temaya geç</button>;
}

Bu senaryoda, kullanıcı `ThemeToggleButton`'a tıkladığında ve `updateTheme` çağrıldığında, tüm `SessionContext` değer nesnesi değiştirilir. Bu, `user` nesnesi değişmemiş olmasına rağmen hem `ThemeToggleButton`'ın HEM DE `WelcomeMessage`'ın yeniden render edilmesine neden olur. Yüzlerce context tüketicisi olan büyük bir uygulamada, bu ciddi performans sorunlarına yol açabilir.

Karşınızda `use(Context)`: Koşullu Tüketim

`use` hook'u bu soruna çığır açan bir çözüm sunar. Koşullu olarak çağrılabildiği için, bir bileşen yalnızca değeri gerçekten okuduğunda context'e bir abonelik kurar.

Bu gücü göstermek için bir bileşeni yeniden düzenleyelim:


function UserSettings({ userId }) {
  const { user, theme } = useContext(SessionContext); // Geleneksel yöntem: her zaman abone olur

  // Sadece mevcut giriş yapmış kullanıcı için tema ayarlarını gösterdiğimizi hayal edelim
  if (user?.id !== userId) {
    return <p>Yalnızca kendi ayarlarınızı görüntüleyebilirsiniz.</p>;
  }

  // Bu kısım sadece kullanıcı ID'si eşleşirse çalışır
  return <div>Mevcut tema: {theme}</div>;
}

`useContext` ile bu `UserSettings` bileşeni, `user.id !== userId` olsa ve tema bilgisi hiç gösterilmese bile, tema her değiştiğinde yeniden render edilir. Abonelik, en üst seviyede koşulsuz olarak kurulur.

Şimdi `use` versiyonunu görelim:


import { use } from 'react';

function UserSettings({ userId }) {
  // Önce kullanıcıyı oku. Bu kısmın maliyetsiz veya gerekli olduğunu varsayalım.
  const user = use(SessionContext).user;

  // Koşul sağlanmazsa, erken dönüş yaparız.
  // EN ÖNEMLİSİ, henüz temayı okumadık.
  if (user?.id !== userId) {
    return <p>Yalnızca kendi ayarlarınızı görüntüleyebilirsiniz.</p>;
  }

  // SADECE koşul sağlanırsa, temayı context'ten okuruz.
  // Context değişikliklerine abonelik burada, koşullu olarak oluşturulur.
  const theme = use(SessionContext).theme;

  return <div>Mevcut tema: {theme}</div>;
}

Bu, oyunun kurallarını değiştirir. Bu versiyonda, eğer `user.id` `userId` ile eşleşmezse, bileşen erken dönüş yapar. `const theme = use(SessionContext).theme;` satırı asla çalıştırılmaz. Bu nedenle, bu bileşen örneği `SessionContext`'e abone olmaz. Uygulamanın başka bir yerinde tema değiştirilirse, bu bileşen gereksiz yere yeniden render edilmez. Koşullu olarak context'ten okuma yaparak kendi kaynak tüketimini etkili bir şekilde optimize etmiştir.

Kaynak Tüketimi Analizi: Abonelik Modelleri

Context tüketimi için zihinsel model önemli ölçüde değişir:

Yeniden render'lar üzerindeki bu ince ayarlı kontrol, büyük ölçekli uygulamalarda performans optimizasyonu için güçlü bir araçtır. Geliştiricilerin, karmaşık memoizasyon (`React.memo`) veya durum seçici desenlerine başvurmadan, alakasız durum güncellemelerinden gerçekten izole edilmiş bileşenler oluşturmasına olanak tanır.

Kesişim Noktası: Context'teki Promise'ler ile `use`

`use`'un gerçek gücü, bu iki konsepti birleştirdiğimizde ortaya çıkar. Ya bir context sağlayıcısı veriyi doğrudan değil de, o veri için bir promise sağlarsa? Bu model, uygulama genelindeki veri kaynaklarını yönetmek için inanılmaz derecede kullanışlıdır.


// DataContext.js
import { createContext } from 'react';
import { fetchSomeGlobalData } from './api'; // Önbelleğe alınmış bir promise döndürür

// Context, verinin kendisini değil, bir promise sağlar.
export const GlobalDataContext = createContext(fetchSomeGlobalData());

// App.js
function App() {
  return (
    <GlobalDataContext.Provider value={fetchSomeGlobalData()}>
      <Suspense fallback={<h1>Uygulama yükleniyor...</h1>}>
        <Dashboard />
      </Suspense>
    </GlobalDataContext.Provider>
  );
}

// Dashboard.js
import { use } from 'react';
import { GlobalDataContext } from './DataContext';

function Dashboard() {
  // İlk `use` context'ten promise'i okur.
  const dataPromise = use(GlobalDataContext);

  // İkinci `use` promise'i açar, gerekirse askıya alır.
  const globalData = use(dataPromise);

  // Yukarıdaki iki satırı yazmanın daha kısa yolu:
  // const globalData = use(use(GlobalDataContext));

  return <h1>Hoş geldiniz, {globalData.userName}!</h1>;
}

`const globalData = use(use(GlobalDataContext));` ifadesini inceleyelim:

  1. `use(GlobalDataContext)`: İçteki çağrı önce çalışır. `GlobalDataContext`'ten değeri okur. Bizim kurulumumuzda bu değer, `fetchSomeGlobalData()` tarafından döndürülen bir promise'dir.
  2. `use(dataPromise)`: Dıştaki çağrı daha sonra bu promise'i alır. Tam olarak ilk bölümde gördüğümüz gibi davranır: eğer promise beklemedeyse `Dashboard` bileşenini askıya alır, reddedilirse hata fırlatır veya çözümlenmiş veriyi döndürür.

Bu model olağanüstü güçlüdür. Veri tüketen bileşenlerden veri çekme mantığını ayırırken, sorunsuz bir yükleme deneyimi için React'in yerleşik Suspense mekanizmasından yararlanır. Bileşenlerin verinin *nasıl* veya *ne zaman* çekildiğini bilmesine gerek yoktur; sadece onu isterler ve React gerisini düzenler.

Performans, Tuzaklar ve En İyi Uygulamalar

Her güçlü araç gibi, `use` hook'u da etkili bir şekilde kullanılabilmesi için anlayış ve disiplin gerektirir. İşte üretim uygulamaları için bazı önemli hususlar.

Performans Özeti

Kaçınılması Gereken Yaygın Tuzaklar

  1. Önbelleğe Alınmamış Promise'ler: Bir numaralı hata. Bir bileşende doğrudan `use(fetch(...))` çağırmak sonsuz bir döngüye neden olur. Her zaman React'in `cache`'i gibi bir önbellekleme mekanizması veya SWR/React Query gibi kütüphaneler kullanın.
  2. Eksik Sınırlar: Bir üst `` sınırı olmadan `use(Promise)` kullanmak uygulamanızı çökertir. Benzer şekilde, bir üst `` olmadan reddedilen bir promise de uygulamayı çökertir. Bileşen ağacınızı bu sınırları göz önünde bulundurarak tasarlamalısınız.
  3. Erken Optimizasyon: `use(Context)` performans için harika olsa da, her zaman gerekli değildir. Basit, seyrek değişen veya tüketicilerinin yeniden render edilmesinin ucuz olduğu context'ler için geleneksel `useContext` gayet iyidir ve biraz daha basittir. Açık bir performans nedeni olmadan kodunuzu aşırı karmaşıklaştırmayın.
  4. `cache`'i Yanlış Anlamak: React'in `cache` fonksiyonu argümanlarına göre memoize eder, ancak bu önbellek genellikle sunucu istekleri arasında veya istemcide tam bir sayfa yeniden yüklemesinde temizlenir. Uzun süreli istemci tarafı durumu için değil, istek düzeyinde önbellekleme için tasarlanmıştır. Karmaşık istemci tarafı önbellekleme, geçersiz kılma ve mutasyon için, özel bir veri çekme kütüphanesi hala çok güçlü bir seçimdir.

En İyi Uygulamalar Kontrol Listesi

Gelecek `use`'da: Sunucu Bileşenleri ve Ötesi

`use` hook'u sadece istemci tarafında bir kolaylık değildir; React Sunucu Bileşenleri'nin (RSC'ler) temel bir direğidir. Bir RSC ortamında, bir bileşen sunucuda çalışabilir. `use(fetch(...))`'i çağırdığında, sunucu kelimenin tam anlamıyla o bileşenin render edilmesini duraklatabilir, veritabanı sorgusunun veya API çağrısının tamamlanmasını bekleyebilir ve ardından veriyle render etmeye devam ederek nihai HTML'i istemciye akışla gönderebilir.

Bu, veri çekmenin render sürecinin birinci sınıf bir vatandaşı olduğu, sunucu tarafı veri alımı ile istemci tarafı arayüz kompozisyonu arasındaki sınırı ortadan kaldıran sorunsuz bir model oluşturur. Daha önce yazdığımız aynı `UserProfile` bileşeni, minimum değişiklikle sunucuda çalışabilir, verisini çekebilir ve tarayıcıya tamamen oluşturulmuş HTML gönderebilir, bu da daha hızlı ilk sayfa yüklemelerine ve daha iyi bir kullanıcı deneyimine yol açar.

`use` API'si aynı zamanda genişletilebilir. Gelecekte, Observable'lar (örneğin RxJS'ten) veya diğer özel "thenable" nesneler gibi diğer asenkron kaynaklardan değerleri sarmalını açmak için kullanılabilir, bu da React bileşenlerinin harici veriler ve olaylarla nasıl etkileşime girdiğini daha da birleştirir.

Sonuç: React Geliştirmede Yeni Bir Çağ

`use` hook'u yeni bir API'den daha fazlasıdır; daha temiz, daha bildirimsel ve daha performanslı React uygulamaları yazmaya bir davettir. Asenkron operasyonları ve context tüketimini doğrudan render akışına entegre ederek, yıllardır karmaşık desenler ve standart kod gerektiren sorunları zarif bir şekilde çözer.

Her küresel geliştirici için ana çıkarımlar şunlardır:

React 19 ve ötesinin çağına girerken, `use` hook'unda ustalaşmak çok önemli olacaktır. Dinamik kullanıcı arayüzleri oluşturmak için daha sezgisel ve güçlü bir yolun kilidini açar, istemci ile sunucu arasındaki boşluğu doldurur ve yeni nesil web uygulamalarının önünü açar.

`use` hook'u hakkındaki düşünceleriniz nelerdir? Onunla denemeler yapmaya başladınız mı? Deneyimlerinizi, sorularınızı ve görüşlerinizi aşağıdaki yorumlarda paylaşın!