Bölüm 5 / 16
Liste ve koşullu render
Liste render, key, koşullu render.
11 dakikalık okuma
Panel ekranlarının iki temel hareketi: bir diziyi satırlara
çizmek ve "hangi durumda ne görünecek" kararını vermek. Angular bunlar için şablon
sözdizimi verdi (@for/@if — 10. hattın 3. dersi); React'in
cevabı 1. dersteki ilkeden çıkar: şablon dili yok, JavaScript var —
döngü map, koşul ternary. Ama iki gerçek tuzak seni bekliyor:
key olarak index vermenin state'i kaydırması (Angular
track'te yaşadığın deneyin aynısı) ve &&'in ekrana
"0" basması. İkisi de üretim koduna sızan, mülakatta sorulan hatalardır.
01Ne öğreneceksin
- Liste render:
mapile JSX dizisi;key'in reconciliation'daki gerçek rolü - key=index zararı: state kayması deneyi — Angular
track $indexile aynı ders keydeğiştirerek bilinçli sıfırlama: remount bir araçtır- Koşullu render kalıpları: erken
return, ternary,&&— ve "0" tuzağı - Yükleniyor / hata / veri / boş dörtlüsü — 9. hattın ayırt edici birleşimiyle tip güvenli ekran
- Üretim pratiği: key'li Fragment, uzun listede sanal kaydırma kavramı
02map ile liste: key kimlik defteridir
JSX, süslü parantez içinde eleman dizisi kabul eder; liste render bu yüzden
özel sözdizimi değil, bildiğin map'tir (8. hattın 15. dersi):
<ul>
{basvurular.map(b => (
<li key={b.id}> {/* key: React'in kimlik defteri */}
<BasvuruKarti basvuru={b} onSil={sil} />
</li>
))}
</ul>
key olmadan konsol uyarı basar; ama key bir "uyarıyı
sustur" alanı değildir. 1. dersteki reconciliation'ı hatırla: React her render'da
yeni tarifi eskisiyle kıyaslar. Listede sorun şudur — yeni dizide üç satır var,
eskisinde de üç; ama hangisi hangisi? key, bu eşleştirmenin
cevabıdır: React "aynı key = aynı eleman" der, elemanın DOM'unu ve state'ini
korur, yalnız props'unu günceller. Angular köprüsü bire bir: @for'un
track'i aynı soruyu cevaplıyordu (10. hattın 3. dersi) — orada
performans vurguluydu, React'te işin içine state doğruluğu da girer; deneyi
birazdan yapacağız.
03key=index: sessizce kayan state
En kolay kaçış key={i} yazmaktır — ve en pahalısı. Index kimlik
değil konumdur; liste değişince konum el değiştirir:
{/* ❌ her satırında notu tutan input olan bir liste */}
{basvurular.map((b, i) => (
<li key={i}>
{b.unvan} <input placeholder="not al" />
</li>
))}
{/* Senaryo: 1. satırın input'una "aradım, dönecekler" yazdın.
Sonra 1. sıradaki başvuruyu SİLDİN.
Eski liste: key=0 (Sibersay), key=1 (Savunmatek)
Yeni liste: key=0 (Savunmatek)
React: "key=0 hâlâ var" → DOM'u ve INPUT DEĞERİNİ korur,
sadece metni değiştirir. Sonuç: notun artık SAVUNMATEK'in
satırında duruyor — veri yanlış kayda yapıştı. */}
Hata sinsidir çünkü konsol tertemizdir — yanlışlık yalnız kullanıcının verisinde görünür. Aynısı satır içi state'te (açık/kapalı panel), animasyonda, seçim vurgusunda da olur. Kurallar:
- Doğru key, verinin kalıcı kimliğidir: veritabanı id'si, benzersiz kod.
Render sırasında üretilen değer (
Math.random(),i) kimlik olamaz. Math.random()en kötüsüdür: her render'da her key değişir → React her satırı söküp baştan kurar (state tamamen uçar, performans çöker).- index'in meşru olduğu dar alan: liste asla yeniden sıralanmıyor, filtrelenmiyor, ortadan silinmiyor ve satırların state'i yoksa (statik madde listesi) — o zaman index zararsızdır. Şüphedeysen id kullan.
- key listede benzersizdir, globalde değil; ve
keybir prop değildir — çocuğaprops.keyolarak geçmez, tamamen React'e aittir.
04key ile bilinçli sıfırlama: remount bir araçtır
Madem "key değişti = eski eleman öldü, yenisi doğdu" — bunu bilerek kullanabilirsin. Klasik ihtiyaç: detay panelindeki düzenleme formu, seçili başvuru değişince sıfırdan kurulmalı (eski taslak state'i taşınmamalı):
<BasvuruDuzenleForm key={secili.id} basvuru={secili} />
{/* secili.id değişti → React eski form örneğini söker, yenisini kurar:
bütün iç state (taslak alanlar, touched bayrakları) temiz başlar. */}
Bu kalıp, 2. dersin "props state'e kopyalanmaz" kuralının şık istisnasını yönetir:
form, basvuru'yu başlangıç değeri olarak state'ine alabilir — çünkü
"hangi başvurunun taslağı" sorusunu artık key çözüyor. Effect'le
"props değişince state'i resetle" yazmaya kalkmak (3. dersin kokusu) yerine tek
öznitelik: key. Mülakat cümlesi: "remount'u bug değil, araç olarak
kullanırım."
05Koşullu render: kalıplar ve "0" tuzağı
JSX ifade istediği için (1. ders) koşul araçların JavaScript'tir; dört kalıp işin tamamını görür:
// 1) erken return — bileşenin tamamı koşula bağlıysa
if (!secili) return <p>Bir başvuru seç.</p>;
// 2) ternary — iki dal da görünecekse
{gonderim.tur === "gonderiliyor" ? <Donuyor /> : <Kaydet />}
// 3) && — "varsa göster, yoksa hiçbir şey"
{hata && <p className="hata">{hata}</p>}
// 4) değişkene alma — dallar büyüyünce JSX'i okunur tutar
let icerik;
if (yukleniyor) icerik = <Iskelet />;
else if (hata) icerik = <HataKutusu mesaj={hata} />;
else icerik = <BasvuruListesi basvurular={veri} />;
return <section>{icerik}</section>;
Temel bilgi: JSX'te null, undefined, false
ve true hiçbir şey render etmez — && kalıbı bu
sayede çalışır. Ama "0" bu listede yoktur:
{/* ❌ mulakatSayisi === 0 iken ekranda çırılçıplak bir "0" belirir */}
{mulakatSayisi && <span className="rozet">{mulakatSayisi} mülakat</span>}
{/* neden: 0 falsy → && soldaki DEĞERİ döndürür (8. hat 13. ders)
→ JSX sayı 0'ı metin olarak basar. false basılmazdı, 0 basılır. */}
{/* ✅ koşulu boolean yap */}
{mulakatSayisi > 0 && <span className="rozet">{mulakatSayisi} mülakat</span>}
Aynı ailenin ikinci üyesi boş string'dir ("" && ... bir şey
basmaz ama mantığı bulanıklaştırır). Ekip kuralı olarak benimse:
&&'in solunda daima boolean ifade (> 0,
!== "", !!x). Ternary kullanırken de "hiçbir şey" dalını
null ile yaz.
06Dört durumlu ekran: tip güvenli birleşim
3. derste veri çekmeyi öğrendin; şimdi ekranın dürüstlüğünü kur. Bir liste ekranının dört hâli vardır ve boş, yükleniyor değildir: "sonuç yok" mesajı ile dönen çark ayrı şeylerdir. 9. hattın 4. dersindeki ayırt edici birleşim, dört hâli tek tipte toplar ve imkânsız kombinasyonları derleyiciye yasaklatır:
type ListeDurumu =
| { tur: "yukleniyor" }
| { tur: "hata"; mesaj: string }
| { tur: "hazir"; basvurular: Basvuru[] }; // boşluk ayrı state değil: length === 0
const [durum, setDurum] = useState<ListeDurumu>({ tur: "yukleniyor" });
// render — switch ile daraltma (9. hat 4. ders):
switch (durum.tur) {
case "yukleniyor": return <Iskelet />;
case "hata": return <HataKutusu mesaj={durum.mesaj} />; // mesaj yalnız burada var
case "hazir":
if (durum.basvurular.length === 0)
return <p>Filtreye uyan başvuru yok.</p>; // BOŞ: dördüncü hâl, veri hâlinin içinde
return (
<ul>
{durum.basvurular.map(b => <li key={b.id}>…</li>)}
</ul>
);
}
İki tasarım notu: (1) Filtre uygulanmış liste burada da türetilir
(2. dersin kuralı) — durum.basvurular.filter(...) render'da; filtreye
ayrı state açılmaz. (2) Bu üçlü durum yönetimini her ekranda elle yazmak,
10. derste TanStack Query'ye devredeceğin işin ta kendisidir
(isPending / isError / data) — kütüphane geldiğinde bu deseni tanıyor
olacaksın.
07Üretim pratiği: liste disiplini
- Key'li Fragment: her satır iki kardeş element basacaksa (ör.
dt+dd) kısa<>sözdizimi key alamaz — açık biçim gerekir:<Fragment key={b.id}>…</Fragment>(import { Fragment } from "react"). - Satırı bileşene çıkar:
mapiçi JSX üç-dört satırı geçince satır kendi bileşeni olur (BasvuruSatiri) — okunurluk bugün,React.memoile render kırpma yarın (13. dersin konusu; memo'nun çalışma şartı olan referans kararlılığını 6. ders verecek). - Uzun liste = sanal kaydırma: 5.000 satırı DOM'a basmak, key doğru olsa da tarayıcıyı boğar (8. hattın 19. dersindeki render maliyeti). Çözüm yalnız görünen pencereyi çizmektir — Angular'da CDK virtual scroll'du (10. hattın 11. dersi), React ekosisteminde TanStack Virtual / react-window. Bu partide kurmuyoruz; adını ve ne zaman gerektiğini bil: "listem büyüyünce ne yaparsın?" mülakat sorusunun cevabı.
- Sıralamayı yerinde yapma:
dizi.sortstate'i mutasyona uğratır (2. dersin tablosu) — render içindetoSorted/ kopya-sonra-sort kullan; "sıraladım ama bazen bozuluyor" şikâyetinin klasik kaynağı budur.
08Mini alıştırma — Kimlik deneyleri
Yaklaşık 20 dakika · kurulum yok — StackBlitz projenin üstüne kur
- Başvuru listesine her satırda "not" input'u ekle (satır içi, controlled olmayan basit input yeter). Key olarak bilerek index ver; ilk satıra not yaz, ilk başvuruyu sil — notun alttaki kayda yapıştığını gör. Ekran görüntüsü alır gibi durumu bir cümleyle not et.
- Key'i
b.idyap, deneyi tekrarla — notun sahibiyle birlikte silindiğini gör. İki davranış farkını reconciliation diliyle açıkla. - Felaketi ölç: key'e
Math.random()ver, input'a yazmaya çalış — her tuşta odağın kaçtığını (satır baştan kurulduğu için) yaşa; geri al. - "0" tuzağını üret:
{mulakatSayisi && <span>...</span>}yaz, sayıyı 0 yap, ekrandaki çıplak 0'ı gör;> 0ile düzelt.false'un neden basılmayıp 0'ın basıldığını yorumla açıkla. - Dört durumlu ekranı kur:
ListeDurumubirleşimini yaz, sahte gecikmeli yükleme ileyukleniyor → hazirgeçişini işlet; hata dalını da sahte reddedilen Promise ile test et. Boş filtre sonucunda "sonuç yok" mesajının (çark değil) çıktığını doğrula. - Detay formuna
key={secili.id}ver: iki başvuru arasında geçiş yaparken form alanlarının sıfırlandığını gör; key'i kaldırıp eski taslağın yanlış kayda taşındığını da gör — remount'un araç hâlini not et.
09Kendini sına
key reconciliation'da hangi soruyu cevaplar? "key listede benzersiz olsun" kuralının ötesinde neyi bilmek gerekir?
React her render'da yeni eleman tarifini eskisiyle kıyaslar;
listede kritik soru "yeni dizinin hangi satırı, eskinin hangi satırıdır?"
eşleştirmesidir. key bu kimlik defteridir: aynı key'i taşıyan eleman
"aynı eleman" sayılır — DOM düğümü ve iç state'i korunur, yalnız props'u
güncellenir; eski listede olup yenide olmayan key sökülür, yeni key sıfırdan
kurulur. Bu yüzden key performans süsü değil doğruluk mekanizmasıdır:
yanlış key, satır state'inin (input değeri, açık panel, seçim) yanlış kayda
yapışması demektir. Bilinmesi gerekenler: key verinin kalıcı kimliğinden gelir
(id), render'da üretilen değerden (index, Math.random) gelmez; benzersizlik
yalnız o liste içinde aranır; key prop değildir — çocuk bileşene
props.key olarak geçmez, tamamen React'in iç defterine yazılır.
Angular'daki @for ... track aynı sorunun cevabıdır — iki çerçevede de
kimlik, kimliği olan veriden gelir.
key=index neden zararlıdır — somut senaryoyla anlat. Hangi dar durumda kabul edilebilir, Math.random() neden en kötüsüdür?
Index kimlik değil konumdur; liste filtrelenince,
sıralanınca ya da ortadan eleman silinince konumlar el değiştirir ama React
"key=0 hâlâ burada" diye eski DOM'u ve state'i korur. Somut senaryo: her satırında
not input'u olan listede ilk satıra not yazılır, ilk kayıt silinir — yeni listenin
ilk sırasına yükselen başka kayıt, silinen kaydın input değerini devralır;
konsol temizdir, yanlışlık yalnız kullanıcının verisindedir. Aynı kayma seçim
vurgusu, açık/kapalı panel ve animasyonlarda da yaşanır. Index'in meşru dar
alanı: liste asla yeniden sıralanmıyor/filtrelenmiyor/ortadan silinmiyor ve
satırların iç state'i yok (statik madde listesi) — şüphe varsa id.
Math.random() en kötüsüdür çünkü her render'da her key değişir:
React tüm satırları söküp baştan kurar — bütün state uçar, odak kaçar, performans
çöker. Bu, Angular'da track $index / track b.id
seçiminde yaşadığın dersin aynısıdır.
JSX'te neler render edilmez? && kalıbının "0" tuzağını mekanizmasıyla açıkla ve ekip kuralını söyle.
JSX'te null, undefined,
false ve true hiçbir şey render etmez —
{hata && <p>...</p>} kalıbı bu sayede çalışır: koşul
falsy'yse ifade false/null döner ve ekrana bir şey
basılmaz. Tuzak, &&'in boolean döndürmemesinde: kısa devre
operatörü soldaki değer falsy'yse o değeri döndürür. Sol taraf sayı 0
olduğunda ifadenin sonucu 0'dır ve 0 render edilir — "0 mülakat" rozetini
gizlemek isterken ekranda çıplak bir 0 belirir; boş string aynı ailedendir.
Çözüm ve ekip kuralı: &&'in solunda daima gerçek boolean ifade
bulunur — mulakatSayisi > 0 && ..., metin !== "" &&
..., gerekirse !!x. Ternary'de "hiçbir şey" dalı açıkça
null yazılır; dallar büyüyünce koşul, JSX'in içinde değil değişkene
alınmış if/else zincirinde kurulur — okunurluk da bir üretim
değeridir.
Bir liste ekranının dört hâlini tip güvenli nasıl kurarsın? "Boş" hâli neden ayrı ele alınır ve key ile bilinçli sıfırlama ne zaman doğru araçtır?
Hâller ayırt edici birleşimle tek tipte toplanır:
{ tur: "yukleniyor" } | { tur: "hata"; mesaj } | { tur: "hazir";
basvurular } — böylece "hem yükleniyor hem hata" gibi imkânsız
kombinasyonlar tipte yasaklanır ve switch daraltmasıyla her dalda
yalnız o dalın alanlarına erişilir (hata mesajı yalnız hata dalında vardır).
Boş, dördüncü görsel hâldir ama ayrı state değildir: hazir
dalında length === 0 kontrolüdür — kullanıcıya dönen çark yerine
"filtreye uyan sonuç yok" denir; boşluğu yükleniyormuş gibi göstermek UX
yalanıdır. Filtrelenmiş liste de bu yapıda state değil render'da türetilen
değerdir. Bu elle kurulan üçlü, TanStack Query'nin
isPending/isError/data üçlüsünün ön çalışmasıdır. Key ile sıfırlama:
"key değişti = eski örnek söküldü, yenisi kuruldu" davranışı bilinçli
kullanılır — seçili kayıt değişince düzenleme formuna key={secili.id}
vermek, bütün taslak state'ini temiz başlatır; "props değişince state'i effect'le
resetle" anti-kalıbının tek öznitelikli doğru karşılığıdır.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) React'te liste nasıl render edilir; key neden zorunludur?
(2) key reconciliation'da nasıl kullanılır? (3) key
olarak index vermenin sakıncası nedir; hangi durumda kabul edilebilir?
(4) key={Math.random()} neye yol açar? (5) JSX'te hangi değerler
render edilmez? (6) {sayi && <X />} kalıbının riski
nedir? (7) Koşullu render için hangi kalıplar kullanılır? (8) Yükleniyor / hata
/ veri / boş durumları nasıl modellenir? (9) key değiştirerek
bileşeni sıfırlamak ne zaman kullanılır? (10) Çok uzun listelerde hangi teknik
uygulanır?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Temel — "Liste map'tir, koşul JavaScript'tir — React şablon dili vermez. key ise reconciliation'ın kimlik defteridir: aynı key aynı eleman demektir, DOM ve state korunur." (2) Tuzak 1 — "Index konumdur, kimlik değil: silme/sıralamada satır state'i yanlış kayda yapışır — Angular'daki track dersinin aynısı; doğru key verinin kalıcı id'sidir, Math.random ise her render'da tam sökümdür." (3) Tuzak 2 — "&& soldaki falsy değeri döndürür; sol taraf 0 ise ekrana 0 basılır — solda daima boolean tutarım." (4) Model — "Ekranın hâllerini ayırt edici birleşimle kurarım: yükleniyor/hata/hazir, boş ise hazir'ın içinde ayrı mesajdır — çark değil. Bu, TanStack Query'nin bana vereceği üçlünün elle hâlidir." (5) Kıdem vuruşu — "key'i araç olarak da kullanırım: seçim değişince forma key veririm, remount taslağı sıfırlar; binlerce satırda sanal kaydırmaya geçerim; sort yerine toSorted — state'i yerinde mutasyondan korurum."
