React Hooks: Bileşen Geliştirmenin Geleceği ve Gücü
React geliştirme dünyası, sürekli evrilen bir yapıya sahip. Yıllar içinde bileşen tabanlı mimarisiyle modern web uygulamalarının vazgeçilmezi haline gelen React, 2019 yılında tanıtılan bir özellikle adeta ikinci bir devrim yaşadı: React Hooks. Bu makale, React Hooks’un ne olduğunu, neden bu kadar önemli olduğunu ve geliştiricilerin daha temiz, daha anlaşılır ve daha sürdürülebilir kod yazmasına nasıl olanak tanıdığını derinlemesine inceleyecek. Eğer React’ta durum yönetimi ve yan etkilerle başınız dertteyse veya sadece fonksiyonel bileşenlerin gücünü tam anlamıyla keşfetmek istiyorsanız, doğru yerdesiniz.
React Gelişiminde Bir Dönüm Noktası: Neden Hooks’a İhtiyaç Duyuldu?
React, ilk çıktığı günden itibaren kullanıcı arayüzleri oluşturmak için deklaratif (bildirimsel) bir yaklaşım sunarak geliştiricilerin hayatını kolaylaştırdı. Ancak zamanla, özellikle sınıf bileşenleri (class components) kullanılarak geliştirilen daha karmaşık uygulamalarda bazı zorluklar ortaya çıktı. Bu zorluklar, React ekibini ve topluluğu, fonksiyonel bileşenlerin potansiyelini tam olarak kullanabilecekleri yeni bir çözüm arayışına itti.
Sınıf bileşenleri, React’ın ilk zamanlarından beri durum (state) ve yaşam döngüsü metotları (lifecycle methods) gibi önemli özelliklere erişim sağlamanın tek yoluydu. Ancak bu yaklaşım, beraberinde bazı karmaşıklıkları da getiriyordu. Örneğin, this anahtar kelimesinin JavaScript’teki dinamik bağlamı, birçok geliştirici için kafa karıştırıcı olabiliyordu. Metotları doğru bir şekilde bağlamak (binding) veya ok işlevleri kullanmak, sıkça unutulan veya yanlış yapılan adımlardı. Bu durum, özellikle yeni başlayanlar için React öğrenme eğrisini artırıyordu.
Dahası, sınıf bileşenlerinde durum mantığı ve yan etkiler (side effects) genellikle farklı yaşam döngüsü metotlarına dağılmış durumdaydı. Örneğin, bir API’den veri çekme işlemi hem componentDidMount hem de componentDidUpdate içinde tekrar edebilirken, bir olay dinleyicisini temizleme işlemi componentWillUnmount içinde yapılıyordu. Bu durum, birbiriyle ilişkili mantığın (örneğin, bir kullanıcının konumunu takip eden bir bileşen) kod tabanına dağılmasına ve okunabilirliğin azalmasına neden oluyordu. Geliştiriciler, ilgili mantığı tek bir yerde toplamakta zorlanıyor, bu da kodun bakımını ve test edilmesini güçleştiriyordu.
Bir diğer önemli sorun ise, bileşenler arasında durum bilgisi içermeyen mantığı (stateless logic) veya yan etki mantığını (side effect logic) yeniden kullanmanın zorluğuydu. HOC’lar (Higher-Order Components – Üst Düzey Bileşenler) ve Render Props gibi kalıplar bu soruna çözüm sunmaya çalışsa da, bunlar da beraberinde “wrapper hell” (sarmal cehennemi) veya iç içe geçmiş bileşen ağaçları gibi yeni karmaşıklıklar getiriyordu. Bu durum, bileşen ağacının derinliğini artırarak hata ayıklamayı zorlaştırabiliyor ve performans üzerinde olumsuz etkiler yaratabiliyordu.
İşte tam da bu noktada, React Hooks devreye girdi. Hooks, fonksiyonel bileşenlerin içine durum ve yaşam döngüsü özelliklerini “kancalamanıza” olanak tanıyan özel JavaScript fonksiyonlarıdır. Bu sayede, sınıf bileşenlerinin tüm gücünü, fonksiyonel bileşenlerin sadeliği ve okunabilirliğiyle birleştirmek mümkün hale geldi. Hooks ile geliştiriciler, ilgili mantığı tek bir yerde toplayabilir, bileşenler arasında kolayca yeniden kullanılabilir mantık parçacıkları oluşturabilir ve this karmaşasından kurtulabilirler. Bu, React geliştirme deneyimini kökten değiştirerek daha verimli, daha anlaşılır ve daha keyifli hale getirdi. Hooks’un temel felsefesi, bileşenlerin içinde durum, yan etkiler ve diğer React özelliklerini kullanabilmek için basit ve doğrudan bir yol sunmaktır. Bu sayede, fonksiyonel bileşenler artık sadece “stateless” (durumsuz) bileşenler olmaktan çıkıp, tüm gücüyle “stateful” (durum sahibi) bileşenler haline gelebilmektedir.
useState ile React’ta Durum Yönetimini Nasıl Basitleştiririz?
React Hooks dünyasına adım atarken karşılaşacağınız ilk ve belki de en temel Hook, useState‘dir. Bu Hook, fonksiyonel bileşenlerimize durum (state) eklememizi sağlar; yani, bileşenin render’lar arasında değişebilen ve kullanıcı etkileşimlerine veya dış verilere göre güncellenebilen verileri saklamasına olanak tanır. Sınıf bileşenlerindeki this.state ve this.setState yapısına bir alternatif olarak, useState çok daha basit ve anlaşılır bir sözdizimi sunar.
useState‘in temel kullanımı oldukça basittir. Bir fonksiyonel bileşenin içinde çağrıldığında, bir dizi döndürür. Bu dizinin ilk elemanı mevcut durum değeri, ikinci elemanı ise bu durumu güncelleyecek bir fonksiyondur. useState‘e argüman olarak verdiğiniz değer ise durumun başlangıç değerini belirler.
import React, { useState } from 'react';
function Sayac() {
// 'sayi' mevcut durum, 'setSayi' durumu güncelleyen fonksiyon
// Başlangıç değeri 0
const [sayi, setSayi] = useState(0);
return (
<div>
<p>Sayaç: {sayi}</p>
<button onClick={() => setSayi(sayi + 1)}>Artır</button>
<button onClick={() => setSayi(sayi - 1)}>Azalt</button>
</div>
);
}
Yukarıdaki örnekte görüldüğü gibi, sayi değişkeni bileşenin durumunu tutarken, setSayi fonksiyonu bu durumu değiştirmek için kullanılır. Bu yaklaşım, sınıf bileşenlerindeki this.setState‘e göre önemli avantajlar sunar. this.setState genellikle durumu derinlemesine birleştirirken (merge), setSayi doğrudan yeni değeri atar. Eğer durumunuz bir nesne ise ve sadece belirli bir özelliğini güncellemek istiyorsanız, bunu manuel olarak yapmanız gerekir: setKullanici({ ...kullanici, ad: "Yeni Ad" }). Bu, durumun nasıl güncellendiği konusunda daha açık ve tahmin edilebilir bir kontrol sağlar.
Birden fazla durum değişkeni kullanmak da oldukça kolaydır. Her durum değişkeni için ayrı bir useState çağrısı yapabilirsiniz. Bu, sınıf bileşenlerindeki tek bir büyük durum nesnesini yönetmek yerine, ilgili durum parçacıklarını birbirinden bağımsız olarak ele almanızı sağlar. Bu modüler yaklaşım, kodunuzun daha düzenli ve anlaşılır olmasına yardımcı olur.
import React, { useState } from 'react';
function KullaniciProfili() {
const [ad, setAd] = useState('Ahmet');
const [yas, setYas] = useState(30);
const [aktif, setAktif] = useState(true);
return (
<div>
<p>Ad: {ad}</p>
<p>Yaş: {yas}</p>
<p>Durum: {aktif ? 'Aktif' : 'Pasif'}</p>
<button onClick={() => setAd('Mehmet')}>Adı Değiştir</button>
<button onClick={() => setYas(yas + 1)}>Yaşı Artır</button>
<button onClick={() => setAktif(!aktif)}>Durumu Değiştir</button>
</div>
);
}
useState ayrıca, bir önceki duruma bağlı olarak yeni durumu hesaplamanız gerektiğinde kullanışlı olan fonksiyonel güncellemeleri de destekler. Örneğin, bir sayacı bir önceki değerine göre artırmak için setSayi(prevSayi => prevSayi + 1) şeklinde bir fonksiyon geçirebilirsiniz. Bu, özellikle asenkron güncellemelerle çalışırken veya birden fazla durum güncellemesinin aynı anda gerçekleştiği durumlarda olası hataları önler. Bu yaklaşım, React’ın durum güncellemelerini toplu işlemesi (batching) sırasında bile her zaman doğru ve güncel duruma erişmenizi garanti eder.
Gerçek Dünya Senaryosu: Bir Form Alanının Durumunu Yönetme
useState‘in en yaygın kullanım alanlarından biri, form girişlerinin durumunu yönetmektir. Her bir form elemanı için ayrı bir durum değişkeni tanımlayarak, kullanıcının girdilerini kolayca takip edebilir ve işleyebilirsiniz.
import React, { useState } from 'react';
function KayitFormu() {
const [kullaniciAdi, setKullaniciAdi] = useState('');
const [email, setEmail] = useState('');
const [sifre, setSifre] = useState('');
const handleSubmit = (event) => {
event.preventDefault(); // Sayfanın yeniden yüklenmesini engelle
console.log('Kullanıcı Adı:', kullaniciAdi);
console.log('E-posta:', email);
console.log('Şifre:', sifre);
// Burada sunucuya veri gönderme gibi işlemler yapılabilir
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="kullaniciAdi">Kullanıcı Adı:</label>
<input
type="text"
id="kullaniciAdi"
value={kullaniciAdi}
onChange={(e) => setKullaniciAdi(e.target.value)}
/>
</div>
<div>
<label htmlFor="email">E-posta:</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<div>
<label htmlFor="sifre">Şifre:</label>
<input
type="password"
id="sifre"
value={sifre}
onChange={(e) => setSifre(e.target.value)}
/>
</div>
<button type="submit">Kaydol</button>
</form>
);
}
Bu örnekte, her bir input alanı kendi useState ile yönetiliyor. Kullanıcı her yazdığında, ilgili set fonksiyonu çağrılarak durum güncelleniyor ve input değeri React tarafından kontrol ediliyor. Bu “kontrollü bileşenler” (controlled components) yaklaşımı, form verilerini yönetmek için en iyi yöntemlerden biridir ve useState bu süreci oldukça basitleştirir. Böylece, kullanıcı etkileşimlerinden gelen verilerle bileşenin durumunu senkronize tutmak çok daha kolay hale gelir.
useEffect ile Yan Etkileri Etkin Bir Şekilde Nasıl Yönetiriz?
React uygulamalarında, durum güncellemeleri veya render işlemleri dışındaki etkileşimlere “yan etkiler” (side effects) denir. Bunlar genellikle veri çekme (data fetching), DOM manipülasyonu, olay dinleyicileri ayarlama veya temizleme, zamanlayıcılar gibi işlemleri içerir. Sınıf bileşenlerinde bu tür yan etkiler genellikle componentDidMount, componentDidUpdate ve componentWillUnmount gibi yaşam döngüsü metotlarına dağılmış durumdaydı. Bu durum, ilgili mantığın farklı yerlerde tekrar etmesine ve kodun okunabilirliğinin azalmasına neden oluyordu. İşte useEffect Hook’u, bu sorunu çözmek ve yan etkileri fonksiyonel bileşenler içinde tek, tutarlı bir API ile yönetmek için tasarlandı.
useEffect, bileşen render edildikten sonra çalışır. Varsayılan olarak, her render işleminden sonra (ilk render dahil) çalışır. Ancak, ne zaman çalışacağını kontrol etmek için ikinci bir argüman olarak bir bağımlılık dizisi (dependency array) alır. Bu bağımlılık dizisi, useEffect‘in gücünü ve esnekliğini ortaya koyan kritik bir özelliktir.
import React, { useState, useEffect } from 'react';
function DokumanBasligi() {
const [sayac, setSayac] = useState(0);
useEffect(() => {
// Bu kod her render'dan sonra çalışır (sayac değiştiğinde)
document.title = Sayaç: ${sayac};
console.log('Doküman başlığı güncellendi.');
}, [sayac]); // 'sayac' değiştiğinde tekrar çalış
return (
<div>
<p>Sayaç: {sayac}</p>
<button onClick={() => setSayac(sayac + 1)}>Artır</button>
</div>
);
}
Yukarıdaki örnekte, useEffect, sayac değeri her değiştiğinde doküman başlığını günceller. Eğer bağımlılık dizisini boş bırakırsanız ([]), useEffect sadece bileşen ilk kez monte edildiğinde (mount) çalışır ve bu, sınıf bileşenlerindeki componentDidMount davranışına benzer. Eğer bağımlılık dizisini hiç vermezseniz, her render işleminden sonra çalışır.
Bağımlılık Dizisi (Dependency Array) ve Önemi
useEffect‘in ikinci argümanı olan bağımlılık dizisi, Hook’un ne zaman yeniden çalıştırılacağını belirler. Bu dizi, fonksiyonel bileşenin kapsamındaki değişkenleri içerir. React, bir sonraki render’da bu dizideki değerlerin bir önceki render’daki değerlerle aynı olup olmadığını kontrol eder. Eğer herhangi bir değer değişmişse, useEffect içindeki fonksiyon yeniden çalıştırılır.
-
Boş bağımlılık dizisi (
[]):useEffectsadece bileşen ilk kez DOM’a eklendiğinde (mount edildiğinde) çalışır. Bu, sınıf bileşenlerindekicomponentDidMount‘a eşdeğerdir ve genellikle bir defaya mahsus API çağrıları veya olay dinleyicileri kurmak için kullanılır. -
Bağımlılık dizisi yok:
useEffecther render işleminden sonra çalışır. Bu, genellikle çok dikkatli kullanılması gereken bir durumdur, çünkü performans sorunlarına veya sonsuz döngülere yol açabilir. -
Belirli değişkenleri içeren dizi (örneğin,
[propA, stateB]):useEffect, dizidekipropAveyastateBdeğerlerinden herhangi biri değiştiğinde yeniden çalışır. Bu, yan etkinin belirli verilere bağımlı olduğu durumlar için idealdir.
Bağımlılık dizisini doğru kullanmak, useEffect ile sonsuz döngüleri önlemenin ve performans sorunlarından kaçınmanın anahtarıdır. Örneğin, eğer bir useEffect içinde bir durum güncelleyici çağırıyorsanız ve bu durum güncelleyici bağımlılık dizisinde yer almıyorsa, sonsuz bir döngüye girebilirsiniz. React’ın ESLint eklentisi, bu tür hataları yakalamanıza yardımcı olabilir.
Temizleme Fonksiyonu (Cleanup Function) ile Kaynak Yönetimi
Bazı yan etkiler, bileşen DOM’dan kaldırıldığında veya bir sonraki yan etki çalışmadan önce temizlenmesi gereken kaynaklar oluşturur. Örneğin, bir olay dinleyicisi eklediyseniz, bileşen kaldırıldığında bu dinleyiciyi kaldırmanız gerekir; aksi takdirde hafıza sızıntılarına yol açabilirsiniz. useEffect, temizleme işlevini bir fonksiyon döndürerek sağlar. Bu fonksiyon, bileşen kaldırıldığında veya bağımlılıklar değiştiğinde bir sonraki yan etki çalışmadan önce çağrılır.
import React, { useState, useEffect } from 'react';
function FareKonumuTakibi() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const logMousePosition = (e) => {
setX(e.clientX);
setY(e.clientY);
};
useEffect(() => {
console.log('useEffect çalıştı');
window.addEventListener('mousemove', logMousePosition);
// Temizleme fonksiyonu: bileşen kaldırıldığında veya bağımlılıklar değiştiğinde çalışır
return () => {
console.log('Bileşen temizleniyor');
window.removeEventListener('mousemove', logMousePosition);
};
}, []); // Sadece ilk kez mount edildiğinde ve unmount edildiğinde çalışır
return (
<div>
Fare Konumu: X - {x}, Y - {y}
</div>
);
}
Bu örnekte, mousemove olay dinleyicisi sadece bileşen ilk kez yüklendiğinde eklenir ve bileşen DOM’dan kaldırıldığında temizlenir. Bu, sınıf bileşenlerindeki componentWillUnmount davranışına eşdeğerdir ve kaynak yönetimini çok daha zarif bir şekilde yapmanızı sağlar.
Uygulamalı Örnek: Bir API’den Veri Çekme
useEffect‘in en yaygın kullanım senaryolarından biri, dış bir API’den veri çekmektir. Genellikle, bu işlem bileşen ilk yüklendiğinde yapılır ve çekilen veriler bileşenin durumunda saklanır.
import React, { useState, useEffect } from 'react';
function KullaniciListesi() {
const [kullanicilar, setKullanicilar] = useState([]);
const [yukleniyor, setYukleniyor] = useState(true);
const [hata, setHata] = useState(null);
useEffect(() => {
const veriCek = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(HTTP hata! Durum: ${response.status});
}
const data = await response.json();
setKullanicilar(data);
} catch (error) {
setHata(error);
} finally {
setYukleniyor(false);
}
};
veriCek();
}, []); // Sadece ilk kez mount edildiğinde çalışır
if (yukleniyor) return <p>Kullanıcılar yükleniyor...</p>;
if (hata) return <p>Hata: {hata.message}</p>;
return (
<div>
<h3>Kullanıcı Listesi</h3>
<ul>
{kullanicilar.map((kullanici) => (
<li key={kullanici.id}>{kullanici.name} ({kullanici.email})</li>
))}
</ul>
</div>
);
}
Bu örnekte, useEffect içinde bir asenkron fonksiyon tanımlanarak API’den kullanıcı verileri çekiliyor. yukleniyor ve hata durumları, kullanıcılara geri bildirim sağlamak için kullanılıyor. Bağımlılık dizisinin boş olması ([]), bu veri çekme işleminin sadece bileşen ilk yüklendiğinde bir kez çalışmasını sağlar. Bu sayede, gereksiz API çağrıları önlenir ve uygulamanın performansı artırılır. useEffect, yan etkileri yönetmek için güçlü, esnek ve okunabilir bir yol sunarak fonksiyonel bileşenlerin kapsamını ve yeteneklerini önemli ölçüde genişletir.
İleri Düzey Hooks: useContext, useRef ve useReducer’ın Gücü
React Hooks ekosistemi, useState ve useEffect ile sınırlı değildir. Daha karmaşık senaryoları yönetmek için tasarlanmış başka güçlü Hook’lar da bulunur. Bu bölümde, useContext, useRef ve useReducer gibi ileri düzey Hook’ları inceleyeceğiz ve bunların React uygulamalarınızda nasıl değer katabileceğini göreceğiz.
useContext: Prop Drilling Sorununa Zarif Bir Çözüm
Büyük ve karmaşık React uygulamalarında, bazı verilerin (örneğin, kullanıcı kimlik doğrulama durumu, tema tercihleri veya dil ayarları) bileşen ağacının birçok farklı seviyesindeki bileşenler tarafından erişilebilir olması gerekebilir. Bu tür verileri her bir bileşenden alt bileşenine manuel olarak prop (özellik) olarak geçirmeye “prop drilling” (prop delme) denir. Prop drilling, kod tekrarına yol açar, bileşen ağacını karmaşıklaştırır ve bakımı zorlaştırır.
React’ın Context API‘si, bu soruna bir çözüm sunar ve useContext Hook’u, fonksiyonel bileşenlerin bu bağlam (context) değerine kolayca erişmesini sağlar. İlk olarak, createContext ile bir bağlam oluşturur ve ardından Provider bileşeni ile bu bağlamı bileşen ağacının belirli bir kısmına sağlarız. Son olarak, herhangi bir alt bileşende useContext Hook’unu kullanarak bu bağlamdaki değere doğrudan erişebiliriz.
import React, { createContext, useContext, useState } from 'react';
// 1. Tema bağlamı oluştur
const TemaBaglami = createContext(null);
// 2. Tema sağlayıcı bileşeni
function TemaSaglayici({ children }) {
const [tema, setTema] = useState('light'); // 'light' veya 'dark'
const temaDegistir = () => {
setTema(oncekiTema => (oncekiTema === 'light' ? 'dark' : 'light'));
};
return (
<TemaBaglami.Provider value={{ tema, temaDegistir }}>
{children}
</TemaBaglami.Provider>
);
}
// 3. Bağlamı kullanan bir bileşen
function TemaButonu() {
const { tema, temaDegistir } = useContext(TemaBaglami);
return (
<button onClick={temaDegistir} style={{ background: tema === 'dark' ? '#333' : '#eee', color: tema === 'dark' ? '#eee' : '#333' }}>
Temayı Değiştir ({tema})
</button>
);
}
// Uygulama
function Uygulama() {
return (
<TemaSaglayici>
<div>
<h3>Uygulama Teması</h3>
<TemaButonu />
<p>Bu paragraf da temadan etkilenebilir.</p>
</div>
</TemaSaglayici>
);
}
useContext, özellikle global veya yarı-global durumları yönetmek için mükemmel bir araçtır. Prop drilling’i ortadan kaldırarak kodunuzu daha temiz ve okunabilir hale getirir.
useRef: DOM Elemanlarına Erişim ve Değişken Referanslar
useRef Hook’u, bileşenlerinizde değiştirilebilir (mutable) bir referans tutmanızı sağlar. En yaygın kullanımı, bir DOM elemanına doğrudan erişmektir. Örneğin, bir input alanına odaklanmak (focus) veya bir video oynatıcısını kontrol etmek isteyebilirsiniz. useRef, bir nesne döndürür ve bu nesnenin .current özelliği, referans verilen değeri tutar. Bu değer, bileşenin yeniden render edilmesi durumunda bile değişmeden kalır.
import React, { useRef, useEffect } from 'react';
function OdaklanmaInputu() {
const inputRef = useRef(null);
useEffect(() => {
// Bileşen yüklendiğinde input'a odaklan
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Odaklan</button>
</div>
);
}
useRef sadece DOM referansları için değil, aynı zamanda render’lar arasında değişmeden kalması gereken herhangi bir değeri saklamak için de kullanılabilir. Örneğin, bir zamanlayıcı ID’si veya bir önceki prop değeri gibi. Bu değerler güncellendiğinde bile bileşenin yeniden render edilmesini tetiklemez, bu da performans açısından avantaj sağlayabilir.
useReducer: Karmaşık Durum Mantığı İçin Güçlü Bir Alternatif
useState basit durum yönetimi için harika olsa da, daha karmaşık durum mantığına sahip uygulamalarda veya birden fazla durum güncellemesinin birbiriyle ilişkili olduğu durumlarda yetersiz kalabilir. İşte bu noktada useReducer devreye girer. useReducer, Redux gibi durum yönetim kütüphanelerine benzer bir desen sunar: bir “reducer” fonksiyonu ve bir “dispatch” fonksiyonu.
useReducer, useState‘e bir alternatif olarak, durum mantığını bileşenden ayırmanıza ve daha öngörülebilir bir şekilde yönetmenize olanak tanır. Genellikle, durumun güncellenme şekli mevcut durum ve bir “action” (eylem) nesnesine bağlı olduğunda tercih edilir.
import React, { useReducer } from 'react';
// Reducer fonksiyonu: mevcut durumu ve bir eylemi alır, yeni durumu döndürür
const gorevReducer = (state, action) => {
switch (action.type) {
case 'EKLE':
return [...state, { id: Date.now(), metin: action.payload, tamamlandi: false }];
case 'TOGGLE':
return state.map(gorev =>
gorev.id === action.payload ? { ...gorev, tamamlandi: !gorev.tamamlandi } : gorev
);
case 'SIL':
return state.filter(gorev => gorev.id !== action.payload);
default:
return state;
}
};
function GorevListesi() {
// useReducer, reducer fonksiyonunu ve başlangıç durumunu alır
// Bir dizi döndürür: mevcut durum ve dispatch fonksiyonu
const [gorevler, dispatch] = useReducer(gorevReducer, []);
const [yeniGorevMetni, setYeniGorevMetni] = useState('');
const handleEkle = (e) => {
e.preventDefault();
if (yeniGorevMetni.trim()) {
dispatch({ type: 'EKLE', payload: yeniGorevMetni });
setYeniGorevMetni('');
}
};
return (
<div>
<h3>Görev Listesi</h3>
<form onSubmit={handleEkle}>
<input
type="text"
value={yeniGorevMetni}
onChange={(e) => setYeniGorevMetni(e.target.value)}
placeholder="Yeni görev ekle..."
/>
<button type="submit">Ekle</button>
</form>
<ul>
{gorevler.map((gorev) => (
<li key={gorev.id} style={{ textDecoration: gorev.tamamlandi ? 'line-through' : 'none' }}>
<span onClick={() => dispatch({ type: 'TOGGLE', payload: gorev.id })}>
{gorev.metin}
</span>
<button onClick={() => dispatch({ type: 'SIL', payload: gorev.id })}>Sil</button>
</li>
))}
</ul>
</div>
);
}
useReducer, özellikle durumun birden fazla alt değere sahip olduğu ve bu değerlerin karmaşık bir şekilde güncellendiği durumlarda veya durum güncelleme mantığını test edilebilir ayrı bir fonksiyonda tutmak istediğinizde çok faydalıdır. Bu, kodunuzu daha modüler, daha okunabilir ve daha kolay test edilebilir hale getirir. Bu üç Hook, useContext, useRef ve useReducer, React’ın fonksiyonel bileşenlerinin yeteneklerini genişleterek, daha önce sadece sınıf bileşenleri veya harici kütüphanelerle mümkün olan karmaşık senaryoları yerel olarak yönetmenizi sağlar.
Kendi Özel Hook’larınızı (Custom Hooks) Oluşturma ve Uygulama Geliştirmede En İyi Pratikler
React Hooks’un belki de en güçlü yönlerinden biri, geliştiricilere kendi özel Hook’larını (Custom Hooks) oluşturma yeteneği sunmasıdır. Özel Hook’lar, bileşenler arasında durum bilgisi içermeyen mantığı (stateful logic) yeniden kullanmanın ve paylaşmanın harika bir yoludur. Bu, kod tekrarını azaltır, bileşenlerinizi daha küçük ve daha odaklanmış hale getirir ve uygulamanızın genel mimarisini iyileştirir.
Custom Hooks Nedir ve Neden Kullanmalıyız?
Bir Custom Hook, adında “use” ile başlayan ve diğer Hook’ları (useState, useEffect vb.) çağırabilen bir JavaScript fonksiyonudur. Temel amacı, bir bileşenin karmaşık mantığını soyutlamak ve bu mantığı birden fazla bileşen arasında paylaşılabilir hale getirmektir. Örneğin, bir API’den veri çekme, form doğrulama, yerel depolama (localStorage) ile etkileşim veya pencere boyutunu izleme gibi işlevleri bir Custom Hook içinde toplayabilirsiniz.
Custom Hooks kullanmanın başlıca faydaları şunlardır:
- Kod Tekrarını Azaltma: Aynı mantığı birden fazla bileşende kopyala-yapıştır yapmak yerine, tek bir Custom Hook içinde tanımlayabilir ve istediğiniz yerde kullanabilirsiniz.
- Mantığı Ayırma: Bileşenlerinizin sadece arayüzle ilgili sorumluluklara odaklanmasını sağlarken, durum yönetimi veya yan etki mantığını ayrı bir Hook’a taşıyabilirsiniz. Bu, bileşenlerinizi daha temiz ve daha okunabilir hale getirir.
- Test Edilebilirliği Artırma: Mantığı bileşenlerden ayırarak, bu mantığı daha kolay bir şekilde izole edip test edebilirsiniz.
- Sürdürülebilirlik: Uygulamanız büyüdükçe, paylaşılan mantığı yönetmek ve güncellemek çok daha kolay hale gelir.
Bir Custom Hook Oluşturma Adımları: useToggle Örneği
Basit bir Custom Hook oluşturmak için, “use” ile başlayan bir fonksiyon tanımlamanız ve içinde diğer Hook’ları kullanmanız yeterlidir. İşte basit bir açma/kapama (toggle) işlevini yöneten useToggle Hook’u:
import { useState, useCallback } from 'react';
function useToggle(initialState = false) {
const [state, setState] = useState(initialState);
// useCallback kullanarak toggle fonksiyonunun gereksiz yere yeniden oluşturulmasını engelleriz
const toggle = useCallback(() => setState(prev => !prev), []);
return [state, toggle];
}
// Bu Hook'u kullanan bir bileşen
function AyarButonu() {
const [acik, toggleAcik] = useToggle(false);
return (
<div>
<p>Durum: {acik ? 'Açık' : 'Kapalı'}</p>
<button onClick={toggleAcik}>Değiştir</button>
</div>
);
}
useToggle Hook’u, bir boolean durumu ve bu durumu değiştiren bir fonksiyon döndürür. Artık bu mantığı istediğiniz kadar bileşende yeniden kullanabilirsiniz.
Gerçek Dünya Senaryosu: useLocalStorage Hook’u
localStorage ile etkileşim, birçok web uygulamasında sıkça karşılaşılan bir gereksinimdir. Bu mantığı bir Custom Hook içine almak, kodunuzu çok daha temiz ve kullanışlı hale getirir.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// Durumu localStorage'dan veya başlangıç değerinden al
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.warn(Error reading localStorage key “${key}”:, error);
return initialValue;
}
});
// storedValue değiştiğinde localStorage'ı güncelle
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} catch (error) {
console.warn(Error setting localStorage key “${key}”:, error);
}
}, [key, storedValue]); // key veya storedValue değiştiğinde çalış
return [storedValue, setStoredValue];
}
// Bu Hook'u kullanan bir bileşen
function KullaniciAyarlari() {
const [isim, setIsim] = useLocalStorage('kullaniciIsim', 'Misafir');
const [tema, setTema] = useLocalStorage('uygulamaTema', 'light');
return (
<div>
<h3>Kullanıcı Ayarları</h3>
<p>İsim: {isim}</p>
<input type="text" value={isim} onChange={(e) => setIsim(e.target.value)} />
<p>Tema: {tema}</p>
<button onClick={() => setTema(tema === 'light' ? 'dark' : 'light')}>
Temayı Değiştir
</button>
</div>
);
}
useLocalStorage Hook’u, bir anahtar ve başlangıç değeri alır, ardından localStorage‘dan değeri okur veya kaydeder. Bu, kullanıcı tercihlerini veya diğer verileri tarayıcıda kalıcı hale getirmenin temiz ve yeniden kullanılabilir bir yoludur.
React Hooks ile En İyi Pratikler
Hooks’u etkin bir şekilde kullanmak için bazı en iyi pratikleri göz önünde bulundurmak önemlidir:
- Hooks Kurallarına Uyun: Hooks’u sadece React fonksiyonlarının (fonksiyonel bileşenler veya diğer Custom Hook’lar) en üst seviyesinde çağırın. Döngülerin, koşulların veya iç içe geçmiş fonksiyonların içinde çağırmayın. Bu kurallara uyulmaması, Hook’ların çalışma sırasını bozabilir ve beklenmedik hatalara yol açabilir.
-
Bağımlılık Dizilerini Doğru Kullanın:
useEffect,useCallbackveuseMemogibi Hook’larda bağımlılık dizilerini eksiksiz ve doğru bir şekilde belirtmek çok önemlidir. Yanlış bağımlılıklar sonsuz döngülere, gereksiz yeniden render’lara veya eski değerlerin kullanılmasına neden olabilir. React’ın ESLint eklentisi (eslint-plugin-react-hooks), bu hataları tespit etmede büyük yardımcıdır. -
useCallbackveuseMemoile Performans Optimizasyonu: Fonksiyonlar ve hesaplanmış değerler her render’da yeniden oluşturulur. Eğer bu fonksiyonlar veya değerler pahalı işlemlerse veya alt bileşenlere prop olarak geçiliyorsa,useCallback(fonksiyonlar için) veuseMemo(değerler için) kullanarak bunları önbelleğe alabilirsiniz. Bu, gereksiz yeniden render’ları önleyerek performansı artırabilir. Ancak, bunları her yerde kullanmak yerine, sadece performans sorunu gördüğünüz veya alt bileşenlerin gereksiz yere yeniden render olmasını engellemek istediğiniz durumlarda kullanmalısınız. - Mantığı Soyutlayın: Bileşenlerinizde karmaşık durum yönetimi veya yan etki mantığı gördüğünüzde, bunu bir Custom Hook’a taşımayı düşünün. Bu, bileşenlerinizi daha küçük, daha odaklanmış ve daha kolay anlaşılır hale getirir.
-
Erişilebilirlik (Accessibility): Hooks kullanırken de erişilebilirlik standartlarını göz ardı etmeyin. Örneğin,
useRefile DOM elemanlarına odaklanma veya ARIA niteliklerini dinamik olarak yönetme gibi durumlarda erişilebilirlik etkilerini düşünün.
Özel Hook’lar, React geliştiricilerine güçlü bir soyutlama ve yeniden kullanılabilirlik aracı sunar. Bu sayede, daha modüler, daha bakımı kolay ve daha performanslı uygulamalar geliştirmek mümkün hale gelir. Hooks’u doğru bir şekilde anlamak ve en iyi pratiklerle uygulamak, React projelerinizin kalitesini önemli ölçüde artıracaktır.
Sonuç: React Hooks ile Geleceğe Yönelik Gelişim ve Sıkça Sorulan Sorular
React Hooks, modern React geliştirme paradigmalarını kökten değiştiren, devrim niteliğinde bir özelliktir. Tanıtıldığı günden bu yana, geliştiricilerin fonksiyonel bileşenler içinde durum yönetimi ve yan etkileri çok daha temiz, okunabilir ve yeniden kullanılabilir bir şekilde ele almasına olanak tanımıştır. Sınıf bileşenlerinin getirdiği this karmaşası, yaşam döngüsü metotlarının dağınıklığı ve mantık paylaşımının zorlukları, Hooks sayesinde büyük ölçüde ortadan kalkmıştır.
useState ile basit durum yönetiminden, useEffect ile yan etkilerin kontrolüne, useContext ile prop drilling’i aşmaya ve useReducer ile karmaşık durum mantığını yönetmeye kadar, Hooks yelpazesi geniş bir ihtiyacı karşılar. Dahası, kendi özel Hook’larınızı oluşturma yeteneği, React’ın modülerliğini ve yeniden kullanılabilirliğini zirveye taşır. Bu sayede, geliştiriciler sadece daha az kod yazmakla kalmaz, aynı zamanda daha anlamlı, daha odaklanmış ve daha sürdürülebilir kod tabanları oluşturabilirler.
React Hooks, React’ın geleceğidir. Yeni geliştirilen uygulamaların neredeyse tamamı fonksiyonel bileşenler ve Hooks kullanılarak oluşturulmaktadır. Mevcut sınıf tabanlı projeler bile yavaş yavaş Hooks’a geçiş yapmaktadır, çünkü sunduğu avantajlar göz ardı edilemez. Hooks, React ekosistemini daha erişilebilir, daha performanslı ve daha keyifli bir hale getirerek, web geliştirmenin geleceğinde önemli bir rol oynamaya devam edecektir. Bu güçlü aracı öğrenmek ve projelerinizde uygulamak, şüphesiz geliştiricilik kariyerinize büyük katkı sağlayacaktır.
Sıkça Sorulan Sorular (SSS)
- Hooks ne zaman kullanılmalı, sınıf bileşenleri ne zaman?
- React ekibi, yeni geliştirilen tüm bileşenler için Hooks kullanmayı önermektedir. Hooks, sınıf bileşenlerinin tüm yeteneklerini fonksiyonel bileşenlere taşır ve genellikle daha temiz, daha kısa kod yazmanızı sağlar. Sınıf bileşenlerini kullanmaya devam etmek için geçerli bir nedeniniz yoksa (örneğin, mevcut bir sınıf bileşenini dönüştürmek çok maliyetliyse), Hooks tercih edilmelidir.
- Tüm sınıf bileşenlerini Hooks’a dönüştürmeli miyim?
- Hayır, mevcut ve çalışan sınıf bileşenlerini zorla Hooks’a dönüştürmek genellikle gereksiz bir çaba ve zaman kaybıdır. React ekibi, sınıf bileşenlerini kaldırma planının olmadığını belirtmiştir. Yeni özellikler eklerken veya büyük refactoring (yeniden yapılandırma) yaparken Hooks’a geçişi düşünebilirsiniz.
- Hooks performans sorunlarına yol açar mı?
- Hayır, Hooks doğru kullanıldığında sınıf bileşenlerinden daha kötü performans göstermez. Hatta
useCallbackveuseMemogibi optimizasyon Hook’ları ile performansı daha da artırabilirsiniz. Performans sorunları genellikle Hook’ların yanlış bağımlılık dizileriyle veya gereksiz yere çok sık çalışacak şekilde kullanılmasıyla ortaya çıkar, bu da geliştirici hatasından kaynaklanır. - Hooks ile Redux kullanabilir miyim?
- Evet, kesinlikle! React Redux kütüphanesi, Hooks ile uyumlu
useSelectorveuseDispatchHook’larını sunar. Bu Hook’lar, Redux store’una erişimi ve eylemleri göndermeyi çok daha basit ve doğrudan hale getirir, böylece Redux’u fonksiyonel bileşenlerle sorunsuz bir şekilde entegre edebilirsiniz. - Hangi durumlarda Custom Hook oluşturmalıyım?
- Eğer aynı durum bilgisi içermeyen mantığı (stateful logic) birden fazla bileşende yeniden kullanmanız gerekiyorsa, bu mantığı bir Custom Hook’a taşıyın. Örneğin, bir API’den veri çekme, form girişlerini yönetme, bir pencere boyutunu izleme veya bir sayaç işlevi gibi tekrarlayan görevler için Custom Hook’lar idealdir.
#React #ReactHooks #WebGeliştirme #JavaScript #Frontend #YazılımGeliştirme