"Öğrenecekleriniz" Paragrafının Açıklaması
Paylaştığınız metnin başındaki "Öğrenecekleriniz" (What you will learn) bölümü, bir yazılımcının tek başına modern bir web uygulamasını baştan sona nasıl inşa edeceğini özetlemektedir.
Kendi bilgisayarınızdan sunucuya uzanan süreç: Sadece kod yazmayı değil, yazılan kodun internette canlıya alınmasını ifade eder.
NoSQL Veritabanları (Firebase, MongoDB, Mongoose): Verileri geleneksel tablolar yerine esnek, JSON benzeri belgelerde saklamayı öğrenmektir.
Sunucu Dağıtımı (Heroku, Firebase): Hazırlanan uygulamanın tüm dünyanın erişebileceği bulut sunucularına yüklenmesidir.
Redux ve Durum Yönetimi: Büyük uygulamalarda verilerin kaybolmadan, düzenli bir merkezi depoda (store) yönetilmesidir.
Node.js ve Express: JavaScript kullanarak arka plan (backend) sunucu sistemleri ve API'lar yazmaktır.
Webpack: Yazılan onlarca kod ve stil dosyasını, tarayıcının hızlıca okuyabileceği tek bir pakete dönüştürme işlemidir.
1. Giriş ve Kurulumlar (Intro & Setup)
Geliştirme ortamının hazırlanması ve temel araçların bilgisayara kurulması aşamasıdır.
IDE (Geliştirme Ortamı)
Kodlarımızı rahatça yazmak, renklendirmek ve hataları görmek için kullanılan editörlerdir. En popüleri Visual Studio Code (VS Code) aracıdır.
GitHub Kaynakları
Yazdığımız kodların yedeğini tutmak, geçmişe dönük değişiklikleri izlemek ve başkalarıyla paylaşmak için Git sürüm kontrol sistemini ve GitHub bulut platformunu kullanırız.
Node.js Kurulumu
JavaScript normalde sadece tarayıcıda çalışır. Node.js, JavaScript kodlarının bilgisayarımızda veya sunucuda doğrudan çalışmasını sağlayan bir çalışma ortamıdır. Sunucu yazmak ve React paketlerini yönetmek için şarttır.
2. React Temelleri (React Basics)
React, web sitelerinin kullanıcı arayüzlerini (UI) yapboz parçaları gibi küçük parçalarla (bileşenler) birleştiren bir kütüphanedir.
React Uygulaması Oluşturma ve Proje Yapısı
Modern
React uygulamaları kurmak için Vite
ya
da create-react-app
gibi
araçlar kullanılır. Komut satırına şu yazılır:
bash
npm create vite@latest my-app -- --template reactKodu dikkatli kullanın.
Proje
açıldığında karşımıza src
(kodlarımızın
durduğu klasör), public
(statik
varlıklar) ve package.json
(bağımlılık
listesi) yapıları gelir.
JSX Mantığı (JSX Behind the scenes)
JSX,
JavaScript içinde doğrudan HTML benzeri kodlar yazmamızı sağlar.
Arka planda bu kodlar standart JavaScript fonksiyon çağrılarına
(React.createElement)
dönüştürülür.
Bileşenleri Bağlama ve Props (Connecting Components & Props)
Bileşenler (Components), tekrar kullanılabilir kod bloklarıdır. Props, üst bileşenden (Parent) alt bileşene (Child) veri aktarmak için kullanılan salt okunur parametrelerdir.
Uygulama: Haber Kartı Bileşeni
jsx
// Alt Bileşen (NewsItem.jsx) - Kendisine gelen başlık ve detay verilerini ekrana basar.function NewsItem(props) {return (<div style={{ border: "1px solid #ccc", padding: "10px", margin: "10px" }}><h2>{props.title}</h2><p>{props.detail}</p></div>);}// Üst Bileşen (App.jsx) - Alt bileşene veri (props) gönderir.export default function App() {return (<div><h1>Günün Haberleri</h1><NewsItem title="Yapay Zeka Gelişiyor" detail="React 19 yapay zeka entegrasyonu kolaylaştı." /><NewsItem title="Hava Durumu" detail="Yarın tüm yurtta güneşli bir hava bekleniyor." /></div>);}
Kodu dikkatli kullanın.
Bileşen Durumu (Component State, Prev State & Complex State)
State
(Durum),
bir bileşenin hafızasıdır. Kullanıcı butonlara tıkladıkça
veya yazı yazdıkça değişen verileri saklar. Eğer yeni durum
eski duruma bağlıysa güvenli veri değişimi için fonksiyonel
güncelleme (prevState)
kullanılır.
Uygulama: Sayaç ve İsim Listesi (Complex State)
jsx
import { useState } from 'react';export default function CounterApp() {const [count, setCount] = useState(0);const [user, setUser] = useState({ name: "Ahmet", roles: ["User"] });const arttir = () => {// prevState kullanımısetCount(prevCount => prevCount + 1);};const rolEkle = () => {// Karmaşık durum (Complex State) güncellerken eski verileri korumak için ... (spread) kullanılır.setUser(prevUser => ({...prevUser,roles: [...prevUser.roles, "Admin"]}));};return (<div style={{ padding: "20px" }}><p>Butona basılma sayısı: {count}</p><button onClick={arttir}>Arttır</button><hr /><h3>Kullanıcı Bilgileri</h3><p>İsim: {user.name}</p><p>Roller: {user.roles.join(', ')}</p><button onClick={rolEkle}>Admin Rolü Ekle</button></div>);}
Kodu dikkatli kullanın.
Çocuktan Ebeveyne Veri Aktarımı (Child to Parent)
Bileşenlerde veri akışı yukarıdan aşağı doğrudur. Ancak alt bileşenden üst bileşene bilgi göndermek için, üst bileşen alt bileşene bir fonksiyonu props olarak geçirir. Alt bileşen bu fonksiyonu tetiklediğinde veri yukarı çıkmış olur.
3. React Kancaları (React Hooks)
Hooks, fonksiyonel bileşenlerin state ve diğer React özelliklerini kullanabilmesini sağlayan özel fonksiyonlardır.
useEffect ve Temizleme (Cleanup Effect)
useEffect,
bileşen ekrana ilk geldiğinde, ekrandan gittiğinde veya belirli
veriler değiştiğinde çalışan yan etki (API'den veri çekme,
zamanlayıcı başlatma vb.) kancasıdır. Cleanup
fonksiyonu
ise bileşen kapandığında arkada açık kalan abonelikleri veya
zamanlayıcıları sıfırlayarak hafıza sızıntılarını önler.
useRef
DOM elementlerine doğrudan erişmek veya bileşen yeniden tetiklendiğinde sıfırlanmayan ama güncellendiğinde de ekranı yenilemeyen değişkenler tutmak için kullanılır.
React Context (Bölüm 1, 2, 3)
Props'ları kat kat aşağı taşımak (Props Drilling) yerine, verileri tüm bileşenlerin doğrudan erişebileceği global bir havuzda toplama yöntemidir.
Uygulama: Tema Değiştirici (Context & useEffect Application)
jsx
import { createContext, useState, useEffect, useContext } from 'react';// 1. Kutu (Context) oluşturulurconst ThemeContext = createContext();export function ThemeProvider({ children }) {const [theme, setTheme] = useState("light");// useEffect ile tema değişimini izlemeuseEffect(() => {console.log(`Tema ${theme} olarak değiştirildi.`);}, [theme]);const toggleTheme = () => {setTheme(prev => prev === "light" ? "dark" : "light");};return (<ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>);}// Tüketici alt bileşenfunction ThemeButton() {const { theme, toggleTheme } = useContext(ThemeContext);return (<button onClick={toggleTheme}>Şu anki Tema: {theme}. Değiştirmek için tıkla.</button>);}export default function App() {return (<ThemeProvider><div style={{ padding: "20px" }}><h1>Context Uygulaması</h1><ThemeButton /></div></ThemeProvider>);}
Kodu dikkatli kullanın.
4. Pratik Proje 1 - Hesabı Kim Öder? (Who pays the bill?)
Bu proje; state, validation (doğrulama), listeleme ve Context yapılarının harmanlandığı pratik bir uygulamadır.
Uygulama: Şanslı Kişiyi Seçme Projesi
jsx
import { useState } from 'react';export default function WhoPaysApp() {const [players, setPlayers] = useState([]);const [input, setInput] = useState("");const [error, setError] = useState("");const [loser, setLoser] = useState("");const oyuncuEkle = (e) => {e.preventDefault();if (input.trim() === "") {setError("İsim boş bırakılamaz!");return;}setPlayers([...players, input]);setInput("");setError("");};const oyuncuSil = (index) => {const yeniListe = players.filter((_, i) => i !== index);setPlayers(yeniListe);};const secici = () => {if (players.length < 2) {setError("En az 2 oyuncu olmalıdır!");return;}const rastgeleIndeks = Math.floor(Math.random() * players.length);setLoser(players[rastgeleIndeks]);};return (<div style={{ padding: "20px", maxWidth: "400px", margin: "auto" }}><h2>Hesabı Kim Öder?</h2><form onSubmit={oyuncuEkle}><input value={input} onChange={(e) => setInput(e.target.value)} placeholder="İsim yazın" /><button type="submit">Ekle</button></form>{error && <p style={{ color: "red" }}>{error}</p>}<ul>{players.map((p, idx) => (<li key={idx}>{p} <button onClick={() => oyuncuSil(idx)}>Sil</button></li>))}</ul><button onClick={secici} style={{ marginTop: "10px", background: "orange" }}>Hesabı Ödeyecek Kişiyi Seç!</button>{loser && <h3 style={{ color: "darkred", marginTop: "15px" }}>Fatura kesilen şanssız: {loser} 💸</h3>}</div>);}
Kodu dikkatli kullanın.
5. React Yönlendirici (React Router)
Çok sayfalı web siteleri hissi yaratmak amacıyla, tarayıcıyı tamamen yenilemeden sadece ilgili bileşenleri ekrana getiren sistemdir.
Bileşenler
ve Linkler:
Sayfa
geçişleri için standart <a>
etiketi
yerine React Router'ın <Link>
veya
<NavLink>
bileşenleri
kullanılır. Tarayıcının yenilenmesini engellerler.
Dinamik
Parametreler (Dynamic Params):
/urun/5
veya
/profil/ahmet
gibi
değişken URL yapılarını okumayı sağlar. useParams
kancası
ile bu ID değerleri yakalanır.
404 Sayfaları ve İç İçe Rotalar (Nested Routes): Tanımlanmamış tüm adresler için hata sayfası gösterilir. İç içe rotalar ise bir sayfa düzeninin (layout) içinde başka alt sayfalar açmaya yarar.
6. React Geçişleri ve Form Yönetimi (Transitions & Forms)
Animasyonlar ve Geçiş Grubu (Transition Group)
Bileşenler ekrana girerken veya çıkarken akıcı animasyonlar (solma, kayma) göstermek amacıyla CSS sınıflarını otomatik yöneten yapılardır.
Formik ve Şema Doğrulama (Formik & Validation Schema)
Büyük formlarda verileri toplamak, hata mesajlarını yönetmek ve form gönderim durumlarını kontrol etmek zordur. Formik bu süreçleri kısaltır. Form verilerinin doğruluğunu (eposta formatı, şifre uzunluğu vb.) kontrol etmek için ise Yup kütüphanesiyle şemalar oluşturulur.
Uygulama: Giriş Formu Örneği (Konsept Tasarımı)
jsx
// Formik ile entegre Yup şeması mantığı:// const LoginSchema = Yup.object().shape({// email: Yup.string().email('Geçersiz E-posta').required('Zorunlu Alan'),// password: Yup.string().min(6, 'En az 6 karakter!').required('Zorunlu Alan')// });
Kodu dikkatli kullanın.
7. React Karmaşık Durum Yönetimi (React Complex State & Redux)
Uygulama büyüdükçe Context yavaş kalabilir. Verileri yönetmek için daha profesyonel araçlara ihtiyaç duyulur.
Redux Toolkit (Store, Slice, Actions): Tüm uygulamanın durumunu tek bir büyük merkezde (Store) toplar. Bu yapının parçalarına Slice denir. Durumu değiştirmek için Actions yayınlanır (dispatch).
useReducer
Kancası:
useState'in
daha gelişmiş versiyonudur. Karmaşık durum değişikliklerini
tek bir merkez fonksiyon (reducer) üzerinden yönetir.
8. Pratik Proje 2 - Günlük Haberler (Daily News)
Bu
proje; Redux Toolkit, sahte bir API sunucusu (json-server)
ve çok sayfalı yönlendirmelerin bir araya getirilmesiyle
hazırlanan gelişmiş bir ön yüz portalıdır. Veriler sunucudan
çekilir (fetching),
ana sayfada kartlar halinde listelenir ve "daha fazla yükle"
(load more) butonuyla sayfalandırma yapılır.
9. Next.js Temelleri ve İşleyişi (Next JS Basics)
Next.js, React tabanlı modern bir framework'tür (çerçeve). React kütüphanesine ek olarak dosya tabanlı yönlendirme, optimizasyon ve sunucu tarafı çözümleri sunar.
Dosya Tabanlı Rotalama (File-based Routing)
Next.js'te
rota tanımlamak için harici kütüphanelere gerek yoktur. app
klasörü
içindeki klasör isimleri otomatik olarak web sitesinin link adresi
olur.
app/page.js
->
Ana sayfa (/)
app/about/page.js
->
Hakkımızda sayfası (/about)
app/blog/[id]/page.js
->
Dinamik yazı sayfası (/blog/12,
/blog/react
vb.)
Özel Rota Yapıları
Catch-All
(Hepsini Yakalayan) Rotalar:
[...slug]
yapısıyla
altındaki tüm kırılımları tek bir dosyada yakalar.
Route
Groups (Rota Grupları):
Klasör
ismini parantez içinde (auth)
yazarak
URL'i etkilemeden sadece dosyaları organize etmeyi sağlar.
10. Next.js Sunucu Tarafı İşlemleri (Next JS Rendering & Caching)
Next.js bileşenleri varsayılan olarak Server Components (Sunucu Bileşenleri) mimarisindedir. Kodlar sunucuda çalışır ve tarayıcıya sadece hazır HTML gönderilir.
Neden Kancaları (Hooks) Kullanamıyorum? (Server vs Client)
Sunucu
bileşenlerinde useState
veya
useEffect
gibi
tarayıcıya özel kancalar çalışmaz; çünkü bu kodlar
tarayıcıya hiç uğramadan sunucuda derlenir. Eğer butona tıklama
gibi etkileşimler gerekiyorsa, dosyanın en üstüne "use
client"
yazılarak
Client
Component (İstemci Bileşeni)
moduna
geçilmelidir.
Sunucu
Eylemleri (Server Actions) ve Form Yönetimi (useActionState)
Form
verilerini işlemek için harici API uçları yazmaya gerek kalmadan,
doğrudan sunucuda çalışan asenkron fonksiyonlar yazılabilir.
useActionState
kancası,
bu sunucu eylemlerinin yüklenme ve hata durumlarını form içinde
yönetmeyi kolaylaştırır.
Caching (Önbelleğe Alma) ve Veri Yenileme (Revalidate)
Next.js
sunucuyu yormamak için veri isteklerini önbelleğe alır.
revalidate
seçeneği
ile verilerin belirli saniyelerde bir veya el ile (on-demand) ne
zaman güncelleneceği yapılandırılır.
11. Next.js API Rotaları, Arayüz ve Kimlik Doğrulama (API, UI & Auth)
API Rotaları (App Route Handlers)
Next.js
sadece ön yüz değil, arka yüz (backend) hizmeti de verir.
route.js
dosyaları
oluşturarak dış dünyaya GET,
POST,
PATCH,
DELETE
yanıtları
veren HTTP servisleri yazılabilir.
Tailwind CSS ve Hero UI Entegrasyonu
Modern CSS framework'ü olan Tailwind CSS ile hızlıca responsive tasarımlar üretilir. Hazır bileşen kütüphaneleri (eski adıyla NextUI olan Hero UI) sayesinde profesyonel butonlar, tablolar ve pencereler projeye dahil edilir.
Next Auth Kimlik Doğrulama
Uygulamaya
güvenli giriş-çıkış sistemleri eklemek için kullanılır.
Kullanıcıların e-posta/şifre kombinasyonu ile veya GitHub, Google
gibi platformlar aracılığıyla tek tıkla giriş yapmasını
(OAuth) organize eder. Sunucu ve istemci tarafında oturum yönetimini
(Session)
kontrol altında tutar.
12. Pratik Proje 3 - Event0 (Etkinlik Yönetim Portalı)
Bu aşamada öğrenilen Next.js, Mongoose/MongoDB veritabanı, form doğrulamaları ve görsel bileşenler birleştirilerek tam teşekküllü bir Etkinlik Paylaşım Sitesi kurulur. Kullanıcılar sisteme giriş yapar, etkinlik mekanı ve tarihi belirleyerek yeni organizasyonlar yayınlar ve bu ilanlar ana sayfada listelenir.
13. Node.js Temelleri (Node JS Basics)
Node.js, asenkron ve olay güdümlü (event-driven) bir mimariye sahiptir.
Blocking vs Non-Blocking (Engelleyen - Engellemeyen Yapı): Node.js tek bir iş parçacığı (Single Thread) üzerinde çalışır. Ağır veri tabanı işlemleri yapılırken sistemi kilitlemez (non-blocking). İşi arka plandaki sistem havuzuna devreder, iş bittiğinde sonucu geri alır.
Event Loop ve Call Stack: Fonksiyonların çalışma sırasını belirleyen, asenkron görevlerin zamanı geldiğinde çalıştırılmasını organize eden Node.js motor mekanizmalarıdır.
14. Node Sunucusu ve Express Framework (Node Server & Express)
Node.js'in yalın haliyle sunucu yazmak çok satır kod gerektirir. Bu süreci basitleştirmek için Express.js minimal web çatısı kullanılır.
İstek ve Cevap Mekanizması, Ara Yazılımlar (Middleware)
Gelen isteklere (Request) karşılık HTML veya JSON verileri (Response) dönülür. Middleware, istek sunucuya ulaştıktan sonra asıl fonksiyona gitmeden önce araya girip kontrol yapan (örneğin giriş yapılmış mı, gelen veri düzgün mü) yapılardır.
Uygulama: Temel Express API Sunucusu (MongoDB Bağlantılı)
javascript
const express = require('express');const app = express();// Gelen JSON verilerini okuyabilmek için body-parser ara yazılımı aktifleştirilirapp.use(express.json());// Geçici sahte veritabanılet urunler = [{ id: 1, ad: "Laptop" },{ id: 2, ad: "Telefon" }];// GET İsteği - Verileri Getirmeapp.get('/api/urunler', (req, res) => {res.json(urunler);});// POST İsteği - Yeni Veri Eklemeapp.post('/api/urunler', (req, res) => {const yeniUrun = { id: Date.now(), ad: req.body.ad };urunler.push(yeniUrun);res.status(201).json(yeniUrun);});// Sunucuyu Başlatmaapp.listen(5000, () => {console.log("Express sunucusu port 5000 üzerinde çalışıyor.");});
Kodu dikkatli kullanın.
15. Sunucu Tarafı Güvenlik ve Kimlik Doğrulama (Node Auth)
Şifrelerin Hashlenmesi (Hashing)
Kullanıcı şifreleri veritabanına asla düz metin olarak kaydedilmez. Güvenlik için bcrypt gibi kütüphanelerle geri dönüştürülemeyecek şekilde şifrelenir (hashlenir).
JWT (JSON Web Token)
Kullanıcı giriş yaptığında sunucu ona gizli bir anahtarla imzalanmış dijital bir jeton (token) verir. Kullanıcı sonraki isteklerinde bu jetonu sunucuya göndererek kimliğini kanıtlar, böylece sunucu her seferinde şifre sormak zorunda kalmaz.
16. Kapsamlı Full-Stack Proje - Flickbase (Arka Plan ve Ön Yüz)
Müfredatın son büyük projesi olan Flickbase, tam donanımlı bir içerik (makale/film) yönetim platformudur. Hem sunucu (backend) hem de istemci (frontend) tarafları sıfırdan inşa edilir.
Arka Plan Mimari Yapısı (Server Side)
MVC
Modeli (Controllers, Services, Models):
Kodlar
karmakarışık yazılmaz. Veritabanı şemaları Models
içinde,
iş mantığı Services
içinde,
istekleri karşılayan yapılar ise Controllers
içinde
ayrıştırılır.
Gelişmiş Özellikler: Kullanıcı rollerine göre sayfa yetkilendirmesi (Admin/User yetki kontrolü), e-posta servisleri ile üyelik doğrulama ve veri listelemede sayfalara bölme (Pagination) işlemleri arka planda çözülür.
Ön Yüz Entegrasyonu (Front End)
Redux Entegrasyonu: Giriş yapan kullanıcının verileri ve makaleler Redux deposuna bağlanır.
Yönetim Paneli (Admin Layout): Sadece yetkili kullanıcıların görebileceği, içerik ekleme alanları tasarlanır.
Zengin Metin Editörü (WYSIWYG): Kullanıcıların içerik eklerken kalın, eğik veya listeli yazılar yazabilmesini sağlayan gelişmiş yazı düzenleyiciler entegre edilir.
Sonuç: Hazırlanan tüm ön yüz bileşenleri, yazılan Express API'larına bağlanarak full-stack otomasyon tamamlanır.
*************************************
Gönderdiğiniz ders müfredatını adım adım, satır satır inceleyip tasarladım. İstediğiniz gibi süreleri, önizleme yazılarını ve ders sayılarını tamamen temizledim. Sadece saf konulara, örneklere ve harika uygulamalara odaklandım.
Bu kurs, modern web siteleri ve uygulamaları yapmak için kullanılan en popüler araç olan React JS kütüphanesini öğretir. [1]
Web Sayfaları Arası Geçiş (React Router): Sitenizdeki sayfalar (Ana sayfa, Hakkımızda, İletişim) arasında hiç beklemeden, çok hızlı geçiş yapmayı öğreneceksiniz.
Merkezi Hafıza Yönetimi (Redux Toolkit): Uygulamanız büyüdüğünde, kullanıcının bilgilerini (örneğin sepete eklenen ürünleri) tek bir güvenli merkezden yönetmeyi göreceksiniz. [2, 3]
Şık Tasarımlar (Tailwind CSS): Uzun uzun CSS kodları yazmadan, hazır kelimelerle çok hızlı ve modern görünen siteler tasarlayacaksınız.
Yapay Zeka (AI) Desteği: Kod yazarken yapay zeka araçlarından nasıl yardım alacağınızı öğreneceksiniz.
Gerçek Projeler: Kurs sonunda kendi başınıza bir görev planlayıcı (yapılacaklar listesi) ve çalışan bir online alışveriş mağazası yapabilecek seviyeye geleceksiniz.
Kod yazabilmek için bilgisayarımıza bir laboratuvar kurmamız gerekir. VS Code isimli kod yazma programını indiririz ve işimizi kolaylaştıracak eklentiler yükleriz.
Fonksiyonlar, bilgisayara verdiğimiz özel görevlerdir. Bir kez yazar, istediğimiz kadar kullanırız.
💡 Örnek: Bir sayıyı ikiyle çarpan bir robot yapmak.
💻 Uygulama:
functionikiyleCarp(sayi) {returnsayi *2;}console.log(ikiyleCarp(5));// Ekrana 10 yazar.
Bir listenin içinden aradığımız tek bir şeyi şipşak bulmamızı sağlar.
💡 Örnek: Öğrenci listesinden adı "Ali" olanı seçmek.
💻 Uygulama:
constisimler= ["Ayşe","Ahmet","Ali","Can"];constbulunan= isimler.find(isim => isim ==="Ali");console.log(bulunan);// "Ali" sonucunu verir.
Büyük bir paketin içindeki eşyaları tek tek masaya dizmek gibidir. Nesne veya listelerin içindeki bilgileri kolayca dışarı alırız.
💡 Örnek: Bir oyuncak kutusundan sadece arabayı almak.
💻 Uygulama:
constaraba= {marka:"Tesla",model:"Model Y"};const{marka} = araba;console.log(marka);// "Tesla" yazar.
...)
Üç nokta (...)
işareti, bazen eldeki malzemeleri saçıp dağıtmaya (Spread),
bazen de kalanları bir torbaya toplamaya (Rest) yarar.
💡 Örnek: Eski misafir listesine 2 yeni kişi daha eklemek.
💻 Uygulama:
consteskiGrup= ["Efe","Ece"];constyeniGrup= [...eskiGrup,"Can","Su"];// Dağıttık ve ekledikconsole.log(yeniGrup);// ["Efe", "Ece", "Can", "Su"]
Yazıların içine kolayca
bilgisayar değişkenleri koymamızı sağlar. Ters tırnak (`)
işaretiyle yapılır.
💡 Örnek: "Merhaba Ahmet, hoş geldin!" yazısı oluşturmak.
💻 Uygulama:
constisim="Ahmet";console.log(`Merhaba ${isim}, hoş geldin!`);
Bilgisayara "Şöyleyse bunu yap, yoksa şunu yap" demektir. Ternary, bunu tek satırda yazar.
💡 Örnek: Hava yağmurluysa şemsiye al, değilse alma.
💻 Uygulama:
letyagmurlu Mu =true;letdurum= yagmurluMu ?"Şemsiye al":"Gözlük al";console.log(durum);// "Şemsiye al" yazar.
&&,
||,
??)
Kodların yan yana gelip şartları
kontrol etmesidir. &&
(Ve), ||
(Veya) anlamına gelir.
💡 Örnek: Hem paran varsa HEM DE dükkan açıksa ekmek alabilirsin.
💻 Uygulama:
letparaVar=true;letdukkanAcik=true;if(paraVar && dukkanAcik) { console.log("Ekmek alındı!"); }
?.)Bir şeyin var olup olmadığından emin değilsek, kodumuzun hata verip çökmesini engeller. "Varsa bak, yoksa dur" der.
💡 Örnek: Bir binada asansör düğmesi var mı diye bakmak, bina yoksa hiç aramamak.
💻 Uygulama:
constkullanici= {profil: {isim:"Can"} };console.log(kullanici.adres?.sehir);// Hata vermez, "undefined" der geçer.
Bir listedeki tüm elemanları tek tek alıp, hepsini değiştirerek yeni bir liste yapar.
💡 Örnek: Elimizdeki tüm sayıların iki katını almak.
💻 Uygulama:
constsayilar= [1,2,3];constkatlar= sayilar.map(x => x *2);console.log(katlar);// [2, 4, 6]
Bir listeden sadece bizim istediğimiz şartı sağlayanları süzer, gerisini eler.
💡 Örnek: Sayı listesinden sadece çift olanları seçmek.
💻 Uygulama:
constsayilar= [1,2,3,4,5,6];constciftler= sayilar.filter(x => x %2===0);console.log(ciftler);// [2, 4, 6]
Bir listedeki tüm elemanları toplayıp tek bir sonuç elde etmemizi sağlar.
💡 Örnek: Sepetteki tüm ürünlerin fiyatlarını toplamak.
💻 Uygulama:
constfiyatlar= [10,20,30];consttoplam= fiyatlar.reduce((kumbara,fiyat) => kumbara + fiyat,0);console.log(toplam);// 60
Karmakarışık bir listeyi düzgünce sıraya dizer.
💡 Örnek: Sayıları küçükten büyüğe dizmek.
💻 Uygulama:
constsayilar= [5,1,9];sayilar.sort((a,b) => a - b);console.log(sayilar);// [1, 5, 9]
İnternetten bir bilgi indirirken bilgisayarın donmaması için arka planda bekleme yapmasını sağlarız. Söz vermek (Promise) ve Beklemek (Await) kelimeleri kullanılır.
💡 Örnek: İnternetten hava durumu bilgisinin gelmesini beklemek.
💻 Uygulama:
asyncfunctionveriGetir() {letveri=awaitfetch("https://hava-durumu.com");letsonuc=awaitveri.json();console.log(sonuc);}
Vite aracını kullanarak ilk boş React projemizi bilgisayarda çalıştırırız ve ekrana "Merhaba Dünya" yazarız.
React dünyasında her şey bir Lego parçası gibidir. Sitenin menüsü, logosu, her bir butonu ayrı birer bileşendir (Component). [4, 5]
💡 Örnek: Bir web sitesine tekrar tekrar kullanılabilen şık bir buton yapmak.
💻 Uygulama:
function Buton() {return <button>Beni Tıkla!</button>;}
JSX, JavaScript kodunun içine HTML (yani site tasarım dili) yazmamızı sağlar. Conditional Rendering ise duruma göre tasarım göstermektir. [6]
💡 Örnek: Kullanıcı giriş yaptıysa "Hoş geldin" butonu, yapmadıysa "Giriş Yap" butonu göstermek.
💻 Uygulama:
function GirisEkrani({ girisYapildi }) {return <div>{girisYapildi ? <h1>Hoş geldin!</h1> : <button>Giriş Yap</button>}</div>;}
Bir Lego parçasına dışarıdan renk veya yazı bilgisi göndermektir. [4]
💡 Örnek: Aynı butonu bir kez "Kırmızı", bir kez "Mavi" yapmak.
💻 Uygulama:
function RenkliButon(props) {return <button style={{ backgroundColor: props.renk }}>{props.yazi}</button>;}// Kullanımı: <RenkliButon renk="red" yazi="Sil" />
useState
& Olaylar)
Kullanıcının siteyle etkileşime
girmesidir. Butona tıklamak (onClick)
gibi olayları yakalarız.
Bir sayfanın o anki durumunu hafızada tutmasını sağlar. Sayı her değiştiğinde ekran kendiliğinden güncellenir. [1, 7]
💡 Örnek: Bir sayaç yapıp artı butonuna basınca sayıyı artırmak.
💻 Uygulama:
import { useState } from "react";function Sayac() {const [sayi, setSayi] = useState(0);return <button onClick={() => setSayi(sayi + 1)}>Sayı: {sayi}</button>;}
Bu bölümde öğrendiklerimizle çalışan bir uygulama yaparız. Ekrandaki bir kutuya görev yazarız, ekle deyince listeye eklenir. Yapılan görevlerin üstünü çizebilir veya silebiliriz.
Bir bileşenin içine başka tasarımlar yerleştirmemizi sağlar. Kutu gibi davranır.
💡 Örnek: Etrafı çizgili yeşil bir çerçeve yapıp içine her şeyi koyabilmek.
💻 Uygulama:
function Cerceve({ children }) {return <div style={{ border: "2px solid green" }}>{children}</div>;}// Kullanımı: <Cerceve><p>Bu içindeki yazıdır.</p></Cerceve>
useEffect)Sayfa ilk açıldığında internetten otomatik bilgi çekmek veya bir zamanlayıcı başlatmak için kullanılır.
💡 Örnek: Sayfa açılır açılmaz internetten güncel dolar kurunu çekmek.
💻 Uygulama:
import { useEffect } from "react";useEffect(() => {console.log("Sayfa ilk kez açıldı, veriler yükleniyor...");}, []); // Boş parantez sadece 1 kere çalışmasını sağlar
Bu küçük projede useEffect
kullanarak canlı döviz verileri
çeken ve paraları birbirine çeviren bir hesap makinesi tasarlanır.
React'in siteleri neden bu kadar hızlı çalıştırdığını anlatan mutfak kısmıdır. React, gerçek siteyi değiştirmeden önce hafızada hayali bir kopyasını (Virtual DOM) hazırlar. Sadece değişen küçük parçayı ekrana yansıtır. [1]
useRef
& Özel Kod Yapıları
(Custom Hooks)Sayfadaki bir yazı kutusuna (input) tıklanmadan otomatik odaklanmayı (focus) sağlar. Değiştiğinde sayfayı baştan yenilemeyen gizli bir kutudur.
Kendi özel React aracımızı üretmektir. Kodları temiz tutmaya yarar.
Sitemizde binlerce ürün varsa, her ürün için ayrı sayfa tasarımı yapmayız. Tek bir sayfa yapar, ürünün numarasını (ID) adresten çekeriz.
💡 Örnek: ://site.com
adresindeki 5
sayısını kapmak.
💻 Uygulama:
import { useParams } from "react-router-dom";function UrunDetay() {let { id } = useParams();return <h1>{id} Numaralı Ürünün Sayfası</h1>;}
Tasarım yapmak için CSS dosyasında satırlarca kod yazmak yerine hazır kelimeler (sınıflar) kullanırız.
💡 Örnek: Kırmızı renkli, kalın yazılı, etrafı boşluklu bir yazı oluşturmak.
💻 Uygulama:
<div className="bg-red-500 text-white font-bold p-4">Merhaba Dünyalı!</div>
Sayfa daha ekrana gelmeden önce, internetten gelecek verilerin arka planda erkenden yüklenmesini sağlayan modern yöntemdir.
Bileşenler çok derinleştikçe, en üstteki bilgiyi en alttakine taşımak zorlaşır. Context API, tüm uygulamanın etrafını saran ortak bir radyo yayını gibidir. İsteyen her bileşen bu yayına bağlanıp bilgiyi dinleyebilir.
useReducer)Uygulamadaki butonlar ve işlemler çok karmaşık hale geldiğinde (Örn: Ekle, Sil, Düzenle, Hepsini Temizle, Sırala), tüm bu emirleri tek bir büyük merkezden ("Switch-Case" yapısıyla) yönetmemizi sağlayan gelişmiş hafıza sistemidir.
Çok büyük bir alışveriş sitesi yaptığınızı düşünün. Kullanıcı giriş bilgisi, sepet listesi, gece modu ayarı gibi her şeyi dev bir ortak depoda (Store) saklarız. [3]
İnternetten kullanıcı listesini çekerken "Yükleniyor", "Başarılı" veya "Hata Oluştu" gibi adımları Redux deposuna otomatik bildiren süper yardımcı bir araçtır. [3, 8]
Sitenin boş yere yorulmasını
engeller. Değişmeyen hesaplamaları hafızada tutar (useMemo).
Sayfaları sadece kullanıcı tıkladığında parça parça indirir
(Lazy Loading),
böylece site ışık hızında açılır.
Yazdığımız tüm kodları
paketleyip (npm
run build)
internetteki ücretsiz sunuculara yükleriz. Böylece artık web
sitemizin bir linki olur ve dünyadaki herkes sitemizi ziyaret
edebilir.
================================================================================
Tüm müfredat adımlarını kapsayan, Tailwind CSS ile modern ve şık bir arayüz tasarımına sahip, tamamen çalışan dev bir React uygulaması hazırladım.
Bu tek parça kod içerisinde;
JavaScript temellerinden useState
ve useEffect'e,
useRef
ve Custom
Hook kullanımından
useReducer,
Context
API ve
Redux
mantığına kadar müfredattaki
tüm kritik araçlar bir araya getirilmiştir.
Kodu bilgisayarınızda denemek
için temiz bir React projesine Tailwind CSS kurduktan sonra direkt
App.jsx
dosyanıza yapıştırıp
çalıştırabilirsiniz.
import React, { useState, useEffect, useReducer, useContext, createContext, useRef } from "react";// ==========================================// 1. CONTEXT API (Ortak Hafıza Sistemi)// ==========================================const TemaContext = createContext();// ==========================================// 2. USEREDUCER (Büyük Değişim Kumandası)// ==========================================// Görevleri ekleme, silme ve durumunu değiştirme işlemlerini yönetirconst gorevReducer = (state, action) => {switch (action.type) {case "EKLE":return [...state, { id: Date.now(), metin: action.payload, tamamlandi: false }];case "SIL":return state.filter((g) => g.id !== action.payload);case "DURUM_DEGISTIR":return state.map((g) =>g.id === action.payload ? { ...g, tamamlandi: !g.tamamlandi } : g);default:return state;}};// ==========================================// 3. CUSTOM HOOK (Özel Kod Yapısı)// ==========================================// Döviz çevirici için internetten (simüle edilmiş) anlık veri çeken özel kancaconst useDovizKuru = (paraBirimi) => {const [oran, setOran] = useState(1);const [yukleniyor, setYukleniyor] = useState(false);useEffect(() => {setYukleniyor(true);// USEEFFECT & ASYNC/AWAIT: Arka planda internetten veri çekme taklidiconst kuruGetir = async () => {await new Promise((resolve) => setTimeout(resolve, 800)); // Hızı görmek için beklemeconst kurlar = { USD: 34.2, EUR: 36.8, GBP: 43.1 };setOran(kurlar[paraBirimi] || 1);setYukleniyor(false);};kuruGetir();}, [paraBirimi]); // Dependency Array: Para birimi değiştikçe yeniden çalışırreturn { oran, yukleniyor };};// ==========================================// MAIN APP COMPONENT (Ana Uygulama)// ==========================================export default function App() {const [tema, setTema] = useState("light");return (<TemaContext.Provider value={{ tema, setTema }}><div className={`${tema === "dark" ? "bg-gray-900 text-white" : "bg-gray-50 text-gray-800"} min-h-screen transition-colors duration-300 font-sans`}>{/* TAILWIND CSS: Şık ve modern üst menü tasarımı */}<header className="border-b border-gray-200 dark:border-gray-700 p-4 shadow-sm bg-white dark:bg-gray-800"><div className="max-w-5xl mx-auto flex justify-between items-center"><h1 className="text-2xl font-black bg-gradient-to-r from-blue-600 to-indigo-500 bg-clip-text text-transparent">🚀 React Master Dashboard</h1><buttononClick={() => setTema(tema === "light" ? "dark" : "light")}className="px-4 py-2 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg text-sm font-semibold transition-all shadow-sm">{tema === "light" ? "🌙 Gece Modu" : "☀️ Gündüz Modu"}</button></div></header>{/* Ana İçerik Alanı */}<main className="max-w-5xl mx-auto p-6 grid grid-cols-1 md:grid-cols-2 gap-6">{/* BÖLÜM 1: GÖREV LİSTESİ (useReducer & useRef Örneği) */}<section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700"><h2 className="text-lg font-bold mb-4 flex items-center gap-2">📝 Görev Planlayıcı <span className="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full font-normal">useReducer</span></h2><div className="flex gap-2 mb-4">{/* USEREF: Kutunun içine otomatik odaklanmak veya doğrudan erişmek için */}<inputref={inputRef}type="text"placeholder="Yeni görev yazın..."className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"onKeyDown={(e) => e.key === "Enter" && gorevEkle()}/><buttononClick={gorevEkle}className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl font-medium transition-all">Ekle</button></div>{/* JAVASCRIPT MAP & FILTER: Listeyi ekrana basma ve boşsa kontrolü */}{gorevler.length === 0 ? (<p className="text-sm text-gray-400 text-center py-4">Henüz bir görev eklenmedi.</p>) : (<ul className="space-y-2">{gorevler.map((g) => (<likey={g.id}className="flex justify-between items-center p-3 rounded-xl bg-gray-50 dark:bg-gray-700/50 group border border-gray-100 dark:border-gray-700"><spanonClick={() => dispatch({ type: "DURUM_DEGISTIR", payload: g.id })}className={`cursor-pointer flex-1 text-sm ${g.tamamlandi ? "line-through text-gray-400" : ""}`}>{g.metin}</span><buttononClick={() => dispatch({ type: "SIL", payload: g.id })}className="text-red-500 hover:text-red-700 opacity-0 group-hover:opacity-100 transition-opacity text-sm font-medium px-2">Sil</button></li>))}</ul>)}</section>{/* BÖLÜM 2: DÖVİZ HESAPLAYICI (Custom Hook & useEffect Örneği) */}<section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 flex flex-col justify-between"><div><h2 className="text-lg font-bold mb-4 flex items-center gap-2">💱 Canlı Döviz Çevirici <span className="text-xs bg-purple-100 text-purple-800 px-2 py-0.5 rounded-full font-normal">Custom Hook</span></h2>{/* USESTATE: Girdi değerlerini ve seçilen para birimini hafızada tutar */}<div className="space-y-4"><div><label className="text-xs text-gray-400 block mb-1">Miktar (Döviz Tipi)</label><inputtype="number"value={miktar}onChange={(e) => setMiktar(Number(e.target.value))}className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-purple-500"/></div><div><label className="text-xs text-gray-400 block mb-1">Para Birimi Seçin</label><selectvalue={paraBirimi}onChange={(e) => setParaBirimi(e.target.value)}className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-purple-500"><option value="USD">USD - Amerikan Doları</option><option value="EUR">EUR - Euro</option><option value="GBP">GBP - İngiliz Sterlini</option></select></div></div></div>{/* Hesaplama Sonucu ve Yapay Yüklenme Alanı */}<div className="mt-6 p-4 rounded-xl bg-purple-50 dark:bg-purple-950/30 text-center border border-purple-100 dark:border-purple-900/50">{yukleniyor ? (<span className="text-sm text-purple-500 font-medium animate-pulse">Kurlar güncelleniyor...</span>) : (<div><p className="text-xs text-purple-400">Türk Lirası Karşılığı</p><p className="text-2xl font-black text-purple-600 dark:text-purple-400">{/* JAVASCRIPT REDUCE/MATH MANTIĞI: Hesaplama */}{(miktar * oran).toFixed(2)} TL</p><p className="text-[10px] text-gray-400 mt-1">1 {paraBirimi} = {oran} TL</p></div>)}</div></section>{/* BÖLÜM 3: MERKEZİ REDUX DEPOSU TAKLİDİ (Global State Mantığı) */}<section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 md:col-span-2"><h2 className="text-lg font-bold mb-2 flex items-center gap-2">📦 Global Mağaza Sepeti <span className="text-xs bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded-full font-normal">Redux Toolkit Mantığı</span></h2><p className="text-xs text-gray-400 mb-4">Aşağıdaki sepet verileri, hiyerarşiden bağımsız olarak tüm uygulamayı besleyen "Redux Store" yapısını taklit eder.</p><div className="grid grid-cols-1 sm:grid-cols-3 gap-4">{[{ id: 1, ad: "Kablosuz Kulaklık", fiyat: 1200 },{ id: 2, ad: "Mekanik Klavye", fiyat: 2400 },{ id: 3, ad: "Oyuncu Mouse", fiyat: 850 },].map((urun) => (<div key={urun.id} className="p-4 rounded-xl border border-gray-200 dark:border-gray-700 flex flex-col justify-between items-start bg-gray-50/50 dark:bg-gray-700/30"><div><h4 className="font-semibold text-sm">{urun.ad}</h4><p className="text-xs text-emerald-600 font-bold mt-1">{urun.fiyat} TL</p></div><buttononClick={() => sepeteEkle(urun)}
className="mt-3 w-full bg-emerald-600 hover:bg-emerald-700
text-white text-xs py-2 rounded-lg font-semibold
transition-all"
>
Sepete Ekle
))}
{/* Sepet Detayı ve Toplam Tutar (Reduce kullanımı)
*/}
Sepetteki Ürün Sayısı:{"
"}
{sepet.length}
Toplam Tutar:
{sepet.reduce((toplam, urun) => toplam + urun.fiyat, 0)} TL
</TemaContext.Provider>
);
// ==========================================
// BİLEŞEN İÇİ
TANIMLAMALAR VE FONKSİYONLAR
//
==========================================
// Görev Listesi
State'leri
const [gorevler, dispatch] = useReducer(gorevReducer,
[
{ id: 1, metin: "React Router Kurulumunu İncele",
tamamlandi: true },
{ id: 2, metin: "Tailwind Tasarımını
Tamamla", tamamlandi: false },
]);
const inputRef =
useRef(null);
function gorevEkle() {
const metin =
inputRef.current.value.trim();
if (metin) {
dispatch({
type: "EKLE", payload: metin });
inputRef.current.value
= ""; // Kutuyu temizle
}
}
// Döviz Çevirici State'leri
const [miktar, setMiktar] =
useState(10);
const [paraBirimi, setParaBirimi] =
useState("USD");
const { oran, yukleniyor } =
useDovizKuru(paraBirimi); // Custom Hook'tan verileri çektik
// Sepet (Redux benzeri) State'i
const [sepet, setSepet] =
useState([]);
function sepeteEkle(urun) {
setSepet([...sepet,
urun]);
}
}
### 💎 Kodun İçindeki Müfredat Cevherleri Nelerdir?1. **Görünüm Tasarımı (Tailwind CSS):** Renk geçişleri (`bg-gradient-to-r`), karanlık mod uyumu (`dark:bg-gray-900`), gölgeler (`shadow-xl`) ve oval köşeler (`rounded-2xl`) kullanılarak son derece profesyonel, göze hoş gelen bir arayüz oluşturulmuştur.2. **Hafıza Yönetimi (`useReducer` & `useState`):** Görev listesindeki silme ve ekleme gibi karmaşık emir serileri tek merkezden `useReducer` ile kontrol edilirken, döviz miktar girdileri gibi anlık durumlar `useState` ile yönetilmiştir.3. **Harici Bağlantı (`useEffect` & `Custom Hook`):** Yazılan `useDovizKuru` kancası sayesinde, internetten veri çekme esnasında kullanıcının ekranında beliren yüklenme efektleri (`animate-pulse`) asenkron (`async/await`) mimariyle simüle edilmiştir.4. **Doğrudan Erişim (`useRef`):** Görev kutusuna girilen veriler `useRef` üzerinden okunmuş ve kutu işlem sonrası temizlenmiştir.<FollowUp>Eğer isterseniz projenin bu kısmına:* Sayfalar arası geçişi canlandırmak için **React Router (sayfa yönlendirme) simülasyonu** ekleyebiliriz.* Redux Toolkit'in orijinal **slice ve store kurulum kodlarını** entegre edebiliriz.Uygulamayı tarayıcınızda çalıştırma konusunda bir sorun yaşadınız mı?</FollowUp>
***********************************************************************************************
Bu uzun ve detaylı React/Redux kurs müfredatını sizin için adım adım, parça parça ele alacağız. Harika bir öğrenme rehberi oluşturmak için her bölümü derinlemesine inceleyecek, örnekler ve uygulamalarla zenginleştireceğiz.
İlk olarak en başından, yani sizin özellikle istediğiniz "Öğrenecekleriniz" kısmını açıklayarak başlayalım.
Bu kurs, modern bir web yazılımcısı (Software Engineer) olmanız için gereken tüm modern React ekosistemini size sıfırdan öğretmeyi hedeflemektedir. Maddeler halinde ne anlama geldiğini açıklayalım:
Dinamik Web Uygulamaları Geliştirmek: Statik (sabit) sayfalar yerine, kullanıcının yaptığı hareketlere (tıklama, yazma, arama) anında cevap veren canlı siteler yapmayı öğreneceksiniz.
İş Bulma Odaklı Yazılım Becerileri: Sektörde şirketlerin sizden beklediği temiz kod yazma, problem çözme ve modern mimari tasarlama yeteneklerini kazanacaksınız.
Büyük Projeler ve Pratikler: Sadece teorik bilgi almayacak; büyük uygulamalar, kodlama egzersizleri ve testlerle bilginizi kalıcı hale getireceksiniz.
React Router ile Sayfa Yönetimi: Kullanıcı sayfayı yenilemeden (Facebook veya Twitter'daki gibi) anında sayfalar arası geçiş (Hakkımızda, İletişim, Profil) yapabilen sistemler kuracaksınız.
TypeScript ile Güvenli Kodlama: JavaScript'in esnek ama hata yapmaya açık yapısını, TypeScript kullanarak daha güvenli hale getireceksiniz. Kodunuzu yazarken hataları anında göreceksiniz.
Form Yönetimi ve Doğrulama (Validation): Kullanıcı giriş formları, kayıt formları yapmayı ve kullanıcının şifreyi eksik girmesi veya yanlış e-posta yazması durumunda onu anında uyaran sistemleri yöneteceksiniz.
Redux ve Redux Toolkit ile Durum Yönetimi: Uygulama büyüdükçe (örneğin bir e-ticaret sitesinde sepet, kullanıcı bilgileri, ürünler gibi) verilerin birbirine karışmasını önleyen merkezi bir "veri deposu" (State Management) kurmayı öğreneceksiniz.
API İstekleri ve Veri Akışı: Gerçek dünyadaki sunuculardan (veritabanlarından) veri çekmeyi (örneğin hava durumu verisi, YouTube videoları veya resimler) ve bu veriyi sitede göstermeyi öğreneceksiniz.
Immer ile Performanslı Veri Yönetimi: React'te verileri doğrudan değiştirmek (mutate etmek) yasaktır. Immer kütüphanesi sayesinde verileri güvenle ve performansı düşürmeden güncelleyeceksiniz.
Gelişmiş Veri Tabloları: Büyük verileri (örneğin 1000 adet ürün listesi) ekranda sıralayan, filtreleyen ve arama yapılabilen akıllı tablolar inşa edeceksiniz.
TailwindCSS ile Hızlı Tasarım: Uzun uzun CSS kodları yazmak yerine, modern sınıfları (class) kullanarak çok hızlı ve göze hoş gelen tasarımlar yapacaksınız.
Kursun ilk teknik parçası, React'in temel yapı taşı olan JSX ve Bileşen (Component) mantığıdır.
JSX, JavaScript içinde tıpkı
HTML yazar gibi kod yazmamızı sağlayan özel bir sözdizimidir.
Tarayıcılar JSX kodunu doğrudan anlayamaz. Bu yüzden Babel
adlı bir araç, bizim yazdığımız
HTML benzeri kodları arka planda gerçek JavaScript fonksiyonlarına
(React.createElement)
dönüştürür.
Tek bir ana
kapsayıcı olmalı: İki tane <div>
yan yana doğrudan döndürülemez. Bir dış <div>
veya Boş Etiket (React Fragment <>
... </>) içine alınmalıdır.
Sınıf
isimleri: HTML'deki class
yerine className
kullanılır.
JavaScript çalıştırma:
JSX içinde dinamik bir veri göstermek istiyorsak süslü parantez
{}
kullanırız.
Bir kullanıcının adını ve o anki saati gösteren basit bir bileşen yapalım:
import React from 'react';function ProfilBilgisi() {const kullaniciAdi = "Ahmet Yılmaz";const su AnkiSaat = new Date().toLocaleTimeString();const baslikStili = { color: 'blue', fontSize: '20px' };return (<div className="profil-karti"><h1 style={baslikStili}>Hoş Geldiniz, {kullaniciAdi}!</h1><p>Sisteme giriş saatiniz: {suAnkiSaat}</p><input type="text" autoFocus={true} placeholder="Not bırakın..." /></div>);}export default ProfilBilgisi;
Hatalı (Klasik HTML'den kopyalanan) yapı:
<!-- Bu kod React içinde doğrudan çalışmaz, hata verir --><divclass="kart"><h2style="color: red;">Ürün Açıklaması</h2><inputtype="text"autofocus></div>
Doğru JSX Dönüşümü:
function UrunKarti() {return (<div className="kart"><h2 style={{ color: 'red' }}>Ürün Açıklaması</h2><input type="text" autoFocus={true} /></div>);}
Açıklama:
class
kelimesi className
yapıldı. style
özelliğine çift süslü parantez
içinde bir JavaScript objesi verildi. autofocus
ise camelCase kuralına göre
autoFocus
haline getirildi.
Uygulamamız büyüdükçe aynı kodu tekrar tekrar yazmak yerine, kendisi sabit ama içeriği dışarıdan gelen verilere göre değişen "geliştirilebilir/tekrar kullanılabilir" yapılar kurarız. Bunlara Props denir.
Props, bir ana (Parent) bileşenden alt (Child) bileşene gönderilen bilgi paketleridir. Veri akışı her zaman yukarıdan aşağıya doğrudur. Alt bileşen bu veriyi değiştiremez, sadece okur.
JavaScript'teki Argument
Destructuring (Argüman
Parçalama) yöntemi sayesinde, props.baslik,
props.detay
yazmak yerine doğrudan {
baslik, detay } şeklinde
temiz bir kod yazabiliriz.
Önce dışarıdan veri alan alt
bileşenimizi (ProfileCard.jsx)
tasarlayalım:
import React from 'react';// Argument Destructuring kullanarak props'ları doğrudan aldıkfunction ProfileCard({ isim, kullaniciAdi, gorselLink, altMetin }) {return (<div className="card" style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}><img src={gorselLink} alt={altMetin} style={{ width: '100px', height: '100px' }} /><h3>{isim}</h3><p>@{kullaniciAdi}</p></div>);}export default ProfileCard;
Şimdi bu bileşeni ana bileşenimiz
içinde farklı verilerle kullanalım (App.jsx):
import React from 'react';import ProfileCard from './ProfileCard';function App() {return (<div><h2>Ekibimiz</h2><ProfileCardisim="Canan Dağdeviren"kullaniciAdi="canand"gorselLink="https://picsum.photos"altMetin="Canan profil resmi"/><ProfileCardisim="Aziz Sancar"kullaniciAdi="asancar"gorselLink="https://picsum.photos"altMetin="Aziz profil resmi"/></div>);}export default App;
Uygulamanın kullanıcıyla etkileşime girdiği, ekranın değiştiği ve canlandığı yer burasıdır.
React'te normal değişkenler
değiştiğinde ekran güncellenmez. Ekranın değişmesini (yeniden
çizilmesini - re-render) istiyorsak State
kullanmalıyız. State yönetimi
için React ile birlikte gelen useState
fonksiyonunu (Hook) kullanırız.
useState
bize iki şey verir:
Mevcut durumun
değeri (count)
Bu durumu değiştirecek özel
fonksiyon (setCount)
Önemli Kural: State asla
doğrudan değiştirilemez (count
= count + 1 YAPILAMAZ).
Her zaman değiştirici fonksiyon kullanılmalıdır (setCount(count
+ 1)).
Kullanıcı butona bastıkça listeye yeni bir hayvan ekleyen ve hayvanların üzerine tıklandıkça yanlarındaki kalp ikonunu büyüten bir sistem tasarlayalım.
import React, { useState } from 'react';function HayvanGoster({ tur }) {const [kalpBoyutu, setKalpBoyutu] = useState(10); // Başlangıçta kalp 10 pikselconst kalpArttir = () => {setKalpBoyutu(kalpBoyutu + 5); // Her tıklamada 5 piksel büyüt};return (<div onClick={kalpArttir} style={{ border: '1px solid #ccc', padding: '10px', cursor: 'pointer' }}><span>Evcil Hayvan: {tur}</span><span style={{ fontSize: `${kalpBoyutu}px`, color: 'red', marginLeft: '10px' }}>❤️</span></div>);}function HayvanlarUygulamasi() {const [hayvan Listesi, setHayvanListesi] = useState(['Kedi', 'Köpek']);const rastgeleHayvanEkle = () => {const tumHayvanlar = ['Kedi', 'Köpek', 'Kuş', 'Tavşan', 'At'];const secilen = tumHayvanlar[Math.floor(Math.random() * tumHayvanlar.length)];// DİKKAT: state olan diziyi korumak için push yapmıyoruz, yeni bir dizi oluşturuyoruz (Immutable)setHayvanListesi([...hayvanListesi, secilen]);};return (<div><button onClick={rastgeleHayvanEkle}>Yeni Hayvan Ekle</button><div style={{ marginTop: '20px' }}>{hayvanListesi.map((hayvan, siraNo) => (<HayvanGoster key={siraNo} tur={hayvan} />))}</div></div>);}export default HayvanlarUygulamasi;
Genellikle veriler alt bileşenlerde (örneğin bir arama çubuğunda) üretilir ama en tepedeki ana bileşene gönderilip orada internetten veri aratmak için kullanılır.
React'te props'lar yukarıdan
aşağıya gider demiştik. Peki alt bileşen üst bileşene nasıl
haber uçurur?
Yöntem şu: Üst
bileşen, alt bileşene bir fonksiyon
hediye eder (prop olarak gönderir).
Alt bileşen bu fonksiyonu tetiklediğinde, üst bileşendeki süreç
başlamış olur.
İnternetten veri çekmek için ise
popüler Axios kütüphanesini
ve JavaScript'in asenkron (async/await)
yapısını kullanırız.
Kullanıcının yazdığı kelimeyi alıp internetten (Unsplash benzeri bir sahte servisten) arama yapan bir akış tasarlayalım.
import React, { useState } from 'react';function SearchBar({ onAramaYap }) {const [arananKelime, setArananKelime] = useState('');const formGonderildi = (event) => {event.preventDefault(); // Sayfanın yenilenmesini engelleronAramaYap(arananKelime); // Üst bileşenden gelen fonksiyonu çalıştırıp kelimeyi yukarı yolluyoruz};return (<form onSubmit={formGonderildi}><label>Ne aramak istersiniz?</label><inputtype="text"value={arananKelime}onChange={(e) => setArananKelime(e.target.value)}/><button type="submit">Ara</button></form>);}export default SearchBar;
import React, { useState } from 'react';import SearchBar from './SearchBar';import axios from 'axios';function App() {const [resimler, setResimler] = useState([]);const resimleriInternettenGetir = async (kelime) => {// Temsili bir API adresi ve veri çekme işlemiconst cevap = await axios.get('https://unsplash.com', {headers: { Authorization: 'Client-ID SIZIN_ANAHTARINIZ' },params: { query: kelime }});setResimler(cevap.data.results);};return (<div><SearchBar onAramaYap={resimleriInternettenGetir} /><div>Bulunan Resim Sayısı: {resimler.length}{/* Resimler listelenirken her elemana benzersiz bir 'key' prop'u verilir */}{resimler.map((resim) => (<img key={resim.id} src={resim.urls.small} alt={resim.alt_description} />))}</div></div>);}export default App;
React geliştiricilerinin en çok hata yaptığı yer, state içindeki bir objeyi veya diziyi güncellerken eski veriyi yanlışlıkla silmek ya da doğrudan mutasyona uğratmaktır.
Bir diziden eleman silerken, eklerken veya güncellerken orijinal diziyi bozmayan JavaScript fonksiyonlarını seçmeliyiz:
Eleman
Ekleme: push
yerine Spread Operatörü [...eskiDizi,
yeniEleman]
Eleman
Silme: splice
yerine filter
yöntemi (Filter keeps true - Doğru olanları tutar)
Eleman Güncelleme: Diziyi
baştan sona tarayıp sadece hedef elemanı değiştiren map
yöntemi.
Tüm bu operasyonları tek bir temiz örnek üzerinde inceleyelim:
import React, { useState } from 'react';function KitaplikUygulamasi() {const [kitaplar, setKitaplar] = useState([{ id: 1, isim: 'Nutuk' },{ id: 2, isim: 'Çalıkuşu' }]);const [yeniKitapAdi, setYeniKitapAdi] = useState('');// 1. Kitap Ekleme (Array'e eleman ekleme)const kitapEkle = () => {const eklenecekKitap = {id: Math.round(Math.random() * 10000), // Sahte ID üretimiisim: yeniKitapAdi};setKitaplar([...kitaplar, eklenecekKitap]);setYeniKitapAdi('');};// 2. Kitap Silme (Array'den eleman silme)const kitapSil = (hedefId) => {const kalanKitaplar = kitaplar.filter((kitap) => {return kitap.id !== hedefId; // ID'si eşleşmeyenler kalsın, eşleşen silinsin});setKitaplar(kalanKitaplar);};// 3. Kitap Güncelleme (Array içindeki objeyi modifiye etme)const kitapIsmiGuncelle = (hedefId, yeniIsim) => {const guncelKitaplar = kitaplar.map((kitap) => {if (kitap.id === hedefId) {return { ...kitap, isim: yeniIsim }; // ID doğruysa ismini değiştir}return kitap; // Değilse aynen bırak});setKitaplar(guncelKitaplar);};return (<div style={{ padding: '20px' }}><inputvalue={yeniKitapAdi}onChange={(e) => setYeniKitapAdi(e.target.value)}placeholder="Kitap adı yazın"/><button onClick={kitapEkle}>Ekle</button><ul>{kitaplar.map((kitap) => (<li key={kitap.id} style={{ margin: '10px 0' }}><span>{kitap.isim}</span><button onClick={() => kitapSil(kitap.id)} style={{ marginLeft: '10px' }}>Sil</button><button onClick={() => kitapIsmiGuncelle(kitap.id, kitap.isim + " (Güncellendi)")} style={{ marginLeft: '5px' }}>Güncelle</button></li>))}</ul></div>);}export default KitaplikUygulamasi;
Harika, hiç vakit kaybetmeden kaldığımız yerden, yani React'in en güçlü ve en çok karıştırılan mekanizmalarından biri olan useEffect ve Veri Kalıcılığı (Data Persistence) konularından devam ediyoruz.
Aynı disiplinle; dakikaları, süreleri ve "Önizleme" gibi gereksiz kelimeleri temizleyerek, konuları derinlemesine açıklamaya, örnekler ve uygulamalarla zenginleştirmeye devam ediyoruz.
Uygulamamızda tarayıcıyı
yenileyince verilerin kaybolmasını istemiyorsak, bir veritabanı
veya sahte bir API (JSON Server) kullanırız. Bu verileri sayfa ilk
açıldığında otomatik olarak çekmek için ise useEffect
mekanizmasına ihtiyaç duyarız.
useEffect,
React bileşenlerinin yaşam döngüsünde (Lifecycle) belirli
anlarda yan etkileri (API'den veri çekmek, zamanlayıcı başlatmak,
event dinleyicisi eklemek) çalıştırmak için kullanılır.
useEffect
iki argüman alır: Birinci argüman
çalışacak fonksiyon,
ikinci argüman ise Bağımlılık Dizisidir (Dependency
Array). Bu dizinin içeriğine
göre useEffect
üç farklı şekilde davranır:
Dizi hiç
verilmezse (useEffect(()
=> {})): Bileşen her render
olduğunda (her state değişiminde) tekrar tekrar çalışır.
(Dikkat: İçinde state değiştirirseniz sonsuz döngüye girer!)
Boş dizi
verilirse (useEffect(()
=> {}, [])): Bileşen ekrana ilk
geldiğinde (Mount anında) sadece bir kez çalışır.
API'den ilk verileri çekmek için en ideal yer burasıdır.
Dizinin içine değişken
konursa (useEffect(()
=> {}, [sayac])): Bileşen ilk
açıldığında ve listenin içindeki sayac
değişkeni her değiştiğinde çalışır.
useEffect
içinden geriye bir fonksiyon
döndürebilirsiniz. Bu fonksiyon, bileşen ekrandan kaybolurken
(Unmount) veya efekt bir sonraki sefer çalışmadan hemen önce
çalışır. Tarayıcı hafızasını temizlemek (örneğin arka
plandaki click event dinleyicilerini silmek) için kullanılır.
Hem API'den veri çeken hem de arka planda kullanıcının ekranda nereye tıkladığını takip eden bir uygulama yapalım:
import React, { useState, useEffect } from 'react';import axios from 'axios';function KullaniciAyrinti({ kullaniciId }) {const [kullanici, setKullanici] = useState(null);// 1. Durum: kullaniciId değiştikçe internetten yeni verileri çekuseEffect(() => {const veriGetir = async () => {try {// Temsili yerel JSON Server adresiconst cevap = await axios.get(`http://localhost:3001/users/${kullaniciId}`);setKullanici(cevap.data);} catch (hata) {console.error("Veri çekilirken hata oluştu:", hata);}};veriGetir();}, [kullaniciId]); // kullaniciId değiştikçe bu fonksiyon tetiklenir// 2. Durum: Cleanup (Temizlik) mekanizmasının gösterimiuseEffect(() => {const tiklamaTakip = (etkinlik) => {console.log("Tıklanan koordinat:", etkinlik.clientX, etkinlik.clientY);};// Ekrana tıklandığında çalışacak global dinleyici ekliyoruzdocument.body.addEventListener('click', tiklamaTakip);// Temizlik fonksiyonu: Bileşen ekrandan kalktığında dinleyiciyi kaldırırreturn () => {document.body.removeEventListener('click', tiklamaTakip);console.log("Dinleyici kaldırıldı, hafıza temizlendi.");};}, []); // Boş dizi: Sadece ilk açılışta dinleyici kurulur, kapanışta silinirif (!kullanici) return <div>Yükleniyor...</div>;return (<div style={{ border: '2px solid green', padding: '15px' }}><h3>{kullanici.name}</h3><p>E-posta: {kullanici.email}</p></div>);}export default KullaniciAyrinti;
Projeler büyüdükçe, en üstteki veriyi en alttaki bileşene göndermek için aradaki 5-6 bileşenden paslayarak geçirmek zorunda kalırız. Buna Prop-Drilling (Prop Delme) denir ve kodu okumayı çok zorlaştırır.
Context API, uygulamamızın
etrafına görünmez bir veri bulutu sarmamızı sağlar. Bu bulutun
içine koyduğumuz state'lere ve fonksiyonlara, aradaki hiçbir
bileşene dokunmadan, en alttaki bileşenden doğrudan useContext
Hook'u ile erişebiliriz.
createContext: Veri havuzunu yaratır.
Provider (Sağlayıcı): Veriyi alt bileşenlere dağıtan distribütördür.
useContext: Tüketici bileşenin veri havuzundan bilgi çekmesini sağlar.
Gelin, tüm uygulamaya yön veren bir Context mimarisi kuralım.
TemaContext.jsx)import React, { createContext, useState } from 'react';// 1. Havuzu oluşturdukexport const TemaContext = createContext();// 2. Özel bir Sağlayıcı (Custom Provider) bileşeni yazıyoruzexport function TemaProvider({ children }) {const [tema, setTema] = useState('gunduz'); // varsayılan tema gunduzconst temaDegistir = () => {setTema(tema === 'gunduz' ? 'gece' : 'gunduz');};// Paylaşılacak verileri bir obje haline getiriyoruzconst paylasilanlar = {tema,temaDegistir};return (<TemaContext.Provider value={paylasilanlar}>{children}</TemaContext.Provider>);}
index.jsx
veya App.jsx)import React from 'react';import { TemaProvider } from './TemaContext';import SayfaIcerigi from './SayfaIcerigi';function App() {return (// Uygulamayı veri bulutuyla sarmaladık<TemaProvider><SayfaIcerigi /></TemaProvider>);}export default App;
SayfaIcerigi.jsx)import React, { useContext } from 'react';import { TemaContext } from './TemaContext'; // Havuzu içe aktarfunction SayfaIcerigi() {// Havuzdan ihtiyacımız olanları tek satırda çekiyoruzconst { tema, temaDegistir } = useContext(TemaContext);const sayfaStili = {backgroundColor: tema === 'gunduz' ? '#ffffff' : '#333333',color: tema === 'gunduz' ? '#000000' : '#ffffff',height: '100vh',padding: '20px'};return (<div style={sayfaStili}><h2>Context API Modülü</h2><p>Şu anki uygulama teması: <strong>{tema.toUpperCase()}</strong></p><button onClick={temaDegistir}>Temayı Değiştir</button></div>);}export default SayfaIcerigi;
Kendi yönlendirme (Routing)
sistemimizi kurmayı, window.history.pushState
fonksiyonunun mantığını ve DOM
hiyerarşisini delerek tam ekran açılan Modalları (Açılır
pencereleri) nasıl yapacağımızı inceleyeceğiz.
Normalde yazdığımız her
bileşen, içinde bulunduğu Parent bileşenin HTML etiketlerinin
altına eklenir. Ancak bir Modal (Açılır pencere)
yapıyorsanız ve üst bileşende
overflow:
hidden veya
garip bir CSS konumlandırması (position:
relative)
varsa, modal ekranın dışına taşabilir veya düzgün ortalanamaz.
React Portals (ReactDOM.createPortal),
bir bileşeni kodsal olarak istediğimiz yerde yazıp, HTML ağacında
tamamen farklı bir yere (örneğin doğrudan <body>
etiketinin altındaki ayrı bir div
içine) ışınlamamızı sağlar.
useRef
ise React dünyasında HTML
elementlerine doğrudan el uzatmamızı (referans almamızı)
sağlayan akıllı bir kancadır.
Önce public/index.html
dosyamızda modal için ayrı bir
yer açtığımızı varsayalım:
<divid="root"></div><!-- Modal buraya ışınlanacak --><divid="modal-container"></div>
Şimdi bu modal-container
içine ışınlanacak Modal.jsx
bileşenini kodlayalım:
import React, { useEffect } from 'react';import ReactDOM from 'react-dom';function Modal({ kapatmaFonksiyonu, children, aksiyonlar }) {// Modal açıkken arka plandaki sayfanın kaydırılmasını engelleme (UX kuralı)useEffect(() => {document.body.style.overflow = 'hidden';// Bileşen ekrandan kalkınca eski haline getir (Cleanup)return () => {document.body.style.overflow = 'unset';};}, []);return ReactDOM.createPortal(// 1. Arka plan karartması (Gray overlay)<divonClick={kapatmaFonksiyonu}style={{position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)',display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 999}}>{/* 2. Modal Kutusu (İçeriğe tıklanınca modalın kapanmasını engellemek için stopPropagation kullanıyoruz) */}<divonClick={(e) => e.stopPropagation()}style={{ backgroundColor: 'white', padding: '20px', borderRadius: '8px', minWidth: '300px' }}><div className="modal-icerik">{children}</div><div className="modal-butonlar" style={{ marginTop: '20px', display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>{aksiyonlar}</div></div></div>,document.getElementById('modal-container') // Buraya ışınla!);}export default Modal;
App.jsx):import React, { useState } from 'react';import Modal from './Modal';function App() {const [modalAcik, setModalAcik] = useState(false);const modalButonlari = (<><button onClick={() => setModalAcik(false)}>İptal</button><button onClick={() => { alert("Silindi!"); setModalAcik(false); }} style={{ backgroundColor: 'red', color: 'white' }}>Onayla</button></>);return (<div><button onClick={() => setModalAcik(true)}>Hesabı Sil</button>{modalAcik && (<Modal kapatmaFonksiyonu={() => setModalAcik(false)} aksiyonlar={modalButonlari}><h3>Dikkat!</h3><p>Bu hesabı silmek istediğinize emin misiniz? Bu işlem geri alınamaz.</p></Modal>)}</div>);}export default App;
Kursun en yetenekli tasarım
kalıplarından biri, içerisindeki verinin ne olduğunu bilmeyen,
tamamen dışarıdan verilen bir konfigürasyon (config)
dizisine göre kendi kendini çizen akıllı tablolardır.
Bileşene verileri (data)
düz bir dizi olarak veririz. Kolon başlıklarını ve o kolonun
veriyi nasıl ekrana basacağını ise bir fonksiyon (render)
yardımıyla config
objesinde tanımlarız.
JavaScript'in localeCompare
fonksiyonu ile kelimeleri,
matematiksel çıkarma ile de sayıları sıralarız.
import React, { useState } from 'react';function SortableTable({ data, config, keyFn }) {const [sortOrder, setSortOrder] = useState(null); // 'asc' (artan), 'desc' (azalan), nullconst [sortBy, setSortBy] = useState(null); // Hangi kolona göre sıralanıyor?const handleHeaderClick = (label, sortValueFn) => {// Sıralama fonksiyonu yoksa işlem yapma (O kolon sıralanamaz demektir)if (!sortValueFn) return;if (sortBy !== label) {setSortBy(label);setSortOrder('asc');} else if (sortOrder === 'asc') {setSortOrder('desc');} else {setSortBy(null);setSortOrder(null);}};// Sıralanmış veriyi hesaplalet sortedData = data;if (sortOrder && sortBy) {const { sortValue } = config.find(col => col.label === sortBy);sortedData = [...data].sort((a, b) => {const valueA = sortValue(a);const valueB = sortValue(b);const reverseOrder = sortOrder === 'asc' ? 1 : -1;if (typeof valueA === 'string') {return valueA.localeCompare(valueB) * reverseOrder;} else {return (valueA - valueB) * reverseOrder;}});}return (<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '20px' }}><thead><tr style={{ backgroundColor: '#f4f4f4', borderBottom: '2px solid #ccc' }}>{config.map((column) => (<thkey={column.label}onClick={() => handleHeaderClick(column.label, column.sortValue)}style={{ padding: '10px', cursor: column.sortValue ? 'pointer' : 'default', textAlign: 'left' }}>{column.label} {sortBy === column.label ? (sortOrder === 'asc' ? '🔼' : '🔽') : ''}</th>))}</tr></thead><tbody>{sortedData.map((rowData) => (<tr key={keyFn(rowData)} style={{ borderBottom: '1px solid #eee' }}>{config.map((column) => (<td key={column.label} style={{ padding: '10px' }}>{column.render(rowData)}</td>))}</tr>))}</tbody></table>);}// ---- UYGULAMA KULLANIMI ----function MeyveUygulamasi() {const meyveler = [{ id: 1, ad: 'Elma', renk: 'bg-red-500', skor: 5 },{ id: 2, ad: 'Muz', renk: 'bg-yellow-500', skor: 3 },{ id: 3, ad: 'Kivi', renk: 'bg-green-500', skor: 4 },];const tabloYapilandirmasi = [{label: 'Meyve Adı',render: (meyve) => meyve.ad,sortValue: (meyve) => meyve.ad},{label: 'Renk Kutusu',render: (meyve) => <div style={{ width: '20px', height: '20px', borderRadius: '4px' }} className={meyve.renk}></div>},{label: 'Lezzet Skoru',render: (meyve) => <strong>{meyve.skor} / 5</strong>,sortValue: (meyve) => meyve.skor}];return (<div style={{ padding: '20px' }}><h2>Sıralanabilir Dinamik Tablo Bileşeni</h2><SortableTable data={meyveler} config={tabloYapilandirmasi} keyFn={(m) => m.id} /></div>);}export default MeyveUygulamasi;
React'te sadece arayüzü değil, state mantığını da paketleyip birden fazla bileşende tekrar kullanabiliriz.
Eğer iki farklı bileşende
tamamen aynı state tanımlamalarını, useEffect
tetikleyicilerini ve form yönetim
kurallarını yazıyorsanız, bu mantığı alıp ismi use
ile başlayan bağımsız bir
fonksiyona (useSayac,
useForm,
useSort)
dönüştürebilirsiniz. Custom Hook'lar içlerinde normal React
Hook'larını (useState,
useEffect)
barındırabilirler.
Önce kancamızı yazalım
(useCounter.jsx):
import { useState, useEffect } from 'react';function useCounter(baslangicDegeri = 0, artisMiktari = 1) {const [count, setCount] = useState(baslangicDegeri);useEffect(() => {console.log("Sayaç güncel değeri:", count);}, [count]);const arttir = () => setCount(count + artisMiktari);const azalt = () => setCount(count - artisMiktari);const sifirla = () => setCount(baslangicDegeri);// Dışarıya bileşenlerin kullanabileceği değişkenleri ve fonksiyonları dizi veya obje olarak fırlatıyoruzreturn { count, arttir, azalt, sifirla };}export default useCounter;
Şimdi bu kancayı yepyeni bir
sayfada çocuk oyuncağı gibi kullanalım (SayacSayfasi.jsx):
import React from 'react';import useCounter from './useCounter';function SayacSayfasi() {// Tek satırda tüm sayaç lojiğini içeri aktardık!const { count, arttir, azalt, sifirla } = useCounter(10, 5); // 10'dan başla, 5'er 5'er artreturn (<div style={{ padding: '20px', textAlign: 'center' }}><h2>Özel Kanca (Custom Hook) Pratiği</h2><div style={{ fontSize: '32px', margin: '20px' }}>{count}</div><button onClick={arttir} style={{ marginRight: '10px' }}>Beş Arttır</button><button onClick={azalt} style={{ marginRight: '10px' }}>Beş Azalt</button><button onClick={sifirla}>Sıfırla</button></div>);}export default SayacSayfasi;
Harika, tempomuzu hiç düşürmeden tam gaz devam ediyoruz. Kursun en can alıcı, mülakatlarda en çok soru gelen ve büyük projelerin yönetim merkezini oluşturan useReducer, Redux Toolkit ve RTK Query dünyasına giriş yapıyoruz.
Süreleri, dakikaları ve "Önizleme" gibi işlem yapılmayacak kelimeleri yine temizleyerek; derinlemesine konu anlatımları, temiz kod örnekleri ve uygulamalarla devam ediyoruz.
useState
basit durumlar için harikadır.
Ancak bir bileşende birden fazla birbiriyle ilişkili state varsa ve
bu state'ler karmaşık kurallarla güncelleniyorsa, useReducer
kullanmak işleri çok daha düzenli
hale getirir.
useReducer
mantığı, bir şirketteki iş
akışına benzer:
State (Durum): Şirketin mevcut kasası, deposu, yani verileridir.
Action
(Eylem): Ne yapmak istediğinizi belirten bir emir pusulasıdır.
Genellikle iki bilgi içerir: type
(ne yapılacağı, örn: 'KAPASITE_ARTTIR') ve payload
(işlem yapılacak veri veya miktar).
Dispatch (Sevk Etmek): Bu emir pusulasını (Action) merkeze gönderen tetikleyicidir.
Reducer (Düzenleyici): Asla doğrudan değiştirilemeyen ana state'i alan, gelen action pusulasına bakan ve kurallara göre yepyeni bir state objesi üreten saf (pure) bir JavaScript fonksiyonudur.
State'i
doğrudan mutasyona uğratamazsınız: state.count
= 5 yazamazsınız. Her zaman yeni bir obje
döndürmelisiniz.
Saf (Pure) olmalıdır:
İçinde rastgele sayılar (Math.random),
tarih işlemleri (new
Date()) veya API istekleri barındırmamalıdır.
Sadece girdi alıp çıktı vermelidir.
Bir sayıyı hem arttırıp azaltabildiğimiz hem de input alanına yazılan sayı kadar toplu ekleme yapabildiğimiz bir sistem kuralım.
import React, { useReducer } from 'react';// 1. Eylem Tiplerini Sabit (Constant) olarak tanımlıyoruz (Yazım hatalarını önlemek için)const ETKINLIK = {ARTTIR: 'sayac/arttir',AZALT: 'sayac/azalt',GIRISI_AYARLA: 'form/giris_ayarla',TOPLU_EKLE: 'sayac/toplu_ekle'};// 2. Başlangıç Durumu (Initial State)const baslangicDurumu = {sayac: 0,girdidekiSayi: 0};// 3. Reducer Fonksiyonu (Switch-Case yapısı topluluk kuralıdır)function reducer(state, action) {switch (action.type) {case ETKINLIK.ARTTIR:return { ...state, sayac: state.sayac + 1 };case ETKINLIK.AZALT:return { ...state, sayac: state.sayac - 1 };case ETKINLIK.GIRISI_AYARLA:return { ...state, girdidekiSayi: action.payload };case ETKINLIK.TOPLU_EKLE:return {...state,sayac: state.sayac + state.girdidekiSayi,girdidekiSayi: 0 // İşlem bitince input alanını sıfırla};default:return state; // Bilinmeyen bir eylem gelirse durumu aynen koru}}function ReducerSayfasi() {// useReducer bize güncel state'i ve eylemleri tetikleyecek dispatch fonksiyonunu verirconst [state, dispatch] = useReducer(reducer, baslangicDurumu);return (<div style={{ padding: '20px', maxWidth: '400px', margin: 'auto' }}><h2>useReducer Kontrol Paneli</h2><div style={{ fontSize: '24px', marginBottom: '15px' }}>Mevcut Sayı: {state.sayac}</div><button onClick={() => dispatch({ type: ETKINLIK.ARTTIR })}>+1 Arttır</button><button onClick={() => dispatch({ type: ETKINLIK.AZALT })} style={{ marginLeft: '10px' }}>-1 Azalt</button><div style={{ marginTop: '20px' }}><inputtype="number"value={state.girdidekiSayi || ''}onChange={(e) => dispatch({type: ETKINLIK.GIRISI_AYARLA,payload: parseInt(e.target.value) || 0})}placeholder="Miktar yazın"/><button onClick={() => dispatch({ type: ETKINLIK.TOPLU_EKLE })} style={{ marginLeft: '10px' }}>Yazılan Miktarı Ekle</button></div></div>);}export default ReducerSayfasi;
useReducer
harikadır ama sadece o bileşenin
içinde yaşar. Uygulamanın en ucundaki bir bileşenden en diğer
ucundaki bileşene kadar ortak bir veri deposu yönetmek istiyorsak,
endüstri standardı olan Redux Toolkit kütüphanesini
kullanırız.
Eski Redux yazmak çok fazla kod kalabalığı (boilerplate) gerektiriyordu. Redux Toolkit (RTK) ise bize Slice (Dilim) yapısını sundu. Bir Slice; o konuya ait başlangıç durumunu, reducer'ları ve action üreticilerini tek bir çatı altında otomatik olarak oluşturur.
Ayrıca RTK, arka planda Immer
kütüphanesini kullandığı için
reducer fonksiyonlarının içinde sanki state'i doğrudan
değiştiriyormuş gibi rahatça kod yazmamıza (state.liste.push())
izin verir. Immer bunu güvenli bir kopyaya dönüştürür.
configureStore: Tüm küçük dilimlerin (slices) birleştiği ana mega depoyu kurar.
useDispatch: Bileşenlerden depoya eylem (action) göndermemizi sağlar.
useSelector: Depodaki verileri seçip bileşenin içine çekmemizi sağlar.
Gelin, iki farklı dilimi (Şarkılar ve Filmler) yöneten merkezi bir Redux deposu kuralım.
slices/songSlice.jsx)import { createSlice } from '@reduxjs/toolkit';const songSlice = createSlice({name: 'song',initialState: [],reducers: {// Immer sayesinde doğrudan push yapabiliyoruz!addSong(state, action) {state.push(action.payload);},removeSong(state, action) {// payload olarak gelen şarkının sırasını (index) bulup diziden siliyoruzconst index = state.indexOf(action.payload);if (index !== -1) {state.splice(index, 1);}}}});// Action creator'ları otomatik üretilir, dışa aktarıyoruzexport const { addSong, removeSong } = songSlice.actions;// Store'a eklemek için ana reducer'ı dışa aktarıyoruzexport default songSlice.reducer;
store/index.jsx)import { configureStore } from '@reduxjs/toolkit';import songReducer from '../slices/songSlice';export const store = configureStore({reducer: {// Depodaki 'songs' anahtarını songReducer yöneteceksongs: songReducer}});
index.jsx)import React from 'react';import ReactDOM from 'react-dom/client';import App from './App';import { store } from './store';import { Provider } from 'react-redux'; // Sağlayıcı kütüphaneconst root = ReactDOM.createRoot(document.getElementById('root'));root.render(// Depoyu tüm uygulamaya dağıtıyoruz<Provider store={store}><App /></Provider>);
App.jsx)import React from 'react';import { useDispatch, useSelector } from 'react-redux';import { addSong, removeSong } from './slices/songSlice';function App() {const dispatch = useDispatch();// Depodaki 'songs' dizisini anlık olarak dinliyoruzconst sarkilar = useSelector((state) => {return state.songs;});const yeniSarkiEklemeSüreci = () => {const sahteSarkilar = ["Aramam", "Boşvermişim Dünyaya", "Dön Bak Dünyaya", "Ele Güne Karşı"];const rastgeleSarki = sahteSarkilar[Math.floor(Math.random() * sahteSarkilar.length)];// Depoya şarkı ekleme emrini gönderiyoruzdispatch(addSong(rastgeleSarki));};return (<div style={{ padding: '20px' }}><h3>Müzik Listem</h3><button onClick={yeniSarkiEklemeSüreci}>Listeye Rastgele Şarkı Ekle</button><ul>{sarkilar.map((sarki, sira) => (<li key={sira} style={{ marginTop: '10px' }}>{sarki}<buttononClick={() => dispatch(removeSong(sarki))}style={{ marginLeft: '15px', color: 'red' }}>Kaldır</button></li>))}</ul></div>);}export default App;
Büyük uygulamalarda asenkron API isteklerini (Thunks) manuel yönetmek, yükleniyor (loading) ve hata (error) durumlarını tek tek state'lere bağlamak çok yorucudur. RTK Query, veri çekme (data fetching) işini tamamen otomatize eden muazzam bir alt kütüphanedir.
RTK Query, verileri otomatik olarak hafızada (cache) tutar. Örneğin A sayfasında kullanıcı listesini çektiniz, B sayfasına gidip tekrar A sayfasına dönerseniz internete bir daha istek atmaz; hafızadaki veriyi anında ekrana basar. Buna Request De-Duplication denir.
Peki sunucuda bir veri güncellendiğinde veya yeni bir eleman eklendiğinde ekrandaki eski önbellek verisi nasıl yenilenir? İşte burada Etiket Sistemi (Tag Invalidation) devreye girer:
Provides Tags (Etiket Sağlar): Veri çeken sorgu (Query) bu verilere bir etiket yapıştırır (Örn: 'Album').
Invalidates Tags (Etiketi Geçersiz Kılar): Veri ekleyen veya silen işlem (Mutation), o etiketi havaya uçurur. RTK Query, etiketin patladığını görünce arka planda gidip güncel listeyi sunucudan otomatik olarak tekrar çeker (Refetch).
RTK Query ile hem veri çeken hem de veri ekleyen akıllı bir API mekanizması kuralım.
store/albumsApi.jsx)import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';export const albumsApi = createApi({reducerPath: 'albums', // Store'daki anahtar ismibaseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001' }), // Temel URLtagTypes: ['Album'], // Kullanılacak etiket tanımlarıendpoints: (builder) => ({// 1. Veri Çekme Sorgusu (Query)fetchAlbums: builder.query({providesTags: ['Album'], // Bu sorgu 'Album' etiketiyle işaretlendiquery: (user) => {return {url: '/albums',params: { userId: user.id },method: 'GET'};}}),// 2. Veri Değiştirme/Ekleme İşlemi (Mutation)addAlbum: builder.mutation({invalidatesTags: ['Album'], // Bu işlem yapıldığında 'Album' etiketli tüm önbellekleri sil ve yenile!query: (user) => {return {url: '/albums',method: 'POST',body: {userId: user.id,title: 'Yeni Harika Albüm ' + Math.floor(Math.random() * 100)}};}})})});// RTK Query bizim için otomatik olarak kancalar üretti! İsimlendirme kuralı sabittir.export const { useFetchAlbumsQuery, useAddAlbumMutation } = albumsApi;
AlbumsList.jsx)import React from 'react';import { useFetchAlbumsQuery, useAddAlbumMutation } from './store/albumsApi';function AlbumsList({ user }) {// Sorgu kancası bize yüklenme durumunu, hatayı ve gelen veriyi otomatik nesne içinde verirconst { data, error, isFetching } = useFetchAlbumsQuery(user);// Mutasyon kancası ise tetikleyeceğimiz bir fonksiyon ve işlem durumunu dönerconst [addAlbum, mutationResults] = useAddAlbumMutation();const albümOlusturmaSüreci = () => {addAlbum(user); // API'ye POST isteği gönderir ve etiket sistemini tetikler};if (isFetching) return <div>Albümler yükleniyor, lütfen bekleyin...</div>;if (error) return <div>Veriler getirilirken bir hata oluştu!</div>;return (<div style={{ padding: '15px', border: '1px dashed purple', marginTop: '10px' }}><h4>{user.name} İsimli Kullanıcının Albümleri</h4><buttondisabled={mutationResults.isLoading}onClick={albümOlusturmaSüreci}>{mutationResults.isLoading ? 'Ekleniyor...' : 'Yeni Albüm Ekle'}</button><ul style={{ marginTop: '15px' }}>{data && data.map((album) => (<li key={album.id} style={{ padding: '5px 0' }}>📁 {album.title}</li>))}</ul></div>);}export default AlbumsList;
Harika, enerjimizi ve tempomuzu koruyarak müfredatın son ve en modern zirve noktalarına giriş yapıyoruz: TypeScript ile Tip Güvenliği, React Router v6+ Mekanizmaları (Loaders) ve Legacy (Geçmiş) Versiyon Karşılaştırmaları.
Yine dakikaları, süreleri ve "Önizleme" gibi işlem yapılmayacak kelimeleri tamamen temizleyerek; derinlemesine konu anlatımları, temiz kod örnekleri ve mülakat kalitesinde uygulamalarla rehberimizi tamamlıyoruz.
JavaScript çok esnektir ama bir bileşene yanlış veri gönderdiğinizde (örneğin sayı bekleyen bir yere metin yolladığınızda) uygulama çalışma anında (runtime) çökebilir. TypeScript, bu hataları henüz kodu yazarken (derleme anında) yakalamamızı sağlar.
TypeScript dünyasında her değişkenin, fonksiyon argümanının ve bileşen props'unun tipini önceden ilan ederiz:
Interface (Arayüz): Bir nesnenin (object) veya bileşen props'unun hangi alanlara sahip olması gerektiğini ve bu alanların tiplerini belirten kontratlardır.
Optional
Properties (?):
Bir özelliğin zorunlu olmadığını, opsiyonel olduğunu
belirtir.
Type
Narrowing (Tip Daraltma): string
| number gibi birden fazla tip alabilen bir
değişkeni, typeof
kontrolüyle güvenli bir şekilde tek bir tipe indirgeyip hata
yapmayı önleme işlemidir.
Generics (Genel Tipler): İçine alacağı tipi dışarıdan parametre olarak kabul eden, böylece farklı veri tipleriyle kusursuzca çalışabilen esnek fonksiyonlar veya yapılar kurmamızı sağlar.
Props alan temiz bir TypeScript
bileşeni (UrunKarti.tsx)
tasarlayalım:
importReactfrom'react';// 1. Props'ların uymak zorunda olduğu kuralları (Kontratı) yazıyoruzinterfaceUrunKartiProps{id:number;baslik:string;fiyat:number;indirim Orani?:number;// Opsiyonel (Zorunlu değil)onSecimYap: (urunId:number) =>void;// Fonksiyon tipi tanımı}// 2. React.FC (Functional Component) kullanarak tipleri bileşenimize giydiriyoruzconstUrunKarti: React.FC<UrunKartiProps> = ({id,baslik,fiyat,indirimOrani,onSecimYap}) => {return(<divstyle={{border:'1px solid #ddd',padding:'15px',borderRadius:'6px'}}><h4>{baslik}</h4><p>Fiyat:{fiyat}TL</p>{/* Tip Daraltma / Varlık Kontrolü */}{indirimOrani && <spanstyle={{color:'green'}}>İndirim: %{indirimOrani}</span>}<buttononClick={() => onSecimYap(id)} style={{display:'block',marginTop:'10px'}}>Ürünü Seç</button></div>);};exportdefaultUrunKarti;
Farklı türdeki dizilerin (sayı, kelime veya obje) ilk elemanını güvenli bir şekilde dönen genel bir fonksiyon tasarlayalım:
// 'T' harfi dışarıdan gelecek rastgele bir veri tipini temsil eder (Generic)functionilkElemaniGetir<T>(dizi: T[]): T {returndizi[0];}// Kullanım Örnekleri:constsayilar=;constilkSayi= ilkElemaniGetir<number>(sayilar);// Sonuç: 10 (Tipi kesinlikle number)constisimler= ["Ahmet","Mehmet","Can"];constilkIsim= ilkElemaniGetir<string>(isimler);// Sonuç: "Ahmet" (Tipi kesinlikle string)
Geleneksel React Router
kullanımında önce sayfa açılır, ardından sayfa içindeki
useEffect
tetiklenir ve API'den veri
beklenirdi. Bu durum ekranda sürekli "Yükleniyor..."
yazısı görmemize neden olurdu. Modern React Router v6 ile birlikte
gelen Loader yapısı
bu akışı tamamen değiştirdi.
Modern yaklaşımda, sayfa daha
ekrana çizilmeden (render olmadan) önce yönlendirici (Router) arka
planda verileri çeker (loader
fonksiyonu ile). Veri hazır
olduğunda sayfa anında tam dolu olarak ekrana gelir. Kullanıcı
deneyimini (UX) muazzam derece artıran bir yöntemdir.
createBrowserRouter: Modern rota kurallarını (Loader'lar dahil) nesne tabanlı tanımlamamızı sağlar.
useLoaderData: Loader fonksiyonunun internetten çekip getirdiği verilere bileşen içinden erişmemizi sağlar.
useParams: URL üzerindeki
dinamik parametreleri (Örn: /detay/:id
kısmındaki id
değerini) okumamızı sağlar.
Bir kütüphane projesindeki paket detaylarını sayfa açılmadan önce çeken akıllı bir yapı kuralım.
router.tsx)import { createBrowserRouter } from 'react-router-dom';import PaketDetaySayfasi from './PaketDetaySayfasi';import axios from 'axios';export const router = createBrowserRouter([{path: '/paketler/:paketAdi', // Dinamik rotaelement: <PaketDetaySayfasi />,// SİHİRLİ BÖLÜM: Sayfa açılmadan hemen önce bu fonksiyon otomatik çalışırloader: async ({ params }) => {// URL'deki :paketAdi parametresini alıyoruzconst { paketAdi } = params;// Veriyi sayfa render edilmeden önce çekiyoruzconst cevap = await axios.get(`https://npmjs.org{paketAdi}`);// Döndürdüğümüz bu veri doğrudan bileşenin içine akacakreturn cevap.data;}}]);
PaketDetaySayfasi.jsx)import React from 'react';import { useLoaderData, useParams } from 'react-router-dom';function PaketDetaySayfasi() {// URL'deki parametreye ihtiyacımız olursa okuyabilirizconst { paketAdi } = useParams();// Loader fonksiyonunun çektiği hazır veri tek satırda elimize geliyor!// Artık burada useEffect veya 'isLoading' state yönetimine gerek yok. Sayfa anında dolu açılır!const paketVerisi = useLoaderData();return (<div style={{ padding: '20px', border: '1px solid blue' }}><h2>Modül Ayrıntıları: {paketAdi}</h2><p><strong>Açıklama:</strong> {paketVerisi.description}</p><p><strong>Son Sürüm:</strong> {paketVerisi['dist-tags']?.latest}</p><p><strong>Lisans:</strong> {paketVerisi.license}</p></div>);}export default PaketDetaySayfasi;
Müfredatın son bölümü, eski kod tabanına sahip şirketlerde (Legacy projeler) çalışabilmeniz için geçmişteki kalıplarla günümüzdeki modern yapıları karşılaştırmaktadır. Mülakatlarda eski yapılardan yeni yapılara refactor (kodu iyileştirme) soruları sıkça gelir.
Özellik / Yapı |
Eski Sektör Standardı (Legacy) |
Modern Sektör Standardı (RTK / Hooks) |
|---|---|---|
Bileşen Türü |
Class-Based Components (Sınıf tabanlı, |
Functional Components (Fonksiyon tabanlı, tamamen Hooks mimarisi) |
Yaşam Döngüsü |
|
Tek bir güçlü |
Merkezi Depo |
Plain Redux ( |
Redux Toolkit ( |
Veri Çekme (Async) |
Redux Thunk ile manuel asenkron action yazımı ve elle yükleniyor state takibi |
RTK Query ile tamamen otomatik önbelleklemeli kancalar
( |
Navigasyon |
|
Veriyi önceden yükleyen |
Tüm bu devasa müfredatı parça parça ele alarak; ham, süre içeren ve karmaşık yazılardan tamamen arındırılmış temiz bir geliştirici kılavuzu çıkardık.
Bu eğitim yolculuğunda sırasıyla:
Temel JSX kuralları ile arayüz tasarlamayı,
Props ve State ile sayfaları canlandırmayı,
Context API ile veri ağları örüp prop-drilling'i engellemeyi,
useReducer ve Redux Toolkit ile kurumsal ölçekte merkezi devlet depoları (store) inşa etmeyi,
RTK Query sayesinde asenkron veri ağlarını tek satırda yönetmeyi,
TypeScript ve React Router Loader yapılarıyla ise uygulamayı kırılmaz, hatasız ve yüksek performanslı hale getirmeyi öğrendiniz.
============================================================================================================
Harika bir karar! Bu iki büyük uygulama ve mülakat simülasyonu, öğrendiğiniz tüm teorik altyapıyı (React, Redux Toolkit, TypeScript) gerçek birer projeye dönüştürerek portfolyonuza eklemenizi sağlayacak.
Adım adım, en temiz kod standartlarıyla (Clean Code) ve mülakat kalitesinde ilerliyoruz.
Bu uygulamada TypeScript tipleri, Redux Toolkit mimarisi ve sepet hesaplama lojikleri bir arada yer alıyor.
types.ts)exportinterfaceUrun{id:number;ad:string;fiyat:number;gorselUrl:string;}exportinterfaceSepetElemani{urun: Urun;adet:number;}exportinterfaceSepetState{elemanlar: SepetElemani[];toplamFiyat:number;toplamAdet:number;}
sepetSlice.ts)import{createSlice,PayloadAction}from'@reduxjs/toolkit';import{SepetState,Urun}from'./types';constinitialState: SepetState = {elemanlar: [],toplamFiyat:0,toplamAdet:0,};// Toplam fiyat ve adet hesaplayan yardımcı fonksiyon (Clean Code)constsepetiHesapla= (state: SepetState) => {state.toplamAdet = state.elemanlar.reduce((toplam,eleman) => toplam + eleman.adet,0);state.toplamFiyat = state.elemanlar.reduce((toplam,eleman) => toplam + (eleman.urun.fiyat * eleman.adet),0);};exportconstsepetSlice= createSlice({name:'sepet',initialState,reducers: {sepeteEkle: (state,action: PayloadAction<Urun>) => {constmevcutEleman= state.elemanlar.find(item => item.urun.id === action.payload.id);if(mevcutEleman) {mevcutEleman.adet +=1;}else{state.elemanlar.push({urun: action.payload,adet:1});}sepetiHesapla(state);},sepettenCikar: (state,action: PayloadAction<number>) => {state.elemanlar = state.elemanlar.filter(item => item.urun.id !== action.payload);sepetiHesapla(state);},adetGuncelle: (state,action: PayloadAction<{urunId:number;yeniAdet:number}>) => {const{urunId,yeniAdet} = action.payload;constmevcutEleman= state.elemanlar.find(item => item.urun.id === urunId);if(mevcutEleman && yeniAdet >0) {mevcutEleman.adet = yeniAdet;}sepetiHesapla(state);},sepetiTemizle: (state) => {state.elemanlar = [];state.toplamFiyat =0;state.toplamAdet =0;}}});exportconst{sepeteEkle,sepettenCikar,adetGuncelle,sepetiTemizle} = sepetSlice.actions;exportdefaultsepetSlice.reducer;
SepetUygulamasi.tsx)importReactfrom'react';import{useSelector,useDispatch}from'react-redux';import{RootState}from'./store';// Store tipinizimport{sepeteEkle,sepettenCikar,adetGuncelle}from'./sepetSlice';import{Urun}from'./types';constSAHTE_URUNLER: Urun[] = [{id:101,ad:"Kablosuz Kulaklık",fiyat:1500,gorselUrl:"https://picsum.photos"},{id:102,ad:"Mekanik Klavye",fiyat:2500,gorselUrl:"https://picsum.photos"},{id:103,ad:"Oyuncu Mouse",fiyat:950,gorselUrl:"https://picsum.photos"}];exportconstSepetUygulamasi: React.FC = () => {constdispatch= useDispatch();const{elemanlar,toplamFiyat,toplamAdet} = useSelector((state: RootState) => state.sepet);return(<divstyle={{padding:'20px',display:'flex',gap:'40px',fontFamily:'sans-serif'}}>{/* Ürün Listesi */}<divstyle={{flex:1}}><h2>🏪 Mağaza Ürünleri</h2><divstyle={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'15px'}}>{SAHTE_URUNLER.map(urun => (<divkey={urun.id} style={{border:'1px solid #ddd',padding:'15px',borderRadius:'8px'}}><imgsrc={urun.gorselUrl} alt={urun.ad} style={{borderRadius:'4px'}} /><h4>{urun.ad}</h4><p>{urun.fiyat}TL</p><buttononClick={() => dispatch(sepeteEkle(urun))}>Sepete Ekle</button></div>))}</div></div>{/* Sepet Detayı */}<divstyle={{width:'400px',borderLeft:'2px solid #eee',paddingLeft:'20px'}}><h2>🛒 Alışveriş Sepetim ({toplamAdet})</h2>{elemanlar.length ===0? (<p>Sepetiniz şu anda boş.</p>) : (<>{elemanlar.map(item => (<divkey={item.urun.id} style={{display:'flex',gap:'10px',marginBottom:'15px',alignItems:'center'}}><divstyle={{flex:1}}><strong>{item.urun.ad}</strong><div>{item.urun.fiyat}TL x{item.adet}</div></div><inputtype="number"value={item.adet}style={{width:'50px'}}onChange={(e) => dispatch(adetGuncelle({urunId: item.urun.id,yeniAdet: parseInt(e.target.value) ||1}))}/><buttononClick={() => dispatch(sepettenCikar(item.urun.id))} style={{color:'red'}}>Sil</button></div>))}<hr/><h3>Toplam Ödenecek:{toplamFiyat}TL</h3></>)}</div></div>);};
Bu projede veri filtreleme, durum değiştirme ve TypeScript tip katılıklarını içeren bir Todo/Task sistemi inşa ediyoruz.
taskTypes.ts)exporttypeGorevDurumu ='Bekliyor'|'Yapılıyor'|'Tamamlandı';exportinterfaceGorev{id:string;baslik:string;durum: GorevDurumu;}exportinterfaceGorevState{liste: Gorev[];mevcutFiltre: GorevDurumu |'Tümü';}
gorevSlice.ts)import{createSlice,PayloadAction}from'@reduxjs/toolkit';import{Gorev,GorevState,GorevDurumu}from'./taskTypes';constinitialState: GorevState = {liste: [{id:'1',baslik:'React Router v6 entegrasyonu yap',durum:'Tamamlandı'},{id:'2',baslik:'TypeScript arayüzlerini tanımla',durum:'Yapılıyor'}],mevcutFiltre:'Tümü',};exportconstgorevSlice= createSlice({name:'gorev',initialState,reducers: {gorevEkle: (state,action: PayloadAction<string>) => {constyeniGorev: Gorev = {id: Date.now().toString(),baslik: action.payload,durum:'Bekliyor',};state.liste.push(yeniGorev);},durumDegistir: (state,action: PayloadAction<{id:string;yeniDurum: GorevDurumu }>) => {constgorev= state.liste.find(item => item.id === action.payload.id);if(gorev) {gorev.durum = action.payload.yeniDurum;}},gorevSil: (state,action: PayloadAction<string>) => {state.liste = state.liste.filter(item => item.id !== action.payload);},filtreAyarla: (state,action: PayloadAction<GorevDurumu |'Tümü'>) => {state.mevcutFiltre = action.payload;}}});exportconst{gorevEkle,durumDegistir,gorevSil,filtreAyarla} = gorevSlice.actions;exportdefaultgorevSlice.reducer;
GorevUygulamasi.tsx)importReact, {useState}from'react';import{useSelector,useDispatch}from'react-redux';import{RootState}from'./store';import{gorevEkle,durumDegistir,gorevSil,filtreAyarla}from'./gorevSlice';import{GorevDurumu}from'./taskTypes';exportconstGorevUygulamasi: React.FC = () => {constdispatch= useDispatch();const{liste,mevcutFiltre} = useSelector((state: RootState) => state.gorev);const[metin,setMetin] = useState('');consthandleEkle= (e: React.FormEvent) => {e.preventDefault();if(!metin.trim())return;dispatch(gorevEkle(metin));setMetin('');};// Filtreleme mantığı (Derived State)constfiltrelenmisGorevler= liste.filter(gorev => {if(mevcutFiltre ==='Tümü')returntrue;returngorev.durum === mevcutFiltre;});return(<divstyle={{padding:'20px',maxWidth:'600px',margin:'auto',fontFamily:'sans-serif'}}><h2>📋 Görev Yönetim Paneli (Kanban)</h2>{/* Görev Ekleme Formu */}<formonSubmit={handleEkle} style={{display:'flex',gap:'10px',marginBottom:'20px'}}><inputtype="text"value={metin}onChange={(e) => setMetin(e.target.value)}placeholder="Yeni görev açıklaması yazın..."style={{flex:1,padding:'8px'}}/><buttontype="submit"style={{padding:'8px 15px'}}>Ekle</button></form>{/* Filtre Butonları */}<divstyle={{display:'flex',gap:'10px',marginBottom:'20px'}}>{(['Tümü','Bekliyor','Yapılıyor','Tamamlandı']asconst).map(filtre => (<buttonkey={filtre}onClick={() => dispatch(filtreAyarla(filtre))}style={{padding:'5px 10px',backgroundColor: mevcutFiltre === filtre ?'#333':'#eee',color: mevcutFiltre === filtre ?'#fff':'#000',border:'none',borderRadius:'4px',cursor:'pointer'}}>{filtre}</button>))}</div>{/* Görev Listesi */}<div>{filtrelenmisGorevler.map(gorev => (<divkey={gorev.id} style={{display:'flex',justifyContent:'space-between',alignItems:'center',padding:'10px',border:'1px solid #eee',marginBottom:'8px',borderRadius:'4px'}}><spanstyle={{textDecoration: gorev.durum ==='Tamamlandı'?'line-through':'none'}}>{gorev.baslik}</span><divstyle={{display:'flex',gap:'10px'}}><selectvalue={gorev.durum}onChange={(e) => dispatch(durumDegistir({id: gorev.id,yeniDurum: e.target.valueasGorevDurumu }))}><optionvalue="Bekliyor">Bekliyor</option><optionvalue="Yapılıyor">Yapılıyor</option><optionvalue="Tamamlandı">Tamamlandı</option></select><buttononClick={() => dispatch(gorevSil(gorev.id))} style={{color:'red',border:'none',background:'none',cursor:'pointer'}}>❌</button></div></div>))}</div></div>);};
Gerçekçi bir teknik mülakat (Technical Interview) senaryosu. Kıdemli Yazılım Geliştirici (Interviewer) soruyor, Aday (Siz) yanıtlıyor.
Mülakatçı: Projelerinizde Prop Drilling sorunuyla karşılaştınız mı? Redux Toolkit kullandığınızda mimari olarak ne değişiyor?
İdeal Cevap: Evet, Prop
Drilling, bir verinin hiyerarşik olarak çok alttaki bir Child
bileşene ulaştırılması için, aradaki o veriyle hiç ilgisi
olmayan Parent bileşenlerden prop olarak tek tek geçirilmesi
durumudur. Bu durum kodun bakımını zorlaştırır. Context API
veya Redux Toolkit, uygulamayı global bir veri deposu (Store) ile
sarmalar. Bileşenler aradaki paslaşma zincirine ihtiyaç duymadan,
useSelector
kancası (veya useContext)
vasıtasıyla doğrudan bu merkezi depoya bağlanıp ihtiyacı olan
veriyi çekebilir. Bu sayede aradaki bileşenlerin gereksiz yere
render olması da engellenir.
useEffect
içindeki Bağımlılık
Dizisi (Dependency Array) kuralları nelerdir? Boş bırakırsak veya
hiç yazmazsak ne olur?Mülakatçı:
useEffect
hook'unun ikinci parametresini yönetirken nelere dikkat
ediyorsunuz? []
ile hiçbir şey yazmamak arasındaki fark nedir?
İdeal Cevap: İkinci parametre olan bağımlılık dizisi, efektin ne zaman çalışacağını kontrol eder. Üç senaryo vardır:
Hiç yazmazsak: Bileşendeki herhangi bir state güncellenip render tetiklendiğinde bu efekt her seferinde en baştan çalışır. İçinde state değiştirirsek sonsuz döngüye sebep olur.
Boş dizi
[]
bırakırsak: Bileşen DOM ağacına ilk eklendiğinde
(Mount anında) sadece 1 kez çalışır. API istekleri için
kullanılır.
Dizi içine değişken
koyarsak ([sayac]):
İlk açılışta ve o değişken her değiştiğinde tetiklenir.
Ayrıca mülakatlarda kritik olan Cleanup (Temizlik) fonksiyonu
vardır; efekt içinden bir fonksiyon döndürürsek, bileşen
ekrandan kalkarken (Unmount) veya efekt yeniden tetiklenmeden hemen
önce çalışarak bellek sızıntılarını (Memory leak) önler.
createSlice
fonksiyonunun avantajı
nedir? Arka plandaki "Immer" kütüphanesi ne işe yarar?Mülakatçı:
Klasik Redux ile Redux Toolkit arasındaki en büyük fark nedir?
Reducer içinde doğrudan state.push()
yapabiliyoruz, bu Redux'ın "Immerse/Immutable"
(Değiştirilemezlik) kuralına aykırı değil mi?
İdeal Cevap: Klasik
Redux'ta action tipleri, action creator'lar ve reducer'lar ayrı
ayrı yazılıyordu ve çok ciddi kod kalabalığı vardı.
createSlice
tüm bunları tek bir çatı altında otomatik üretir. İkinci
sorunuza gelirsek; normalde React ve Redux'ta state'i doğrudan
mutasyona uğratmak (değiştirmek) kesinlikle yasaktır ve büyük
hatalara yol açar. Ancak Redux Toolkit, arka planda Immer
kütüphanesini entegre olarak çalıştırır. Biz reducer içinde
state.push()
veya state.eleman
= yeniDeger yazdığımızda, Immer bunu
yakalar ve bizim yerimize spread operatörü ([...])
kullanılmış gibi güvenli, yepyeni bir immutable obje üretir.
Yani kod okunabilirliği artar ama değiştirilemezlik kuralı
çiğnenmemiş olur.
Mülakatçı:
Bir form alanındaki veriyi takip ederken neden useRef
ile DOM'dan çekmek yerine state kullanıyoruz?
İdeal Cevap: Kontrollü
girdi (Controlled Input), form elemanının değerinin (value)
tamamen bir React state'ine bağlı olması durumudur. Input alanına
her bir karakter girildiğinde onChange
olayı tetiklenir ve state güncellenir. Böylece formun o anki
değeri hakkında "Tek Doğruluk Kaynağı" (Single Source
of Truth) React olur. Doğrudan DOM manipülasyonu (veya kontrolsüz
girdi) yerine bunu seçmemizin nedeni; veriyi anlık doğrulamak
(validation), dinamik arama yapmak veya kullanıcı hatalı karakter
girdiğinde anında input içeriğini maskelemek gibi dinamik arayüz
süreçlerini React'in re-render mekanizmasıyla tam uyumlu
yürütmektir.
***************************************************************************************************
Harika bir tercih! Teknik mülakatların en heyecanlı ve en çok belirleyici olan aşamasına geldik: Live Coding (Canlı Kodlama Testi).
Mülakatçılar bu testlerde sadece kodun çalışıp çalışmadığına bakmazlar; aynı zamanda problemi çözerken nasıl düşündüğünüze (Problem Solving), TypeScript tip güvenliğine ne kadar dikkat ettiğinize ve performansı nasıl optimize ettiğinize (Debounce lojiği gibi) bakarlar.
Önce mülakatçının size canlı yayında vereceği o zorlu problemi çözeceğiz, ardından kurduğumuz iki projeyi (Sepet ve Görev Yönetimi) TailwindCSS (v4 standartlarında) modern bir arayüze kavuşturacağız.
Senaryo: Mülakatçı ekranını paylaştı ve size şu problemi verdi:
"Bize TypeScript kullanarak bir arama fonksiyonu ve bu fonksiyonu optimize edecek özel bir debounced kancası (Custom Hook) yazın. Kullanıcı klavyeden her harf girdiğinde API'ye veya filtreleme motoruna gereksiz yük binmesini engelleyin. Süre sınırınız 15 dakika."
"Kodlamaya başlamadan
önce mantığı açıklamak isterim: Kullanıcı yazı yazarken, her
harfe bastığında bir asenkron işlem tetiklemek performansı
düşürür. Debounce lojiği sayesinde, kullanıcının yazı
yazmayı bitirmesini bekleriz. Belirlediğimiz süre (örneğin 300
milisaniye) boyunca yeni bir harfe basılmazsa, nihai kelimeyi işleme
alırız. Bunu React'te useEffect
ve setTimeout
temizlik (cleanup)
mekanizmasıyla çözeceğim."
useDebounce.ts)import{useState,useEffect}from'react';// Dışarıdan her türlü veri tipini (Generic <T>) ve gecikme süresini alan kancaexportfunctionuseDebounce<T>(value: T,delay:number=300): T {const[debouncedValue,setDebouncedValue] = useState<T>(value);useEffect(() => {// Kullanıcı her yazı yazdığında yeni bir zamanlayıcı (timer) başlatıyoruzconsthandler= setTimeout(() => {setDebouncedValue(value);}, delay);// CLEANUP (Temizlik): Kullanıcı süre dolmadan tekrar bir harfe basarsa,// bir önceki zamanlayıcıyı iptal ediyoruz. En kritik performans optimizasyonu burasıdır!return() => {clearTimeout(handler);};}, [value, delay]);// value her değiştiğinde bu efekt sıfırlanıp baştan başlarreturndebouncedValue;}
AramaBileseni.tsx)importReact, {useState,useMemo}from'react';import{useDebounce}from'./useDebounce';interfaceKullanici{id:number;isim:string;departman:string;}constSAHTE_KULLANICILAR: Kullanici[] = [{id:1,isim:"Can Yılmaz",departman:"Yazılım"},{id:2,isim:"Merve Demir",departman:"Tasarım"},{id:3,isim:"Ahmet Kaya",departman:"Yazılım"},{id:4,isim:"Zeynep Çelik",departman:"Pazarlama"}];exportconstAramaBileseni: React.FC = () => {const[aramaMetni,setAramaMetni] = useState<string>('');// Yazılan metni 400ms boyunca debounce süzgecinden geçiriyoruzconstdebouncedArama= useDebounce<string>(aramaMetni,400);// Performans için filtreleme işlemini useMemo ile sarmalıyoruzconstfiltrelenmisKullanicilar= useMemo(() => {if(!debouncedArama.trim())returnSAHTE_KULLANICILAR;returnSAHTE_KULLANICILAR.filter(kullanici =>kullanici.isim.toLowerCase().includes(debouncedArama.toLowerCase()) ||kullanici.departman.toLowerCase().includes(debouncedArama.toLowerCase()));}, [debouncedArama]);// Sadece debouncedArama süresi dolup değiştiğinde filtreleme yapar!return(<divclassName="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md space-y-4"><h2className="text-xl font-bold text-gray-900">👥 Akıllı Personel Arama</h2><inputtype="text"value={aramaMetni}onChange={(e) => setAramaMetni(e.target.value)}placeholder="İsim veya departman arayın..."className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"/><divclassName="text-sm text-gray-500">API/Filtre Tetikleyici Kelime:<spanclassName="font-mono text-red-500">"{debouncedArama}"</span></div><ulclassName="divide-y divide-gray-200">{filtrelenmisKullanicilar.map(k => (<likey={k.id} className="py-2 flex justify-between"><spanclassName="font-medium text-gray-800">{k.isim}</span><spanclassName="text-sm bg-gray-100 px-2 py-0.5 rounded text-gray-600">{k.departman}</span></li>))}</ul></div>);};
Mülakatçı Notu: "Harika!
Hem useEffect
temizlik mekanizmasını
kullandınız hem de useMemo
ile gereksiz array
operasyonlarının önüne geçtiniz. Geçer not aldınız."
Şimdi, bir önceki adımda yazdığımız E-Ticaret Sepeti ve Görev Yönetimi uygulamalarımızın arayüzlerini tamamen modern, göze hitap eden ve responsive bir hale getirelim.
ModernSepet.tsx)importReactfrom'react';import{useSelector,useDispatch}from'react-redux';import{RootState}from'./store';import{sepeteEkle,sepettenCikar,adetGuncelle}from'./sepetSlice';import{Urun}from'./types';constSAHTE_URUNLER: Urun[] = [{id:101,ad:"Pro Kablosuz Kulaklık",fiyat:1500,gorselUrl:"https://picsum.photos"},{id:102,ad:"RGB Mekanik Klavye",fiyat:2500,gorselUrl:"https://picsum.photos"},{id:103,ad:"Ergonomik Oyuncu Mouse",fiyat:950,gorselUrl:"https://picsum.photos"}];exportconstModernSepet: React.FC = () => {constdispatch= useDispatch();const{elemanlar,toplamFiyat,toplamAdet} = useSelector((state: RootState) => state.sepet);return(<divclassName="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8"><divclassName="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-8">{/* Sol Taraf: Ürünler Kataloğu */}<divclassName="lg:col-span-2"><h2className="text-3xl font-extrabold text-gray-900 mb-6 flex items-center gap-2"><span>🏪</span>Teknoloji Mağazası</h2><divclassName="grid grid-cols-1 sm:grid-cols-2 gap-6">{SAHTE_URUNLER.map(urun => (<divkey={urun.id} className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow duration-300"><imgsrc={urun.gorselUrl} alt={urun.ad} className="w-full h-48 object-cover"/><divclassName="p-5"><h4className="text-lg font-bold text-gray-900 mb-1">{urun.ad}</h4><pclassName="text-xl font-semibold text-blue-600 mb-4">{urun.fiyat.toLocaleString('tr-TR')}TL</p><buttononClick={() => dispatch(sepeteEkle(urun))}className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-4 rounded-xl transition-colors duration-200">Sepete Ekle</button></div></div>))}</div></div>{/* Sağ Taraf: Alışveriş Sepeti Kutusu */}<divclassName="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 h-fit sticky top-6"><h2className="text-2xl font-bold text-gray-900 mb-6 flex items-center justify-between"><span>🛒 Sepetim</span><spanclassName="bg-blue-100 text-blue-800 text-sm font-semibold px-3 py-1 rounded-full">{toplamAdet}Ürün</span></h2>{elemanlar.length ===0? (<divclassName="text-center py-12 text-gray-400"><spanclassName="text-5xl block mb-2">🛍️</span>Sepetiniz henüz boş.</div>) : (<divclassName="space-y-4"><divclassName="max-h-96 overflow-y-auto pr-1 space-y-4">{elemanlar.map(item => (<divkey={item.urun.id} className="flex gap-4 items-center bg-gray-50 p-3 rounded-xl"><divclassName="flex-1"><h5className="font-semibold text-gray-800 text-sm">{item.urun.ad}</h5><spanclassName="text-xs text-gray-500">{item.urun.fiyat.toLocaleString('tr-TR')}TL</span></div><divclassName="flex items-center gap-2"><inputtype="number"min="1"value={item.adet}onChange={(e) => dispatch(adetGuncelle({urunId: item.urun.id,yeniAdet: parseInt(e.target.value) ||1}))}className="w-14 text-center border border-gray-300 rounded-lg p-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"/><buttononClick={() => dispatch(sepettenCikar(item.urun.id))}className="text-red-500 hover:text-red-700 p-1 text-sm font-medium">Sil</button></div></div>))}</div><divclassName="border-t border-gray-100 pt-4 mt-6"><divclassName="flex justify-between text-gray-600 mb-2"><span>Ara Toplam</span><span>{toplamFiyat.toLocaleString('tr-TR')}TL</span></div><divclassName="flex justify-between text-gray-900 font-bold text-xl mb-4"><span>Genel Toplam</span><spanclassName="text-blue-600">{toplamFiyat.toLocaleString('tr-TR')}TL</span></div><buttonclassName="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-xl shadow-sm transition-colors duration-200">Alışverişi Tamamla</button></div></>)}</div></div></div>);};
ModernGorevUygulamasi.tsx)importReact, {useState}from'react';import{useSelector,useDispatch}from'react-redux';import{RootState}from'./store';import{gorevEkle,durumDegistir,gorevSil,filtreAyarla}from'./gorevSlice';import{GorevDurumu}from'./taskTypes';exportconstModernGorevUygulamasi: React.FC = () => {constdispatch= useDispatch();const{liste,mevcutFiltre} = useSelector((state: RootState) => state.gorev);const[metin,setMetin] = useState<string>('');consthandleEkle= (e: React.FormEvent) => {e.preventDefault();if(!metin.trim())return;dispatch(gorevEkle(metin));setMetin('');};constfiltrelenmisGorevler= liste.filter(gorev => {if(mevcutFiltre ==='Tümü')returntrue;returngorev.durum === mevcutFiltre;});// Durumlara göre şık rozet (Badge) renkleri atayan fonksiyonconstdurumRengi Getir = (durum: GorevDurumu) => {if(durum ==='Tamamlandı')return'bg-green-50 text-green-700 border-green-200';if(durum ==='Yapılıyor')return'bg-amber-50 text-amber-700 border-amber-200';return'bg-blue-50 text-blue-700 border-blue-200';};return(<divclassName="min-h-screen bg-slate-50 py-12 px-4 sm:px-6"><divclassName="max-w-2xl mx-auto bg-white rounded-2xl shadow-sm border border-slate-100 p-6 sm:p-8"><h2className="text-2xl font-bold text-slate-800 mb-6 flex items-center gap-2"><span>📋</span>Proje Görev Paneli</h2>{/* Form Alanı */}<formonSubmit={handleEkle} className="flex gap-2 mb-6"><inputtype="text"value={metin}onChange={(e) => setMetin(e.target.value)}placeholder="Yapılacak yeni bir görev yazın..."className="flex-1 px-4 py-2.5 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 placeholder-slate-400 text-slate-700 text-sm"/><buttontype="submit"className="bg-indigo-600 hover:bg-indigo-700 text-white font-medium px-5 rounded-xl text-sm transition-colors duration-200">Ekle</button></form>{/* Filtreleme Çubuğu */}<divclassName="flex flex-wrap gap-1.5 mb-6 bg-slate-50 p-1.5 rounded-xl border border-slate-100">{(['Tümü','Bekliyor','Yapılıyor','Tamamlandı']asconst).map(filtre => (<buttonkey={filtre}onClick={() => dispatch(filtreAyarla(filtre))}className={`flex-1 text-center py-2 text-xs font-semibold rounded-lg transition-all duration-200 ${mevcutFiltre === filtre?'bg-white text-indigo-600 shadow-sm border border-slate-100':'text-slate-500 hover:text-slate-800 hover:bg-slate-100'}`}>{filtre}</button>))}</div>{/* Görev Listesi */}<divclassName="space-y-3">{filtrelenmisGorevler.length ===0? (<divclassName="text-center py-8 text-slate-400 text-sm">Bu filtreye uygun görev bulunamadı.</div>) : (filtrelenmisGorevler.map(gorev => (<divkey={gorev.id}className={`flex justify-between items-center p-4 border rounded-xl transition-all duration-200 ${gorev.durum ==='Tamamlandı'?'border-slate-100 bg-slate-50/50':'border-slate-200 hover:border-slate-300'}`}><spanclassName={`text-sm font-medium ${gorev.durum ==='Tamamlandı'?'line-through text-slate-400':'text-slate-700'}`}>{gorev.baslik}</span><divclassName="flex items-center gap-3"><selectvalue={gorev.durum}onChange={(e) => dispatch(durumDegistir({id: gorev.id,yeniDurum: e.target.valueasGorevDurumu }))}className={`text-xs font-semibold px-2 py-1.5 border rounded-lg focus:outline-none ${durumRengiGetir(gorev.durum)}`}><optionvalue="Bekliyor">Bekliyor</option><optionvalue="Yapılıyor">Yapılıyor</option><optionvalue="Tamamlandı">Tamamlandı</option></select><buttononClick={() => dispatch(gorevSil(gorev.id))}className="text-slate-400 hover:text-red-500 p-1 text-sm transition-colors duration-150"title="Görevi Sil">❌</button></div></div>)))}</div></div></div>);};