İçeriğe geç
XDD.baby Yazılım Öğrenme
Sözlük Hata rehberi 0/56

Blok 6 · Ders 40

React'e giriş: bileşenler ve JSX

Vite ile React projesi açıyor, JSX yazıyor, arayüzü bileşenlere bölüyor ve props ile veri aktarıyorsun.

Bu dersin sonunda

  • Vite ile bir React + TypeScript projesi açıp npm run dev ile tarayıcıda çalıştırabilir, index.html → main.tsx → App.tsx zincirini anlatabilirsin
  • JSX'in kurallarını (tek kök ya da fragment, className, { } içinde ifade, her etiketin kapanması) uygulayıp Vite'ın ve tsc'nin JSX mesajlarını okuyabilirsin
  • Arayüzü büyük harfle başlayan bileşenlere bölüp her birini kendi dosyasından içe aktararak kullanabilirsin
  • Bileşenlere tipli props verip eksik ya da yanlış tipteki props'u npx tsc -b ile yakalayabilirsin
  • Bir diziyi map ve key ile listeye çevirebilir; &&, üçlü ifade ve erken return ile koşullu çizim yapabilirsin
  • React Developer Tools'un Components sekmesinde bileşen ağacını ve bir bileşenin props'larını inceleyebilirsin
Bu sayfada
  1. React neden var: arayüz verinin görüntüsüdür
  2. Vite ile proje açmak
  3. JSX: JavaScript'in içinde HTML gibi yazmak
  4. Bileşen: arayüzün yapı taşı
  5. Props: bileşene veri vermek
  6. Liste çizmek ve key
  7. Koşullu çizim
  8. React geliştirici araçları
  9. Sık hatalar
  10. Görevler
  11. Kendini sına
  12. Kayıt noktası

Blok 5'te veriyi sunucuda ve veritabanında tuttun. Şimdi o verinin kullanıcıya görünen yüzüne dönüyorsun. Bu derste React ile tanışıyorsun: arayüzü küçük, yeniden kullanılabilir bileşenlere bölen ve sayfayı veriden kendisi kuran bir kütüphane. Vite ile ilk React projeni açacak, HTML'e benzeyen JSX sözdizimini yazacak, bileşenlere props ile veri verecek, bir diziyi listeye çevirip bazı parçaları yalnız gerektiğinde göstereceksin. Bütün kodu Ders 39'daki gibi TypeScript ile, yani .tsx dosyalarında yazacaksın.

React neden var: arayüz verinin görüntüsüdür

Ders 25'teki ansiklopedide kartları createElement ile tek tek kurmuştun. Sadeleştirilmiş hâliyle, solda o yol, sağda React'teki karşılığı:

Ders 25: öğeleri tek tek kurmak
function kartOlustur(sampiyon) {    const kart = document.createElement("article")    kart.className = "kart"    const ad = document.createElement("h3")    ad.textContent = sampiyon.isim    const rol = document.createElement("p")    rol.textContent = sampiyon.rol    kart.append(ad, rol)    return kart}
React: kartın neye benzediğini söylemek
function SampiyonKarti({ isim, rol }: SampiyonKartiProps) {    return (        <article className="kart">            <h3>{isim}</h3>            <p>{rol}</p>        </article>    )}

Soldaki kod nasıl yapılacağını adım adım söylüyor: öğe oluştur, sınıf ver, yazı koy, birbirine ekle. Sağdaki ise yalnız ne istediğini söylüyor: "kart, içinde bir başlık ve bir paragraf olan bir article'dır". İkinci yazım hem kısa hem de sayfadaki sonuca benziyor; okuyunca kartı gözünde canlandırabiliyorsun.

Asıl fark veri değişince ortaya çıkar. Ders 23'teki maç günlüğünde kural "veriyi değiştir, sonra ciz() ile listeyi baştan kur" idi (çizmek). Listeyi her seferinde silip baştan kurmak yavaştır; silmeyip yalnız değişen yeri elle düzeltmek ise hataya açıktır: bir yeri güncellemeyi unutursan ekran ile veri birbirini tutmaz. React bu ikinci işi senden alır. Sen "bu veri için ekran böyle görünür" diye bir fonksiyon yazarsın; veri değişince React aynı fonksiyonu yeniden çağırır, eski sonuçla yenisini karşılaştırır ve sayfada yalnız değişen yeri günceller.

1. Veri Ahri · Büyücü Garen · Dövüşçü Jinx · Nişancı bir dizi, 3 obje 2. Bileşen SampiyonKarti bir şampiyon alır, kartın neye benzediğini söyler → <article> 3. Ekran A Ahri G Garen J Jinx Sen 1'i ve 2'yi yazarsın, 3'ü React kurar. Veri değişince bileşeni yeniden çağırır ve sayfada yalnız değişen yeri günceller.
Şekil 40.1 Arayüz verinin görüntüsüdür: veriyi ve bileşeni sen yazarsın, sayfayı React kurar.

React tek başına yalnız arayüz kısmını çözer; sayfalar arası gezinme, sunucuda çalışma gibi işler için üstüne kurulan çatılar var. Blok 6'nın ilerisinde onlardan birini, Next.js'i göreceksin (Ders 43). Önce React'in kendisini sağlam öğreniyoruz.

Vite ile proje açmak

Tarayıcı JSX'ten de TypeScript'ten de anlamaz. Ders 39'da Node .ts dosyalarını tipleri silerek çalıştırıyordu, ama tarayıcıda böyle bir kolaylık yok. Kodunu tarayıcının anlayacağı JavaScript'e çeviren, dosyalarını sunan ve sen kaydettikçe sayfayı tazeleyen bir araca ihtiyacın var. Bu işi Vite yapıyor.

Projeyi oluştur

VS Code'da bir terminal aç ve cd C:\kod ile kod klasörüne geç. Sonra Vite'ın proje sihirbazını çalıştır:

Terminal — PowerShell
PS C:\kod> npm create vite@latest sampiyon-kartlari -- --template react-ts
  • npm create vite@latest: create-vite adlı paketin en son sürümünü indirip çalıştırır. Bu paketin tek işi yeni proje klasörü kurmak.
  • sampiyon-kartlari: oluşturulacak klasörün adı.
  • --: "bundan sonrası npm'e değil, çalıştırılan araca" demek. Yazmazsan --template'i npm kendisine sanır ve şablon seçilmez.
  • --template react-ts: React + TypeScript şablonu. Yalnız react yazsaydın .jsx dosyalı, tipsiz bir proje gelirdi.

İlk çalıştırmada npm, paketi indirmek için izin ister; y yaz ve Enter'a bas. Ardından sihirbaz iki soru sorar. İlki hangi kod denetleyiciyi (linter) istediğin:

Terminal — PowerShell
│◆  Which linter to use?│  ● Oxlint│  ○ ESLint│  ↑/↓ to navigate • Enter: confirm└

Seçili olan Oxlint'te kal ve Enter'a bas. İkinci soru "paketleri kurup projeyi hemen başlatayım mı?":

Terminal — PowerShell
◆  Install with npm and start now?│  ● Yes / ○ No└

Bu kez → ok tuşuyla No'ya geç ve Enter'a bas. Kurma ve başlatma adımlarını kendin yapıp her birinin ne olduğunu göreceksin. Sonunda ekranın tamamı şöyle olmalı:

Terminal — PowerShell
PS C:\kod> npm create vite@latest sampiyon-kartlari -- --template react-tsNeed to install the following packages:create-vite@9.2.1Ok to proceed? (y) y > npx> create-vite sampiyon-kartlari --template react-ts  To create in one go, run: create-vite <DIRECTORY> --no-interactive --template <TEMPLATE> │◇  Which linter to use?│  Oxlint│◇  Install with npm and start now?│  No│◇  Scaffolding project in C:\kod\sampiyon-kartlari...│└  Done. Now run:   cd sampiyon-kartlari  npm install  npm run dev

Bu ders create-vite 9.2.1 ile denendi; sende daha yeni bir sürüm ve biraz farklı sorular çıkabilir. Seçeneklerin anlamı aynı kalır. Altında npm notice ile başlayan bir yeni sürüm duyurusu görürsen görmezden gel. To create in one go satırı, aynı işi sorusuz yapmanın yolunu söylüyor; şimdilik gerek yok.

Şimdi klasörü VS Code'da aç ve paketleri kur:

Terminal — PowerShell
PS C:\kod> code sampiyon-kartlari

Yeni pencerede güven sorusu gelirse Yes, I trust the authors'a bas, menüden Terminal → New Terminal ile terminali aç ve yaz:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npm install added 27 packages, and audited 28 packages in 3s 9 packages are looking for funding  run `npm fund` for details found 0 vulnerabilities

npm install, package.json'daki paketleri (react, react-dom, vite, typescript ve birkaç yardımcı) node_modules'a kurar (Ders 14). Paket sayısı ve süre sende farklı olabilir.

Geliştirme sunucusunu başlat

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npm run dev > sampiyon-kartlari@0.0.0 dev> vite    VITE v8.3.4  ready in 655 ms   ➜  Local:   http://localhost:5173/  ➜  Network: use --host to expose  ➜  press h + enter to show help

npm run dev, package.json'daki dev betiğini, yani vite komutunu çalıştırır. Vite bilgisayarında bir yerel sunucu açtı ve onu 5173 numaralı porttan dinliyor. Ctrl basılıyken http://localhost:5173/ bağlantısına tıkla; tarayıcıda şablonun hazır sayfası açılır:

localhost:5173
Vite React şablonunun sayfası: ortada kesik çizgili bir resim yeri, altında Get started başlığı, Edit src/App.tsx and save to test HMR yazısı, Count is 0 düğmesi ve en altta Documentation bölümü.
Vite'ın React şablonu. Kesik çizgili kutunun yerinde sende bir resim ile Vite ve React logoları görünür; tarayıcın açık temadaysa sayfa da açık renkli olur.

Count is 0 düğmesine birkaç kez tıklarsan sayı artar. Bunun nasıl çalıştığını Ders 41'de öğreneceksin; bu derste o düğmeyi silip kendi sayfanı yazacaksın.

Sunucu çalıştığı sürece terminal meşguldür: istem (PS C:\…>) geri gelmez, yeni komut yazamazsın. Başka bir komut gerekince terminal panelinin sağ üstündeki + düğmesiyle ikinci bir terminal aç; Vite birincide çalışmaya devam eder. Sunucuyu durdurmak için onun terminaline tıklayıp Ctrl+C'ye bas.

Klasörün içi

Vite sana küçük ama eksiksiz bir proje kurdu. Önemli dosyalar ve birbirlerini nasıl çağırdıkları:

sampiyon-kartlari ├─ node_modules/ ├─ public/ ├─ src/ │ ├─ App.css, assets/ │ ├─ App.tsx │ ├─ index.css │ └─ main.tsx ├─ index.html ├─ package.json ├─ tsconfig*.json (3) └─ vite.config.ts index.html <div id="root"></div> <script src="/src/main.tsx"> src/main.tsx createRoot(#root) .render(<App />) src/App.tsx return <h1>…</h1> sayfanın içeriği
Şekil 40.2 Proje klasörü ve sayfanın açılış zinciri: tarayıcı index.html'i açar, o main.tsx'i yükler, main.tsx de App bileşenini #root kutusuna çizer.
  • index.html: tarayıcının açtığı tek HTML sayfası. Gövdesinde yalnız boş bir <div id="root"></div> ve main.tsx'i yükleyen bir <script type="module"> var. Sayfanın bütün içeriğini React bu kutunun içine koyacak.
  • src/main.tsx: React'i başlatan dosya. Aşağıda içine bakıyoruz.
  • src/App.tsx: ilk bileşen; şablon sayfasının bütün içeriği burada. Bu derste en çok bu dosyayla çalışacaksın.
  • src/index.css, src/App.css, src/assets/: şablon sayfasının stilleri ve resimleri.
  • public/: olduğu gibi sunulan dosyalar (sekmedeki küçük simge favicon.svg gibi).
  • package.json: betikler ve paketler. dev (geliştirme sunucusu), build (yayın için derleme), lint (Oxlint), preview (derlenmiş hâli denemek).
  • tsconfig.json, tsconfig.app.json, tsconfig.node.json: TypeScript ayarları. Biri src'deki tarayıcı kodu, biri vite.config.ts için; üçüncüsü ikisini birbirine bağlar. Şimdilik dokunmana gerek yok.
  • vite.config.ts: Vite'ın ayarları; React eklentisini açan tek satır var.

src/main.tsx'i aç:

src/main.tsx
import { StrictMode } from 'react'import { createRoot } from 'react-dom/client'import './index.css'import App from './App.tsx' createRoot(document.getElementById('root')!).render(  <StrictMode>    <App />  </StrictMode>,)
  • import './index.css': CSS dosyasını sayfaya ekler. Vite, JavaScript'ten CSS içe aktarmayı da bilir.
  • import App from './App.tsx': App.tsx'in varsayılan dışa aktarmasını App adıyla alır.
  • document.getElementById('root'): index.html'deki boş kutuyu bulur (Ders 22'deki querySelector("#root") ile aynı iş). Kutu bulunamazsa null döner; sondaki ! TypeScript'e "burada kesin var" diye söz verir. Ders 39'daki as gibi bir sözdür, denetim değil.
  • createRoot(…).render(<App />): "bu kutunun içini React yönetsin ve içine App'i çiz" demek.
  • <StrictMode>: geliştirirken sık yapılan bazı hataları yakalamak için React'in ek denetimlerini açar. Yayındaki sitede hiçbir şey yapmaz.

Bu dosyaya dokunmayacaksın. Tek tırnak ve iki boşluk girinti kullandığına dikkat et: Vite'ın tercihi. Kursta yazdığımız dosyalarda kendi düzenimizi (çift tırnak, dört boşluk, noktalı virgül yok) sürdürüyoruz; JavaScript ikisini de kabul eder (Ders 7).

Sıfırdan başla

Şablonun sayfası işini gördü. Kendi sayfanı yazmak için önce src/App.tsx'in içini tamamen sil ve yerine şu üç satırı kendin yaz:

src/App.tsx — sampiyon-kartlari — Visual Studio Code
src/App.tsx
export default function App() {    return <h1>Şampiyon Kartları</h1>}
PROBLEMSOUTPUTDEBUG CONSOLETERMINAL
PS C:\kod\sampiyon-kartlari> npm run dev > sampiyon-kartlari@0.0.0 dev> vite    VITE v8.3.4  ready in 211 ms   ➜  Local:   http://localhost:5173/  ➜  Network: use --host to expose  ➜  press h + enter to show help03:36:00 [vite] (client) hmr update /src/App.tsx
Ln 1, Col 1Spaces: 4UTF-8TSX

Kaydettiğin anda Vite'ın terminaline hmr update /src/App.tsx satırı düşer. Saat sende farklı olacak.

Tarayıcıya bak: sayfayı yenilemene gerek kalmadan koca başlık "Şampiyon Kartları" oldu. Bu, senin ilk bileşenin: adı App, büyük harfle başlıyor ve HTML'e benzeyen bir şey döndürüyor. Başlıktaki yazıyı değiştirip kaydet; sayfa yine kendiliğinden güncellenir.

Şimdi temizlik. Yeni App.tsx şablonun stillerini ve resimlerini artık kullanmıyor; src/App.css dosyasını ve src/assets klasörünü sil (VS Code'un dosya listesinde sağ tık → Delete). Sıra önemli: eski App.tsx dururken silseydin, o dosya hâlâ ./App.css'i içe aktardığı için Vite Failed to resolve import "./App.css" from "src/App.tsx". Does the file exist? ("içe aktarılan dosya bulunamadı") diye hata katmanı açardı.

Sonra src/index.css'in içini tamamen sil ve yerine şunu koy. CSS'i Ders 20 ve Ders 21'de öğrendin; bu dersin konusu olmadığı için bu kez kopyalayabilirsin:

src/index.css
:root {    font-family: system-ui, "Segoe UI", sans-serif;    color: #e8e6f0;    background: #16141f;} body {    margin: 0;} main {    max-width: 760px;    margin: 0 auto;    padding: 24px 16px;} h1 {    margin: 0 0 12px;} .ozet {    margin: 0 0 20px;    color: #a9a4bd;} .kartlar {    display: grid;    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));    gap: 12px;} .kart {    position: relative;    padding: 16px;    border: 1px solid #34304a;    border-radius: 12px;    background: #211e2e;} .kart h3 {    margin: 10px 0 2px;} .kart p {    margin: 0;    color: #a9a4bd;} .harf {    display: grid;    place-items: center;    width: 48px;    height: 48px;    border-radius: 50%;    background: #6d4bd8;    font-size: 22px;    font-weight: 700;} .rozet {    position: absolute;    top: 12px;    right: 12px;    padding: 2px 8px;    border-radius: 999px;    background: #1f6f4a;    font-size: 12px;} .bos {    padding: 16px;    border: 1px dashed #34304a;    border-radius: 12px;    color: #a9a4bd;} .kart.ucretsiz {    border-color: #2f8f62;}

Kaydedince sayfa koyu renge döner. Birazdan yazacağın kartların, rozetlerin ve boş liste kutusunun stilleri de bu dosyada hazır.

JSX: JavaScript'in içinde HTML gibi yazmak

return <h1>Şampiyon Kartları</h1> satırı bir JavaScript dosyasında tuhaf duruyor: tırnak yok, ama metin var; HTML etiketi var, ama bir fonksiyonun içinde. Bu yazımın adı JSX. Tarayıcı onu tanımaz; Vite her .tsx dosyasını sunmadan önce sıradan JavaScript'e çevirir. Şu iki satırın Vite'tan geçmiş hâli (çeviriyi Vite'ın kendi çeviricisiyle, npm run build'in kullandığı ayarlarla aldık; okuman yeter, yazmayacaksın):

Senin yazdığın
const oyuncu = "Deniz" export const baslik = <h1 className="baslik">Şampiyon Kartları</h1>export const selam = <p>{oyuncu} hoş geldin</p>
Vite'ın çevirisi
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";const oyuncu = "Deniz";export const baslik = /* @__PURE__ */ _jsx("h1", {	className: "baslik",	children: "Şampiyon Kartları"});export const selam = /* @__PURE__ */ _jsxs("p", { children: [oyuncu, " hoş geldin"] });

Her JSX etiketi bir fonksiyon çağrısına dönüşüyor: ilk argüman etiketin adı ("h1"), ikincisi bir obje. Nitelikler objenin alanları oluyor (className: "baslik"), etiketin içindekiler de children alanına giriyor. /* @__PURE__ */ yalnız derleme aracına bırakılmış bir not. npm run dev sırasında çağrının adı _jsxDEV olur ve hata mesajları için dosya adıyla satır numarası da eklenir; fikir aynıdır. Yani JSX, React'in fonksiyonlarını daha okunaklı yazmanın bir yolu; altta yine JavaScript var. Bu bilgi, birazdan göreceğin kuralların neden var olduğunu açıklayacak.

1. Sen yazarsın (App.tsx) <h1 className="baslik">Şampiyon Kartları</h1> Vite, dosyayı sunmadan önce 2. JavaScript'e çevrilir _jsx("h1", { className: "baslik", children: "Şampiyon Kartları" }) React, tarayıcıda çalışırken 3. Sayfada gerçek öğe (F12 → Elements) <h1 class="baslik">Şampiyon Kartları</h1>
Şekil 40.3 Bir JSX satırının yolculuğu: sen yazarsın, Vite fonksiyon çağrısına çevirir, React sayfaya gerçek bir öğe koyar.

JSX HTML'e benzer ama HTML değildir; birkaç kuralı var. Hepsini tek tabloda gör, sonra tek tek deneyeceğiz:

HTML'deJSX'teNeden
İstediğin kadar yan yana öğeBir return tek bir kök öğe döndürür; gerekirse <>…</>Fonksiyon tek değer döndürür
class="kart"className="kart"class JavaScript'te ayrılmış bir sözcük (Ders 16)
for="not" (label)htmlFor="not"for da ayrılmış bir sözcük (döngü)
<br>, <hr>, <img …>, <input …><br />, <hr />, <img … />, <input … />Her etiket kapanmak zorunda
Değer koymanın yolu yok{oyuncu}, {2 + 3}Süslü parantez "burası JavaScript" demek
<!-- yorum -->{/* yorum */}Süslü parantez içinde JavaScript yorumu

Süslü parantez: JSX'in içinde JavaScript

App.tsx'i şöyle değiştir:

src/App.tsx
export default function App() {    const oyuncu = "Deniz"    const sampiyonSayisi = 6     return (        <main>            <h1>Şampiyon Kartları</h1>            <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>        </main>    )}
localhost:5173
Koyu zeminde Şampiyon Kartları başlığı, altında soluk yazıyla Deniz, koleksiyonunda 6 şampiyon var.

JSX'in içinde { } açtığın her yer bir JavaScript penceresidir: içindeki ifade hesaplanır ve sonucu oraya yazılır. Şablon metindeki ${ } ile aynı fikir. İçine değer üreten her şeyi yazabilirsin: {oyuncu}, {sampiyonSayisi + 1}, {oyuncu.toUpperCase()}, {sampiyonlar.length}. Değer üretmeyen satırları yazamazsın: {if (…) …} ya da {for (…) …} yazım hatasıdır; Vite [PARSE_ERROR] Unexpected token der. Onların JSX'teki karşılıklarını bu dersin ilerisinde göreceksin.

{ } ile koyduğun metin, textContent gibi her zaman düz yazı olarak basılır. Bir oyuncu adına <script> yazmış olsa bile sayfada yalnız o harfleri görürsün; React metni hiçbir zaman etikete çevirmez. Ders 22'deki XSS korumasını kendiliğinden alırsın.

İki ayrıntı daha. Birden çok satırlık JSX'i return'den sonra parantez içine al; <main>'i return ile aynı satırda açmazsan bu parantez seni bir tuzaktan korur (Sık hatalar). Nitelik değeri sabit bir metinse tırnakla yazılır (className="ozet"), bir JavaScript değeriyse süslü parantezle (className={secilenSinif}); ikisi birden olmaz.

Sık hataObjects are not valid as a React child

Ne zaman çıkar? { } içine bir objeyi olduğu gibi koyunca. React metni, sayıyı ve JSX'i çizebilir; bir objenin ekranda neye benzeyeceğini bilemez. Dene: App.tsx'te sampiyonSayisi yerine bir obje tut ve onu paragrafa koy.

src/App.tsx
export default function App() {    const oyuncu = "Deniz"    const favori = { isim: "Ahri", rol: "Büyücü" }     return (        <main>            <h1>Şampiyon Kartları</h1>            <p className="ozet">{oyuncu}, favorin: {favori}</p>        </main>    )}

Sayfa tamamen boş kalır. Konsolda:

Konsol (F12 → Console)
Uncaught Error: Objects are not valid as a React child (found: object with keys {isim, rol}). If you meant to render a collection of children, use an array instead.An error occurred in the <p> component. Consider adding an error boundary to your tree to customize error handling behavior.Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

"Objeler React'in çizebileceği bir çocuk değil (bulunan: isim ve rol alanlı bir obje)." Parantezde alan adlarını sayması, hangi objeyi koyduğunu bulmana yardım eder. İkinci satır hatanın hangi öğede çıktığını söylüyor: <p>. tsc de aynı satırı yakalar: error TS2322: Type '{ isim: string; rol: string; }' is not assignable to type 'ReactNode'. (ReactNode: React'in çizebildiği her şeyin tipi.)

Nasıl düzelir? Objenin göstermek istediğin alanını yaz: {favori.isim}. Sonra App.tsx'i yukarıdaki çalışan hâline geri döndür.

Tek kök öğe

Şimdi bilerek bozalım. App.tsx'teki <main> ve </main> satırlarını sil, başlık ile paragraf yan yana kalsın:

src/App.tsx✗ Yanlış
export default function App() {    const oyuncu = "Deniz"    const sampiyonSayisi = 6     return (        <h1>Şampiyon Kartları</h1>        <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>    )}

Kaydettiğin anda tarayıcıda Vite'ın hata katmanı açılır:

localhost:5173
Vite'ın siyah hata katmanı: plugin vite oxc Transform failed with 1 error, PARSE_ERROR Adjacent JSX elements must be wrapped in an enclosing tag, src/App.tsx 7. satır 9. sütun, altında Help: Did you want a JSX fragment.
Vite hata katmanı. Kodu düzeltip kaydedince kendiliğinden kapanır.

Aynı mesaj Vite'ın terminaline de düşer:

Terminal — PowerShell
03:10:29 [vite] Internal server error: Transform failed with 1 error: [PARSE_ERROR] Adjacent JSX elements must be wrapped in an enclosing tag.   ╭─[ src/App.tsx:7:9 ]   │ 7 │         <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>   │         ┬   │         ╰──   │   │ Help: Did you want a JSX fragment `<>...</>`?───╯   Plugin: vite:oxc  File: C:/kod/sampiyon-kartlari/src/App.tsx

Parça parça oku:

  • [PARSE_ERROR]: dosya okunurken (ayrıştırılırken) çıkan bir yazım hatası; Ders 1'deki SyntaxError'ın Vite'taki adı. Kod hiç çalışmadı.
  • Adjacent JSX elements must be wrapped in an enclosing tag: "yan yana JSX öğeleri, onları saran bir etiketin içine alınmalı".
  • src/App.tsx:7:9: 7. satır, 9. sütun; yani ikinci öğe olan <p>'nin başı. Kutunun içindeki ┬ işareti de oranın altında.
  • Help: Did you want a JSX fragment <>...</>?: "Bir JSX fragment'ı mı istedin?" Çözümü de söylüyor.

Kuralın sebebi yukarıdaki çeviride: her etiket bir fonksiyon çağrısı. İki kardeş öğeyi döndürmek return _jsx("h1", …) _jsx("p", …) yazmak gibi olurdu; bir fonksiyon iki değer döndüremez.

Düzeltmeden önce tsc'nin aynı hatayı nasıl söylediğine de bak. Bir Vite projesinde tip denetimi komutu npx tsc -b'dir (sebebini props bölümünde göreceksin). Vite'ın çalıştığı terminale dokunmadan + ile ikinci bir terminal aç:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/App.tsx:6:9 - error TS2657: JSX expressions must have one parent element. 6         <h1>Şampiyon Kartları</h1>          ~~~~~~~~~~~~~~~~~~~~~~~~~~7         <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Found 1 error.

JSX expressions must have one parent element: "JSX ifadelerinin tek bir ebeveyn öğesi olmalı". ~~~ çizgisi iki satırın da altında; sorun ikisinin birlikteliği. VS Code da aynı yerin altını kırmızıyla çizer.

İki çözüm var. Ya öğeleri anlamlı bir kapsayıcıya koyarsın (biz <main> kullanmıştık), ya da sayfaya fazladan bir öğe eklemek istemiyorsan fragment kullanırsın:

src/App.tsx
export default function App() {    const oyuncu = "Deniz"    const sampiyonSayisi = 6     return (        <>            <h1>Şampiyon Kartları</h1>            <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>        </>    )}

<> ile </> arası, JSX açısından tek bir kök öğedir ama sayfaya hiçbir etiket eklemez. F12 → Elements'te bakarsan h1 ile p'nin doğrudan #root'un içinde durduğunu görürsün. Biz kartlar için ortak bir <main> istediğimizden <main>'e geri dön.

className ve htmlFor

HTML'den alışkanlıkla class yazarsan ne olur? Paragraftaki className'i class yap ve kaydet. Sayfa değişmez, paragraf yine soluk renkte görünür. Ama bu "doğru" demek değil:

Sık hataInvalid DOM property `class`. Did you mean `className`?

Ne zaman çıkar? JSX'te HTML'deki gibi class (ya da <label>'da for) yazınca.

src/App.tsx
            <p class="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>
Konsol (F12 → Console)
Invalid DOM property `class`. Did you mean `className`?

React yanlış niteliği yine sayfaya geçirdiği için stil çalışır; ama konsol her yüklemede uyarır, tsc de projeyi derletmez:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/App.tsx:8:16 - error TS2322: Type '{ children: (string | number)[]; class: string; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>'.  Property 'class' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>'. Did you mean 'className'? 8             <p class="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>                 ~~~~~  Found 1 error.

İlk satır uzun ve korkutucu, ama Ders 39'da öğrendiğin gibi zincirde en içteki satır asıl sebebi söyler: Property 'class' does not exist … Did you mean 'className'? DetailedHTMLProps<…> bir <p> öğesinin alabileceği bütün niteliklerin tipi; class onların arasında yok. for için mesaj aynıdır, yalnız öneri htmlFor olur.

Nasıl düzelir? class → className, for → htmlFor. Başka bir yerden HTML yapıştırdıysan bu ikisini ara.

Neden böyle? JSX JavaScript'e çevriliyor ve class JavaScript'te sınıf tanımlamak için ayrılmış bir sözcük. Ders 25'te kart.className = "kart" yazmıştın; DOM da aynı adı kullanıyor. Bu kural birçok nitelikte geçerli: JSX'te nitelikler camelCase yazılır (tabIndex, maxLength). aria-… ve data-… ile başlayanlar ise HTML'deki gibi kalır.

Her etiket kapanır

HTML'de <br>, <hr>, <img>, <input> gibi etiketlerin kapanışı yoktur. JSX'te her etiket kapanmak zorunda; içi boş olanlar /> ile kendini kapatır: <br />, <hr />, <input id="not" />. Kapatmazsan Vite dosyanın geri kalanını o etiketin içi sanır ve şaşırtıcı bir yerde hata verir:

Sık hataExpected corresponding JSX closing tag for 'br'

Ne zaman çıkar? İçi boş bir etiketi /> ile kapatmayınca. Örnek: başlığın altına <br> eklemek.

src/App.tsx
        <main>            <h1>Şampiyon Kartları</h1>            <br>            <p className="ozet">{oyuncu}, koleksiyonunda {sampiyonSayisi} şampiyon var</p>        </main>
Terminal — PowerShell
03:11:53 [vite] Internal server error: Transform failed with 2 errors: [PARSE_ERROR] Expected corresponding JSX closing tag for 'br'.    ╭─[ src/App.tsx:10:11 ]    │  8 │             <br>    │              ─┬    │               ╰── Opened here    │ 10 │         </main>    │           ──┬─    │             ╰─── Expected `</br>`────╯ [PARSE_ERROR] Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

Mesaj iki yeri gösteriyor: <br> 8. satırda açıldı (Opened here), Vite onun kapanışını beklerken 10. satırda </main>'e çarptı (Expected </br>). İkinci hata ilkinin yan etkisi; ilkini düzeltince kaybolur. tsc daha doğrudan söyler: error TS17008: JSX element 'br' has no corresponding closing tag.

Nasıl düzelir? <br /> yaz. Ders 1'deki kural burada da geçerli: birden çok mesaj varsa önce ilkini düzelt.

Bileşen: arayüzün yapı taşı

Sayfaya şampiyon kartları koyacağız. Her kartta aynı yapı var: harfli bir yuvarlak, isim, rol. Aynı JSX'i altı kez kopyalamak Ders 3'teki kendini tekrar etme kuralını çiğner; kartın görünüşünü değiştirmek isteyince altı yeri düzeltmen gerekir. Çözüm, kartı bir kez tarif edip istediğin kadar kullanmak: bir bileşen.

Bileşen, JSX döndüren bir fonksiyondur. App de öyleydi. src klasöründe yeni bir dosya aç, adını SampiyonKarti.tsx koy:

src/SampiyonKarti.tsx
export default function SampiyonKarti() {    return (        <article className="kart">            <div className="harf">A</div>            <h3>Ahri</h3>            <p>Büyücü</p>        </article>    )}

Sonra App.tsx'te onu içe aktar ve bir HTML etiketi gibi kullan:

src/App.tsx
import SampiyonKarti from "./SampiyonKarti" export default function App() {    return (        <main>            <h1>Şampiyon Kartları</h1>            <div className="kartlar">                <SampiyonKarti />                <SampiyonKarti />                <SampiyonKarti />            </div>        </main>    )}
localhost:5173
Şampiyon Kartları başlığının altında yan yana üç aynı kart: mor yuvarlakta A harfi, Ahri, Büyücü.

<SampiyonKarti /> yazdığın her yerde React SampiyonKarti fonksiyonunu çağırır ve döndürdüğü JSX'i oraya koyar. İçe aktarırken dosya uzantısı yazmıyorsun ("./SampiyonKarti"); Vite ve TypeScript .tsx'i kendileri bulur.

Bileşen yazarken üç alışkanlık edin:

  • Ad büyük harfle başlar. JSX küçük harfle başlayan her etiketi HTML etiketi sanar. Bu, Ders 16'daki PascalCase yazımı: SampiyonKarti, KartListesi.
  • Her bileşen kendi dosyasında. Dosyanın adı bileşenin adıyla aynı olsun (SampiyonKarti.tsx). Aradığını bulmak kolaylaşır.
  • export default. Dosyanın asıl bileşenini varsayılan dışa aktarma ile ver, içe aktaran da süslü parantezsiz alsın.
Sık hata<sampiyonKarti /> is using incorrect casing

Ne zaman çıkar? Bileşenin adını küçük harfle yazınca. Dene: App.tsx'te içe aktarma satırını import sampiyonKarti from "./SampiyonKarti" yap ve etiketleri <sampiyonKarti /> diye yaz. Kartlar kaybolur, hata katmanı çıkmaz. Konsolda:

Konsol (F12 → Console)
<sampiyonKarti /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.The tag <sampiyonKarti> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

"React bileşenleri için PascalCase, HTML öğeleri için küçük harf kullan." İlk mesaj her etiket için bir kez, toplam üç kez yazılır. React sampiyonKarti'yi bir HTML etiketi sandı ve sayfaya içi boş bir <sampiyonkarti> öğesi koydu. F12 → Elements'te onu görebilirsin. tsc'nin söylediği:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/App.tsx:1:1 - error TS6133: 'sampiyonKarti' is declared but its value is never read. 1 import sampiyonKarti from "./SampiyonKarti"  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/App.tsx:8:17 - error TS2339: Property 'sampiyonKarti' does not exist on type 'JSX.IntrinsicElements'. 8                 <sampiyonKarti />                  ~~~~~~~~~~~~~~~~~...Found 4 errors.

İki mesaj aynı şeyi söylüyor: içe aktardığın sampiyonKarti hiç kullanılmıyor (TS6133), çünkü etiket onu değil, JSX.IntrinsicElements içinde, yani HTML etiketlerinin listesinde sampiyonKarti arıyor (TS2339) ve bulamıyor. Aynı mesaj öteki iki etiket için de tekrarlandı; kısalttık.

Nasıl düzelir? Bileşenin adını ve etiketlerini büyük harfle başlat: SampiyonKarti.

Sık hatadoes not provide an export named 'default'

Ne zaman çıkar? Bileşen dosyasında export default yerine yalnız export yazıp öteki dosyada süslü parantezsiz içe aktarınca (ya da export'u hiç yazmayınca). Dene: SampiyonKarti.tsx'te default sözcüğünü sil ve kaydet. Kartlar ekranda kalır, ama bu yanıltıcı: Vite yeni dosyayı yükleyemedi ve konsola iki kırmızı satır yazdı:

Konsol (F12 → Console)
[vite] SyntaxError: The requested module '/src/SampiyonKarti.tsx?t=1791774687932' does not provide an export named 'default'[vite] Failed to reload /src/App.tsx. This could be due to syntax errors or importing non-existent modules. (see errors above)

"İstenen modül default adlı bir dışa aktarma sağlamıyor." İkinci satır "App.tsx yeniden yüklenemedi, yukarıdaki hatalara bak" diyor. Sayfayı yenilersen (F5) eski kartlar da gider, sayfa bomboş kalır ve aynı mesaj Uncaught SyntaxError: ile başlar. Adresin sonundaki ?t=…, Vite'ın değişen dosyaya eklediği bir zaman damgası; sende başka bir sayı olur. tsc'deki karşılığı (yolu kısalttık):

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/App.tsx:1:8 - error TS2613: Module '"C:/kod/sampiyon-kartlari/src/SampiyonKarti"' has no default export. Did you mean to use 'import { SampiyonKarti } from "C:/kod/sampiyon-kartlari/src/SampiyonKarti"' instead? 1 import SampiyonKarti from "./SampiyonKarti"         ~~~~~~~~~~~~~  Found 1 error.

Nasıl düzelir? Bileşen dosyasında export default function SampiyonKarti() yaz. Ya da adlı dışa aktarmada kalıp içe aktarmayı import { SampiyonKarti } from "./SampiyonKarti" yap; ikisi de doğru, yeter ki iki taraf aynı yolu seçsin (Ders 14).

Props: bileşene veri vermek

Üç kartın üçü de Ahri. Bileşen her seferinde aynı sabit metni döndürdüğü için başka türlü olamaz. Ders 3'te aynı sorunu fonksiyonlarda çözmüştün: değişen kısmı parametre yaptın. Bileşenlerde parametrelere props denir.

Önce kartın hangi bilgileri alacağını bir tiple söyle, sonra o bilgileri kullan. SampiyonKarti.tsx'i baştan yaz:

src/SampiyonKarti.tsx
type SampiyonKartiProps = {    isim: string    rol: string    zorluk: number} export default function SampiyonKarti({ isim, rol, zorluk }: SampiyonKartiProps) {    return (        <article className="kart">            <div className="harf">{isim[0]}</div>            <h3>{isim}</h3>            <p>{rol}</p>            <p>Zorluk {zorluk}/3</p>        </article>    )}

App.tsx'te de her karta kendi bilgilerini ver:

src/App.tsx
import SampiyonKarti from "./SampiyonKarti" export default function App() {    return (        <main>            <h1>Şampiyon Kartları</h1>            <div className="kartlar">                <SampiyonKarti isim="Ahri" rol="Büyücü" zorluk={2} />                <SampiyonKarti isim="Garen" rol="Dövüşçü" zorluk={1} />                <SampiyonKarti isim="Yasuo" rol="Dövüşçü" zorluk={3} />            </div>        </main>    )}
localhost:5173
Üç farklı kart: A harfli Ahri, Büyücü, Zorluk 2/3; G harfli Garen, Dövüşçü, Zorluk 1/3; Y harfli Yasuo, Dövüşçü, Zorluk 3/3.

Ne oldu, adım adım:

  1. <SampiyonKarti isim="Ahri" rol="Büyücü" zorluk={2} /> yazınca React nitelikleri tek bir objede toplar: { isim: "Ahri", rol: "Büyücü", zorluk: 2 }. Bu objenin adı props.
  2. React SampiyonKarti'yi bu objeyle çağırır. Bileşen props'u tek parametre olarak alır.
  3. Parametredeki { isim, rol, zorluk }, Ders 12'deki parçalama: objenin alanlarını aynı adlı değişkenlere açar. props.isim yazmak yerine doğrudan isim yazarsın.
  4. : SampiyonKartiProps Ders 39'daki tip notu: "bu obje bu şekilde olmalı". Tipe Props ekiyle ad vermek React dünyasında yaygın bir alışkanlık.
  5. {isim[0]} metnin ilk harfi (Ders 9): Ahri için A.

Tırnak ile süslü parantezin farkına dikkat et: isim="Ahri" bir metin verir. Metin dışındaki her değer süslü parantezle verilir: zorluk={2} bir sayı, ucretsiz={true} bir boolean, sampiyonlar={liste} bir dizi. zorluk="2" yazarsan bileşene "2" metni gider. Bu yanlışı kimin yakaladığına bak:

Sık hataProp'a yanlış tipte değer vermek: zorluk="2"

Ne zaman çıkar? Bir prop'a tipinden farklı bir değer verince. Dene: Ahri'nin kartında zorluk={2} yerine zorluk="2" yaz.

src/App.tsx
                <SampiyonKarti isim="Ahri" rol="Büyücü" zorluk="2" />

Sayfa hiçbir şey olmamış gibi "Zorluk 2/3" gösterir, ne hata katmanı çıkar ne konsol uyarısı. Yanlışı yalnız tip denetimi görür:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/App.tsx:8:57 - error TS2322: Type 'string' is not assignable to type 'number'. 8                 <SampiyonKarti isim="Ahri" rol="Büyücü" zorluk="2" />                                                          ~~~~~~   src/SampiyonKarti.tsx:4:5    4     zorluk: number          ~~~~~~    The expected type comes from property 'zorluk' which is declared here on type 'IntrinsicAttributes & SampiyonKartiProps'  Found 1 error.

Ders 39'daki TS2322'nin aynısı: verdiğin string, beklenen number. Girintili ek satır beklenen tipin nereden geldiğini gösteriyor: SampiyonKarti.tsx'in 4. satırındaki zorluk: number. IntrinsicAttributes & kısmı, React'in her bileşene eklediği key gibi ortak nitelikler; şimdilik geç.

Nasıl düzelir? Sayıyı süslü parantezle ver: zorluk={2}. Sayfa "çalışıyor" görünse bile "2" metni ileride bir hesaba girdiğinde (Ders 1'deki "5" + 3) sessizce yanlış sonuç verir.

Bir prop'u hiç vermezsen de tsc yakalar. Garen'in kartından rol="Dövüşçü"'yü silersen: error TS2741: Property 'rol' is missing in type '{ isim: string; zorluk: number; }' but required in type 'SampiyonKartiProps'. Sayfada ise Garen'in rol satırı sessizce boş kalır. Tipte olmayan bir prop verirsen (seviye={18}) yine TS2322 gelir; zincirin en içindeki satır: Property 'seviye' does not exist on type 'IntrinsicAttributes & SampiyonKartiProps'.

Props bir yöne akar: yukarıdaki bileşen aşağıdakine verir. SampiyonKarti kendi props'unu değiştirmez; isim = "Lux" gibi bir satır, React'in "bileşen aynı props'la hep aynı sonucu verir" varsayımını bozar. Ekranda değişmesi gereken veri için props değil state kullanılır (Ders 23'teki durumun React'teki hâli); o, Ders 41'in konusu.

Liste çizmek ve key

Kartları elle yazmak yerine veriden üretelim. Gerçek bir uygulamada bu veri bir API'den gelir (Ders 42); şimdilik bir dosyada tutuyoruz. src/veri.ts dosyasını oluştur (JSX yok, o yüzden .ts):

src/veri.ts
export type Sampiyon = {    id: number    isim: string    rol: string    zorluk: number    ucretsiz: boolean} export const sampiyonlar: Sampiyon[] = [    { id: 1, isim: "Ahri", rol: "Büyücü", zorluk: 2, ucretsiz: true },    { id: 2, isim: "Garen", rol: "Dövüşçü", zorluk: 1, ucretsiz: false },    { id: 3, isim: "Jinx", rol: "Nişancı", zorluk: 2, ucretsiz: true },    { id: 4, isim: "Lux", rol: "Büyücü", zorluk: 1, ucretsiz: false },    { id: 5, isim: "Yasuo", rol: "Dövüşçü", zorluk: 3, ucretsiz: false },    { id: 6, isim: "Teemo", rol: "Nişancı", zorluk: 1, ucretsiz: false },]

Her şampiyonun bir id'si var; veritabanından gelen satırlardaki birincil anahtar gibi. Birazdan neden önemli olduğunu göreceksin.

Diziyi kartlara çevirmek için Ders 11'deki map'i kullan. Bu kez her eleman bir metne değil, bir JSX öğesine dönüşüyor:

src/App.tsx
import SampiyonKarti from "./SampiyonKarti"import { sampiyonlar } from "./veri" export default function App() {    return (        <main>            <h1>Şampiyon Kartları</h1>            <p className="ozet">Koleksiyonunda {sampiyonlar.length} şampiyon var</p>            <div className="kartlar">                {sampiyonlar.map((s) => (                    <SampiyonKarti key={s.id} isim={s.isim} rol={s.rol} zorluk={s.zorluk} />                ))}            </div>        </main>    )}

map altı elemanlı bir JSX dizisi döndürür; süslü parantez içindeki bir diziyi React elemanlarını sırayla yan yana çizerek gösterir. Ok fonksiyonunun gövdesindeki normal parantez ( … ), "bu JSX'i döndür" demek (Ders 10); süslü parantez kullansaydın return yazman gerekirdi. Veriye yedinci bir şampiyon eklersen kart kendiliğinden çıkar; App.tsx'e dokunmazsın.

key neden şart?

key={s.id} kısmını sil ve kaydet. Sayfa aynı görünür, ama konsolda kırmızı bir satır belirir:

Konsol (F12 → Console)
Each child in a list should have a unique "key" prop. Check the render method of `App`. See https://react.dev/link/warning-keys for more information.

"Bir listedeki her çocuğun benzersiz bir key prop'u olmalı. App'in çizim fonksiyonuna bak." React listeyi her yeniden çizdiğinde eski listeyle yenisini karşılaştırır ve hangi kartın hangisi olduğunu key'e bakarak anlar. key yoksa elinde yalnız sıra numarası kalır; liste değişince yanlış kartı yanlış veriyle eşleştirebilir.

key={s.id} ile önce sonra 4 · Lux yeni 1 · Ahri 1 · Ahri 2 · Garen 2 · Garen 3 · Jinx 3 · Jinx 3 kart yerinde kalır, 1 kart eklenir. key olmadan (sıra no) önce sonra 0 · Ahri 0 · Lux 1 · Garen 1 · Ahri 2 · Jinx 2 · Garen 3 · Jinx yeni 3 kartın içi yeniden yazılır, sona 1 kart eklenir.
Şekil 40.4 Listenin başına Lux eklendi. key ile React eski kartları tanır ve yalnız bir kart ekler. Sıra numarasıyla her konumun içeriği değişmiş görünür.

Şimdilik kartların içinde değişen bir şey olmadığı için iki yol da ekranda aynı sonucu verir; sağdaki yalnız fazladan iş yapar. Liste öğelerinin kendi hâli olunca (örneğin içine not yazılmış bir kutu) fark büyür: sıra numarasıyla eşleştirme, Garen'in satırına yazılmış notu listeden Garen çıkınca başka bir satırda bırakır. Bunu Ders 41'de kendi gözünle göreceksin.

key seçerken:

  • Verideki kimliği kullan: id alanı en iyisi. Veritabanından gelen her satırın zaten bir birincil anahtarı var.
  • Benzersiz olsun. Aynı listedeki iki öğenin key'i aynı olamaz (aşağıdaki hata kartı). Farklı listelerde aynı key kullanılabilir.
  • Sabit olsun. key={Math.random()} her çizimde yeni bir değer üretir; React her seferinde bütün kartları yeni sanar.
  • Sıra numarası son çare. map((s, i) => … key={i}) uyarıyı susturur ama yukarıdaki şekildeki sorunu çözmez. Yalnız hiç sıralanmayan, araya ekleme ya da silme yapılmayan listelerde kabul edilebilir.
  • key en dıştaki öğeye yazılır: map'in döndürdüğü öğeye. SampiyonKarti'nin içindeki <article>'a yazmak işe yaramaz.
Sık hataEncountered two children with the same key

Ne zaman çıkar? İki öğeye aynı key'i verince. Örneğin key={s.rol} yazarsan iki Büyücü, iki Dövüşçü ve iki Nişancı çakışır.

Konsol (F12 → Console)
Encountered two children with the same key, `Büyücü`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

"Aynı key'e sahip iki çocuk bulundu: Büyücü. Bileşenler güncellemeler arasında kimliklerini korusun diye key'ler benzersiz olmalı. Benzersiz olmayan key'ler çocukların çoğalmasına ya da kaybolmasına yol açabilir." Mesaj her çakışan değer için bir kez yazılır; burada üç kez.

Nasıl düzelir? Gerçekten benzersiz bir alan kullan: key={s.id}. Verinde böyle bir alan yoksa ekle; veritabanında id sütunu bu yüzden var.

Listeyi kendi bileşenine taşı

Kart listesi kendi başına bir parça: başka bir sayfada da işine yarayabilir. Onu ayrı bir bileşene al. src/KartListesi.tsx:

src/KartListesi.tsx
import SampiyonKarti from "./SampiyonKarti"import type { Sampiyon } from "./veri" type KartListesiProps = {    sampiyonlar: Sampiyon[]} export default function KartListesi({ sampiyonlar }: KartListesiProps) {    return (        <div className="kartlar">            {sampiyonlar.map((s) => (                <SampiyonKarti                    key={s.id}                    isim={s.isim}                    rol={s.rol}                    zorluk={s.zorluk}                />            ))}        </div>    )}

Props'un bir dizi olabileceğine dikkat et: sampiyonlar: Sampiyon[]. Satır uzayınca props'u alt alta yazmak okunurluğu artırır. İkinci satırdaki import type, yalnız bir tip aldığını söyler. Vite tipleri silerken bu satırı da tamamen siler; sıradan import { Sampiyon } yazarsan ne olduğunu aşağıdaki kartta göreceksin.

App.tsx de kısaldı:

src/App.tsx
import KartListesi from "./KartListesi"import { sampiyonlar } from "./veri" export default function App() {    return (        <main>            <h1>Şampiyon Kartları</h1>            <p className="ozet">Koleksiyonunda {sampiyonlar.length} şampiyon var</p>            <KartListesi sampiyonlar={sampiyonlar} />        </main>    )}
Sık hataerror TS1484: 'Sampiyon' is a type and must be imported using a type-only import

Ne zaman çıkar? Bir tipi import type yerine sıradan import ile alınca. Dene: KartListesi.tsx'in ikinci satırını import { Sampiyon } from "./veri" yap ve kaydet. Kartlar ekranda kalır ama konsolda [vite] SyntaxError ve [vite] Failed to reload /src/KartListesi.tsx ile başlayan iki kırmızı satır belirir. Sayfayı yenilersen (F5) sayfa bomboş kalır ve konsolda şu kalır:

Konsol (F12 → Console)
Uncaught SyntaxError: The requested module '/src/veri.ts' does not provide an export named 'Sampiyon'

Tarayıcı açısından doğru bir mesaj: tipler JavaScript'e çevrilirken silindiği için veri.ts'in tarayıcıya giden hâlinde Sampiyon diye bir şey yok. tsc sebebini açıkça söyler:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/KartListesi.tsx:2:10 - error TS1484: 'Sampiyon' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled. 2 import { Sampiyon } from "./veri"           ~~~~~~~~  Found 1 error.

"Sampiyon bir tiptir; verbatimModuleSyntax açıkken yalnız-tip içe aktarmasıyla alınmalı." verbatimModuleSyntax, Vite şablonunun tsconfig.app.json'da açtığı bir ayar: "içe aktarmaları olduğu gibi bırak, tahmin yürütme".

Nasıl düzelir? import type { Sampiyon } from "./veri". Aynı dosyadan hem değer hem tip alıyorsan tek satırda import { sampiyonlar, type Sampiyon } from "./veri" da yazabilirsin.

Artık sayfan üç katlı bir ağaç: App, KartListesi'ni; KartListesi de her şampiyon için bir SampiyonKarti çiziyor. Veri yukarıdan aşağı props'la iniyor:

App veri.ts'den sampiyonlar sampiyonlar={…} 6 şampiyon KartListesi Ahri Garen Jinx Lux Yasuo Teemo Ahri'nin kartına: isim="Ahri" rol="Büyücü" … her kart: SampiyonKarti
Şekil 40.5 Bileşen ağacı ve props akışı. Her ok bir props aktarımı; veri hep yukarıdan aşağı iner.

Koşullu çizim

Bazı parçalar yalnız belli durumlarda görünmeli: bu hafta ücretsiz olan şampiyonların kartında bir rozet, liste boşsa bir uyarı. JSX'in içine if yazamadığını gördün. React'te koşul üç yolla kurulur.

&&: ya göster ya hiçbir şey

Önce JavaScript'in && işaretinin küçük bir sırrını gör. Ders 3'te iki koşulu birleştirmek için kullanmıştın; aslında && her zaman true ya da false döndürmez. Soldaki değer yanlış sayılıyorsa onu, değilse sağdakini döndürür. C:\kod\ogren\ders40.js dosyasında dene:

ders40.js
console.log(true && "rozet")console.log(false && "rozet")console.log(0 && "rozet")console.log(3 && "rozet")
Çıktı
rozetfalse0rozet

React false, true, null ve undefined'ı hiç çizmez. Bu sayede {kosul && <Oge />} "koşul doğruysa öğeyi göster, değilse hiçbir şey" anlamına gelir. SampiyonKarti'ye isteğe bağlı bir ucretsiz prop'u ekle:

src/SampiyonKarti.tsx
type SampiyonKartiProps = {    isim: string    rol: string    zorluk: number    ucretsiz?: boolean} export default function SampiyonKarti({ isim, rol, zorluk, ucretsiz }: SampiyonKartiProps) {    return (        <article className={ucretsiz ? "kart ucretsiz" : "kart"}>            {ucretsiz && <span className="rozet">Ücretsiz</span>}            <div className="harf">{isim[0]}</div>            <h3>{isim}</h3>            <p>{rol}</p>            <p>Zorluk {zorluk}/3</p>        </article>    )}

KartListesi.tsx'te de karta bilgiyi ver: zorluk={s.zorluk} satırının altına ucretsiz={s.ucretsiz} ekle. ucretsiz?: isteğe bağlı olduğu için onu vermeyen bir kart (<SampiyonKarti isim="Lux" rol="Büyücü" zorluk={1} />) da hatasız çalışır; o kartta ucretsiz undefined olur ve rozet çıkmaz.

Üçlü ifade: ya şu ya bu

10. satırdaki ucretsiz ? "kart ucretsiz" : "kart" bir üçlü ifade: koşul ? doğruysa : yanlışsa. Ders 25'teki favori ? "★" : "☆" satırında görmüştün. Bir if/else'in değer üreten kısa hâli olduğu için JSX'in içine yazılabilir. Burada ücretsiz kartlar kart ucretsiz sınıfını alıyor ve CSS onlara yeşil bir kenarlık veriyor. İki ayrı öğe arasında da seçebilirsin: {zorluk === 3 ? <strong>Zor</strong> : <span>Normal</span>}.

Erken return: bambaşka bir görünüm

Liste boşsa boş bir ızgara yerine bir mesaj göstermek istiyorsun. Bu durumda bileşenin bütün görünümü değişiyor; en temiz yol, Ders 6'daki erken çıkış. KartListesi'nin başına ekle:

src/KartListesi.tsx
export default function KartListesi({ sampiyonlar }: KartListesiProps) {    if (sampiyonlar.length === 0) {        return <p className="bos">Gösterilecek şampiyon yok.</p>    }     return (

Bileşen bir fonksiyon olduğu için içinde istediğin JavaScript'i yazabilirsin; JSX'te yasak olan if burada serbest. Yeter ki her yol bir JSX (ya da hiçbir şey çizmemek için null) döndürsün.

Şimdi App'e bu hafta ücretsiz olanlardan oluşan ikinci bir liste ekle. Aynı KartListesi bileşeni, farklı props'la:

src/App.tsx
import KartListesi from "./KartListesi"import { sampiyonlar } from "./veri" export default function App() {    const ucretsizler = sampiyonlar.filter((s) => s.ucretsiz)     return (        <main>            <h1>Şampiyon Kartları</h1>            <p className="ozet">Koleksiyonunda {sampiyonlar.length} şampiyon var</p>            <KartListesi sampiyonlar={sampiyonlar} />            <h2>Bu hafta ücretsiz</h2>            <KartListesi sampiyonlar={ucretsizler} />        </main>    )}
localhost:5173
Şampiyon Kartları sayfası: Koleksiyonunda 6 şampiyon var yazısının altında altı kart, Ahri ve Jinx kartlarında yeşil Ücretsiz rozeti ve yeşil kenarlık. Altında Bu hafta ücretsiz başlığı ve yalnız Ahri ile Jinx'in kartları.
Ahri ve Jinx ücretsiz: rozetleri ve yeşil kenarlıkları var. Alttaki liste aynı bileşenin süzülmüş veriyle çizilmiş hâli.

Sınırı dene: veri.ts'te Ahri ile Jinx'in ucretsiz değerini false yap. Üstteki listede rozet kalmaz, "Bu hafta ücretsiz" başlığının altında da kesik çizgili kutuda Gösterilecek şampiyon yok. yazar. Sonra iki değeri true'ya geri al.

Sık hataEkranda tek başına bir 0 çıkıyor

Ne zaman çıkar? &&'nin solunda bir sayı varken. Diyelim ki başlığın altına "bu hafta kaç şampiyon ücretsiz" satırını, yalnız ücretsiz şampiyon varsa göstermek istedin ve App.tsx'te <h1>'in hemen altına şu satırı ekledin:

src/App.tsx
            <h1>Şampiyon Kartları</h1>            {ucretsizler.length && <p className="ozet">Bu hafta {ucretsizler.length} şampiyon ücretsiz</p>}

Ücretsiz şampiyon varken doğru çalışır: başlığın altında "Bu hafta 2 şampiyon ücretsiz" yazar. Ama Ahri ile Jinx'in ucretsiz değerini yine false yaparsan başlığın altında tek başına bir sıfır belirir:

localhost:5173
Şampiyon Kartları başlığının hemen altında tek başına 0 yazıyor, onun altında Koleksiyonunda 6 şampiyon var satırı ve kartlar.

Hiç ücretsiz şampiyon yokken ucretsizler.length 0 olur; yukarıdaki denemede gördüğün gibi 0 && … sonucu da 0'dır. React false'u çizmez ama 0 bir sayıdır ve sayıları çizer. Hata mesajı yoktur, konsol da susar; ancak gözünle yakalarsın.

Nasıl düzelir? &&'nin soluna gerçek bir boolean koy: {ucretsizler.length > 0 && …}. Kural: &&'nin solunda bir sayı görürsen karşılaştırmaya çevir. Denemeden sonra Ahri ile Jinx'i yine true yap; düzeltilmiş satırı istersen bırak.

Üç yolu ne zaman seçeceğin:

DurumYolÖrnek
Bir parça ya görünsün ya hiç görünmesin&&{ucretsiz && <span>…</span>}
İki seçenekten biri görünsünüçlü ifade{zorluk === 3 ? "Zor" : "Normal"}
Bileşenin bütün görünümü değişsinerken çıkışif (liste.length === 0) return <p>…</p>

React geliştirici araçları

Sayfa büyüdükçe "bu kart hangi bileşenden geliyor, eline hangi props geçti?" sorusu sık sorulur. F12 → Elements sana yalnız son HTML'i gösterir (article, div, h3); bileşenleri göremezsin. React'in bunun için kendi aracı var. Konsolu açtığında React de onu hatırlatıyor:

Konsol (F12 → Console)
Download the React DevTools for a better development experience: https://react.dev/link/react-devtools

React Developer Tools, React ekibinin resmî tarayıcı eklentisi. Kurulumu sen yapacaksın (arayüz değişmiş olabilir; adımların özü aynı kalır):

  1. Tarayıcında react.dev/learn/react-developer-tools adresini aç. Sayfadaki Install for Chrome, Install for Firefox ya da Install for Edge bağlantısından kendi tarayıcınınkine tıkla; Brave'de Chrome bağlantısını kullan. Mağazada arama yapmak yerine bu bağlantıdan gitmek en güvenlisi: aynı adla taklit eklentiler olabilir. Chrome Web Mağazası'nda yayıncı Meta görünür.
  2. Chrome'a ekle (Add to Chrome) düğmesine bas ve izni onayla; Firefox'ta ve Edge'de düğmenin adı biraz farklıdır. Eklenti sayfanın React ağacını okumak için izin ister; mağaza açıklamasına göre veriyi dışarıya göndermez.
  3. localhost:5173 sekmesini yenile, F12'ye bas. Üstteki sekmelerin sonunda ⚛ Components ve ⚛ Profiler belirir. Göremiyorsan sekme sırasının sonundaki » işaretine tıkla.
Elements Console … ⚛ Components ⚛ Profiler Search (text or /regex/) ▾ App ▾ KartListesi SampiyonKarti key="1" SampiyonKarti key="2" SampiyonKarti key="3" … ▸ KartListesi SampiyonKarti props isim: "Garen" rol: "Dövüşçü" zorluk: 1 ucretsiz: false rendered by KartListesi App 1 2 3
Şekil 40.6 Components sekmesinin şeması. Solda bileşen ağacı, sağda seçili bileşenin props'ları. Gerçek panelin renkleri ve düzeni sende biraz farklı olabilir.
  1. Bileşen ağacı: Elements'teki HTML ağacı yerine senin bileşenlerin: App, iki KartListesi, onların altında SampiyonKarti'ler, yanlarında key'leri. Ağaçta bir bileşenin üstüne gelince sayfadaki karşılığı vurgulanır.
  2. Props: Ağaçta bir bileşen seçince sağda aldığı props görünür. rendered by kısmı onu kimin çizdiğini söyler. Değerler düzenlenebilir: Garen'in zorluk değerinin üstüne tıkla, 3 yaz ve Enter'a bas; sayfadaki kart anında "Zorluk 3/3" olur. true/false değerlerin yanında bir onay kutusu vardır, tıklamak yeter. Değişiklik geçicidir; sayfayı yenileyince dosyadaki değere döner.
  3. Arama: Büyük bir ağaçta bileşeni adıyla bulursun: Sampiyon yaz.

Bir kart yanlış görünüyorsa önce ona bak: props doğruysa sorun bileşenin JSX'inde, yanlışsa veriyi veren üst bileşende. Profiler sekmesi hangi bileşenin ne kadar sürede çizildiğini ölçer; performansla uğraşırken işine yarar, şimdilik gerek yok.

Sık hatalar

Bileşen, JSX ve props hataları ilgili bölümlerde. Burada projeyi çalıştırırken karşına çıkabilecekler var. Bir hatayla karşılaşınca bütün derslerin hatalarını toplayan hata rehberine de bakabilirsin.

Sık hatanpm error enoent Could not read package.json

Ne zaman çıkar? npm run dev'i projenin klasöründe değil, bir üst klasörde (örneğin C:\kod) çalıştırınca.

Terminal — PowerShell
PS C:\kod> npm run devnpm error code ENOENTnpm error syscall opennpm error path C:\kod\package.jsonnpm error errno -4058npm error enoent Could not read package.json: Error: ENOENT: no such file or directory, open 'C:\kod\package.json'npm error enoent This is related to npm not being able to find a file.npm error enoentnpm error A complete log of this run can be found in: C:\Users\Deniz\AppData\Local\npm-cache\_logs\2026-10-12T00_13_26_846Z-debug-0.log

path satırına bak: npm C:\kod\package.json'ı aradı. dev betiği package.json'da yazılı; o dosya da projenin klasöründe.

Nasıl düzelir? cd sampiyon-kartlari ile projeye gir, sonra yeniden çalıştır. Daha iyisi: projeyi code sampiyon-kartlari ile kendi penceresinde aç; o pencerenin terminali zaten doğru klasörde başlar.

Sık hata'vite' is not recognized as an internal or external command

Ne zaman çıkar? Projeyi oluşturup npm install'u atlayınca, ya da projeyi başka bir bilgisayara kopyalayıp (node_modules Git'e girmediği için yanında gelmez) kurmadan çalıştırınca.

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npm run dev > sampiyon-kartlari@0.0.0 dev> vite 'vite' is not recognized as an internal or external command,operable program or batch file.

npm, betikleri Windows'un komut istemiyle (cmd) çalıştırır; bu satır onun mesajı. Türkçe mesajlı bir Windows'ta aynı satır Türkçe görünür, içinde yine 'vite' adı geçer. vite komutu node_modules klasöründen gelir; klasör yoksa komut da yok.

Nasıl düzelir? npm install, sonra npm run dev.

Sık hataSayfa bomboş, hiçbir hata yok (yalnız return)

Ne zaman çıkar? return'den sonra JSX'i parantezsiz, bir alt satırdan başlatınca:

src/App.tsx✗ Yanlış
export default function App() {    return        <main>            <h1>Şampiyon Kartları</h1>        </main>}

Tarayıcıda ne hata katmanı ne konsol uyarısı çıkar; sayfa yalnız boştur. JavaScript, satır sonunda biten bir return'ü orada bitmiş sayar: fonksiyon hiçbir şey döndürmez ve alttaki JSX hiç çalışmaz. React de "bu bileşen hiçbir şey çizmek istemiyor" diye bir şey çizmez. Tuzağı yakalayan yine tsc, hem de beklemediğin bir dosyada:

Terminal — PowerShell
PS C:\kod\sampiyon-kartlari> npx tsc -bsrc/main.tsx:8:6 - error TS2786: 'App' cannot be used as a JSX component.  Its type '() => void' is not a valid JSX element type.    Type '() => void' is not assignable to type '(props: any) => ReactNode | Promise<ReactNode>'.      Type 'void' is not assignable to type 'ReactNode | Promise<ReactNode>'. 8     <App />       ~~~  Found 1 error.

Zincirin en içi: App'in dönüş tipi void, yani "hiçbir şey döndürmüyor". Bileşen olabilmek için çizilebilecek bir şey döndürmesi gerekirdi.

Nasıl düzelir? Açılış parantezini return ile aynı satıra yaz: return ( ve JSX'i parantez içine al. Bu derste hep böyle yazdık.

Görevler

Görevleri aynı projede yap. Her görevin bileşenini kendi dosyasına yaz, denemek için App.tsx'te </main>'in hemen üstüne ekle. Bir görev bitince iki denetim yap: tarayıcıda konsol temiz mi (kırmızı satır yok), ikinci terminalde npx tsc -b sessiz mi? İkisi de temizse görev bitmiştir.

Görev 1

HTML'den JSX'e: oyuncu profili

Bir arkadaşın profil kutusunu HTML olarak yazmış. src/Profil.tsx dosyasını oluştur, aşağıdakini olduğu gibi yapıştır ve App.tsx'e <Profil /> ekle:

src/Profil.tsx✗ Yanlış
export default function Profil() {    return (        <h2 class="baslik">Oyuncu Profili</h2>        <p>Seviye 30<br>Rütbe: Altın</p>        <hr>        <label for="not">Maç notu</label>        <input id="not" placeholder="Bugün ne öğrendin?">    )}

Bu kodda JSX kurallarına aykırı altı yer var. Gözle aramadan, mesajları sırayla okuyarak düzelt: Vite'ın hata katmanı, sonra konsol, en son npx tsc -b. Sonunda sayfanın altında başlık, iki satırlık seviye bilgisi, bir çizgi, "Maç notu" etiketi ve yazılabilen bir kutu görünmeli.

İpucu 1

İlk mesaj Adjacent JSX elements must be wrapped in an enclosing tag. Bu dersteki hangi kurala uymuyor? Etiketleri bir <section> ile sar.

İpucu 2

Sarınca sıradaki mesajlar kendini kapatmayan etiketlerden gelir. <br>, <hr> ve <input …> etiketlerinin sonu /> olmalı.

İpucu 3

Sayfa görününce konsolda iki uyarı kalır: Invalid DOM property class ve for için. JSX'teki adları tabloda.

Çözümü göster
src/Profil.tsx
export default function Profil() {    return (        <section>            <h2 className="baslik">Oyuncu Profili</h2>            <p>Seviye 30<br />Rütbe: Altın</p>            <hr />            <label htmlFor="not">Maç notu</label>            <input id="not" placeholder="Bugün ne öğrendin?" />        </section>    )}

Sayfanın altında görünen metin:

Metin
Oyuncu Profili Seviye 30Rütbe: Altın Maç notu

Altı düzeltme: <section> ile tek kök (sayfaya fazladan etiket istemiyorsan <>…</> da olur), <br />, <hr />, <input … />, className ve htmlFor. class ve for sayfayı bozmadığı için "çalışıyor" görünür; konsol ve npx tsc -b (TS2322, Did you mean 'className'? ve Did you mean 'htmlFor'?) temizlenene kadar görev bitmiş sayılmaz. htmlFor sayesinde "Maç notu" yazısına tıklayınca imleç kutuya gider (Ders 19).

Görev 2

Eşya kartı

src/EsyaKarti.tsx adında bir bileşen yaz. Üç prop alsın: ad (metin), fiyat (sayı) ve etki (metin). Kartta önce ad (<h3>), sonra 350 altın biçiminde fiyat, en altta etki yazsın. Var olan kart ve kartlar sınıflarını kullanabilirsin. App.tsx'te bir kartlar kutusunun içine üç eşya koy:

adfiyatetki
Uzun Kılıç350+10 saldırı gücü
Yakut Kristali400+150 can
Büyü Kitabı435+20 yetenek gücü

Sonra bilerek bir hata yap: fiyatlardan birini tırnakla yaz (fiyat="400") ve npx tsc -b'nin ne dediğini oku. Düzeltip bitir.

İpucu 1

Önce props'un tipini yaz: type EsyaKartiProps = { … } ve üç alan. Bileşenin parametresi { ad, fiyat, etki }: EsyaKartiProps.

İpucu 2

Fiyat bir sayı: fiyat={350}. Metindeki " altın" eki JSX'in içinde yazılır: <p>{fiyat} altın</p>.

Çözümü göster
src/EsyaKarti.tsx
type EsyaKartiProps = {    ad: string    fiyat: number    etki: string} export default function EsyaKarti({ ad, fiyat, etki }: EsyaKartiProps) {    return (        <article className="kart">            <h3>{ad}</h3>            <p>{fiyat} altın</p>            <p>{etki}</p>        </article>    )}

App.tsx'te (dosyanın başına import EsyaKarti from "./EsyaKarti" ekledikten sonra):

src/App.tsx
            <div className="kartlar">                <EsyaKarti ad="Uzun Kılıç" fiyat={350} etki="+10 saldırı gücü" />                <EsyaKarti ad="Yakut Kristali" fiyat={400} etki="+150 can" />                <EsyaKarti ad="Büyü Kitabı" fiyat={435} etki="+20 yetenek gücü" />            </div>

Sayfanın altında üç kart: Uzun Kılıç / 350 altın / +10 saldırı gücü ve diğerleri. fiyat="400" yazınca tsc: error TS2322: Type 'string' is not assignable to type 'number'. Sayfa yine "400 altın" gösterir; bu yüzden tip denetimini atlama.

Görev 3

Dükkân listesi

Dükkândaki eşyaları bir diziden çiz. src/esyalar.ts dosyasını oluştur:

src/esyalar.ts
export type Esya = {    id: number    ad: string    fiyat: number    stokta: boolean} export const esyalar: Esya[] = [    { id: 1, ad: "Uzun Kılıç", fiyat: 350, stokta: true },    { id: 2, ad: "Yakut Kristali", fiyat: 400, stokta: false },    { id: 3, ad: "Görüş Totemi", fiyat: 0, stokta: true },    { id: 4, ad: "Sonsuzluk Kılıcı", fiyat: 3450, stokta: true },]

Sonra src/Dukkan.tsx'te esyalar prop'u alan bir Dukkan bileşeni yaz. Her eşya bir <li> olsun: Ad: fiyat. Fiyatı 0 olan eşyada fiyat yerine Bedava yazsın; stokta olmayan eşyanın sonuna kalın harfle (Tükendi) eklensin. App.tsx'e <Dukkan esyalar={esyalar} /> koy. Konsolda key uyarısı olmamalı. Beklenen:

Metin
Uzun Kılıç: 350 altınYakut Kristali: 400 altın (Tükendi)Görüş Totemi: BedavaSonsuzluk Kılıcı: 3450 altın
İpucu 1

<ul> içinde esyalar.map((e) => (<li key={e.id}>…</li>)). key için hangi alan benzersiz?

İpucu 2

"Ya Bedava ya fiyat" iki seçenekten biri: üçlü ifade. "Tükendi ya var ya yok": &&. Stokta olmama koşulu !e.stokta.

İpucu 3

Veri dosyasına dukkan.ts deme: Windows büyük-küçük harfi ayırmadığı için ./Dukkan içe aktarması bileşen yerine veri dosyasını bulabilir. esyalar.ts bu yüzden.

Çözümü göster
src/Dukkan.tsx
import type { Esya } from "./esyalar" type DukkanProps = {    esyalar: Esya[]} export default function Dukkan({ esyalar }: DukkanProps) {    return (        <ul>            {esyalar.map((e) => (                <li key={e.id}>                    {e.ad}: {e.fiyat === 0 ? "Bedava" : e.fiyat + " altın"}                    {!e.stokta && <strong> (Tükendi)</strong>}                </li>            ))}        </ul>    )}

App.tsx'in başına iki içe aktarma gerekir: import Dukkan from "./Dukkan" ve import { esyalar } from "./esyalar".

Sınır değeri: Görüş Totemi'nin fiyatı tam 0. {e.fiyat && …} gibi bir kısayol deneseydin bu satırda 0 görürdün; e.fiyat === 0 açıkça soruyor. stokta bir boolean olduğu için !e.stokta && … güvenli.

Görev 4

Altının neye yeter?

Dukkan'ı geliştir: altin adında ikinci bir prop alsın (sayı) ve yalnız stokta olan ve fiyatı altına yeten eşyaları listelesin. Listenin üstünde <h3> ile 350 altınla 2 eşya alabilirsin gibi bir başlık olsun. İki özel durum:

  • Hiçbir eşya alınamıyorsa liste ve başlık yerine yalnız Altının hiçbir eşyaya yetmiyor. yazsın.
  • esyalar dizisi boşsa Dükkân boş. yazsın.

Beş durumla dene, her biri App.tsx'te ayrı bir satır:

src/App.tsx
<Dukkan esyalar={esyalar} altin={350} /><Dukkan esyalar={esyalar} altin={349} /><Dukkan esyalar={esyalar} altin={0} /><Dukkan esyalar={esyalar.filter((e) => e.fiyat > 0)} altin={0} /><Dukkan esyalar={[]} altin={5000} />

Beklenen (sırayla):

Metin
350 altınla 2 eşya alabilirsinUzun Kılıç: 350 altınGörüş Totemi: Bedava349 altınla 1 eşya alabilirsinGörüş Totemi: Bedava0 altınla 1 eşya alabilirsinGörüş Totemi: BedavaAltının hiçbir eşyaya yetmiyor.Dükkân boş.
İpucu 1

Önce const alinabilir = esyalar.filter((e) => …). İki koşul && ile: stokta mı, fiyat altına yetiyor mu? "Yetiyor" < mu, <= mü? 350 altınla 350'lik kılıç alınabilmeli.

İpucu 2

İki özel durum iki erken return. Sıra önemli: dizi boşsa "yetmiyor" demek yanlış olur, önce boşluğa bak.

İpucu 3

Başlıkta {alinabilir.length} kullanırken && tuzağını hatırla. Erken return ile boş durumu zaten ayıkladıysan başlığı koşulsuz yazabilirsin.

Çözümü göster
src/Dukkan.tsx
import type { Esya } from "./esyalar" type DukkanProps = {    esyalar: Esya[]    altin: number} export default function Dukkan({ esyalar, altin }: DukkanProps) {    if (esyalar.length === 0) {        return <p className="bos">Dükkân boş.</p>    }     const alinabilir = esyalar.filter((e) => e.stokta && e.fiyat <= altin)     if (alinabilir.length === 0) {        return <p className="bos">Altının hiçbir eşyaya yetmiyor.</p>    }     return (        <section>            <h3>{altin} altınla {alinabilir.length} eşya alabilirsin</h3>            <ul>                {alinabilir.map((e) => (                    <li key={e.id}>                        {e.ad}: {e.fiyat === 0 ? "Bedava" : e.fiyat + " altın"}                    </li>                ))}            </ul>        </section>    )}

Sınırlar: 350 ile 349 arasındaki fark <='yu sınar (Uzun Kılıç tam 350). 0 altın, bedava totemi yine alabilir. Yakut Kristali hiçbir altın miktarında listeye girmez, çünkü stokta yok; altin={5000} ile de dene, üç eşya çıkmalı. Görev 3'teki (Tükendi) satırını sildik: stokta olmayan eşya artık listeye hiç girmiyor.

Görev 5

Maç geçmişi

Son maçlarını gösteren iki bileşen yaz. Veri src/maclar.ts'te:

src/maclar.ts
export type Mac = {    id: number    sampiyon: string    sonuc: "zafer" | "yenilgi"    kill: number    olum: number    asist: number} export const maclar: Mac[] = [    { id: 101, sampiyon: "Ahri", sonuc: "zafer", kill: 8, olum: 2, asist: 11 },    { id: 102, sampiyon: "Garen", sonuc: "yenilgi", kill: 3, olum: 6, asist: 4 },    { id: 103, sampiyon: "Jinx", sonuc: "zafer", kill: 12, olum: 0, asist: 7 },]
  • MacSatiri (src/MacSatiri.tsx): tek bir prop alsın, mac (tipi Mac). Bir <li> döndürsün: kalın harfle Zafer ya da Yenilgi, ardından · Ahri · 8/2/11. <li>'nin sınıfı maçın sonucu olsun (zafer ya da yenilgi). Hiç ölmediği maçta (olum 0) sonuna Ölümsüz rozeti eklensin.
  • MacGecmisi (src/MacGecmisi.tsx): maclar prop'u alsın. Liste boşsa yalnız Henüz maç yok. yazsın. Değilse önce <h3> ile Son 3 maç: 2 zafer, sonra maclar sınıflı bir <ul> içinde her maç için bir MacSatiri.

Renkler için index.css'in sonuna ekle:

src/index.css
.maclar {    padding: 0;    list-style: none;} .maclar li {    margin-bottom: 8px;    padding: 10px 12px;    border-left: 4px solid;    border-radius: 8px;    background: #211e2e;} .maclar .zafer {    border-color: #2f8f62;} .maclar .yenilgi {    border-color: #b8434e;} .rozet-kucuk {    margin-left: 8px;    padding: 1px 6px;    border-radius: 999px;    background: #1f6f4a;    font-size: 12px;}

Üç durumla dene:

src/App.tsx
<MacGecmisi maclar={maclar} /><MacGecmisi maclar={[]} /><MacGecmisi maclar={maclar.filter((m) => m.sonuc === "yenilgi")} />
localhost:5173
Son 3 maç: 2 zafer başlığı ve üç satır: yeşil kenarlı Zafer Ahri 8/2/11, kırmızı kenarlı Yenilgi Garen 3/6/4, yeşil kenarlı Zafer Jinx 12/0/7 ve yanında Ölümsüz rozeti. Altında kesik çizgili kutuda Henüz maç yok. En altta Son 1 maç: 0 zafer ve kırmızı kenarlı Yenilgi Garen 3/6/4.
Beklenen sonuç (sayfanın alt kısmı).

Son adım, React Developer Tools'u kurduysan: ⚛ Components sekmesinde üç MacGecmisi'yi bul ve maclar prop'larındaki eleman sayısını karşılaştır. Sonra Jinx'in MacSatiri'sini seç, mac prop'unu aç ve olum değerini 1 yap: Ölümsüz rozeti kaybolmalı.

İpucu 1

MacSatiri'nin props tipi: type MacSatiriProps = { mac: Mac }. Bir prop'un kendisi bir obje olabilir; içeride mac.sampiyon, mac.kill diye okursun. Mac tipini import type ile al.

İpucu 2

Sınıf için ayrı bir koşula gerek yok: className={mac.sonuc}. Metin için üçlü ifade: mac.sonuc === "zafer" ? "Zafer" : "Yenilgi". Rozet için {mac.olum === 0 && …}; mac.olum && … yazsaydın ne olurdu?

İpucu 3

Zafer sayısı: maclar.filter((m) => m.sonuc === "zafer").length. key hangi bileşene, hangi değerle? map'in döndürdüğü öğeye: <MacSatiri key={m.id} mac={m} />.

Çözümü göster
src/MacSatiri.tsx
import type { Mac } from "./maclar" type MacSatiriProps = {    mac: Mac} export default function MacSatiri({ mac }: MacSatiriProps) {    return (        <li className={mac.sonuc}>            <strong>{mac.sonuc === "zafer" ? "Zafer" : "Yenilgi"}</strong>            {" · "}{mac.sampiyon} · {mac.kill}/{mac.olum}/{mac.asist}            {mac.olum === 0 && <span className="rozet-kucuk">Ölümsüz</span>}        </li>    )}
src/MacGecmisi.tsx
import MacSatiri from "./MacSatiri"import type { Mac } from "./maclar" type MacGecmisiProps = {    maclar: Mac[]} export default function MacGecmisi({ maclar }: MacGecmisiProps) {    if (maclar.length === 0) {        return <p className="bos">Henüz maç yok.</p>    }     const zaferSayisi = maclar.filter((m) => m.sonuc === "zafer").length     return (        <section>            <h3>Son {maclar.length} maç: {zaferSayisi} zafer</h3>            <ul className="maclar">                {maclar.map((m) => (                    <MacSatiri key={m.id} mac={m} />                ))}            </ul>        </section>    )}

{" · "}: satır sonundaki boşlukları JSX siler; </strong>'dan hemen sonra boşluk istediğimiz için onu bir metin olarak yazdık. Sınırlar: Jinx'in 0 ölümlü maçı rozeti sınar; mac.olum && … yazsaydın Jinx'in satırına 0 düşerdi. Yalnız yenilgilerden oluşan listede başlık 0 zafer der; bu kez sıfırı bilerek yazdırıyoruz, &&'nin solunda değil. sonuc alanı "zafer" | "yenilgi" birleşim tipi olduğu için sonuc: "galibiyet" gibi bir yazım hatasını tsc daha veri dosyasında yakalar (Ders 39).

Components sekmesinde maclar dizileri 3, 0 ve 1 elemanlı görünür. Jinx'in olum değerini 1 yapınca rozet kaybolur, 0'a döndürünce geri gelir: MacSatiri aldığı props'a göre çiziliyor. Sayfayı yenileyince her şey dosyadaki değerlere döner.

Kendini sına

Soru 1

oyuncu "Lux", seviye 5 iken bu paragraf sayfada ne gösterir?

TSX
<p>{oyuncu} {seviye + 1}. seviyeye ulaştı ({"seviye"})</p>
Cevabı göster

Lux 6. seviyeye ulaştı (seviye). Süslü parantezin içi JavaScript olarak hesaplanır: oyuncu değişkeninin değeri, seviye + 1 toplamı. Son süslü parantezin içinde ise bir metin var; metin olduğu gibi yazılır. Süslü parantezin dışındaki her şey düz yazıdır.

Soru 2

Bu bileşende hangi JSX kuralları çiğneniyor? Hangileri Vite'ın hata katmanını açar, hangisini yalnız konsol ve tsc yakalar?

TSX
export default function Baslik() {    return (        <h1 class="baslik">Takımım</h1>        <img src="/logo.svg" alt="">    )}
Cevabı göster

Üç kural: (1) iki kök öğe, <h1> ile <img> yan yana; (2) <img> kapanmamış, /> olmalı; (3) class yerine className gerekir. İlk ikisi yazım hatasıdır ve Vite'ın hata katmanını açar, ama sırayla: önce yalnız Adjacent JSX elements must be wrapped in an enclosing tag. gelir. Kökü <> … </> ile düzeltince kapanmamış <img> yüzünden dosyanın son satırında Unexpected token. Did you mean {'}'} or &rbrace;? gibi şaşırtıcı bir mesaj çıkar; <br> kartındaki gibi, Vite etiketin kapanışını ararken dosyanın sonuna çarpmıştır. tsc ikisini birlikte ve daha açık söyler: TS2657 ve TS17008: JSX element 'img' has no corresponding closing tag. class ise yazım hatası değildir; sayfa çalışır, konsol Invalid DOM property diye uyarır ve tsc TS2322 ile Did you mean 'className'? der. Düzeltilmiş hâli: <> … </> içinde <h1 className="baslik"> ve <img src="/logo.svg" alt="" />.

Soru 3

const kartlar: Sampiyon[] = [] iken <div>{kartlar.length && <KartListesi sampiyonlar={kartlar} />}</div> ne gösterir? Düzelt.

Cevabı göster

0. Boş dizinin length'i 0, 0 && … sonucu da 0; React sayıları çizer. Düzeltme: {kartlar.length > 0 && <KartListesi sampiyonlar={kartlar} />}. Bu derste yazdığın KartListesi boş listeyi zaten kendi erken return'üyle ele aldığı için, istersen koşulu tamamen kaldırıp her zaman <KartListesi sampiyonlar={kartlar} /> da yazabilirsin.

Soru 4

Takım arkadaşın key uyarısını susturmak için key={Math.random()} yazmış. Uyarı kayboldu. Neden yine de yanlış?

Cevabı göster

key'in işi, liste yeniden çizildiğinde aynı öğeyi tanımak. Math.random() her çizimde yeni bir sayı üretir; React her seferinde "bunların hepsi yeni" sanar, bütün kartları atıp baştan kurar. Kimlik hiç tutmadığı için, öğelerin kendi hâli olduğunda (Ders 41'de göreceğin, içine not yazılmış bir kutu gibi) o hâl her güncellemede silinir. key benzersiz ve sabit olmalı: key={s.id}.

Soru 5

<SampiyonKarti isim="Lux" rol="Büyücü" zorluk="1" /> satırı tarayıcıda sorunsuz görünüyor. Kim, ne zaman itiraz eder?

Cevabı göster

Ne Vite ne tarayıcı: Vite tipleri yalnız siler, sayfa "Zorluk 1/3" gösterir. İtiraz eden tip denetimidir: VS Code satırın altını kırmızıyla çizer, npx tsc -b de error TS2322: Type 'string' is not assignable to type 'number'. der. Düzeltme zorluk={1}: metin dışındaki değerler süslü parantezle verilir.

Soru 6

Bir Vite projesinde npx tsc --noEmit hiçbir şey yazmadı. Proje gerçekten temiz mi? Ne yaparsın?

Cevabı göster

Belli değil. Vite'ın ana tsconfig.json'ı "files": [] ile hiçbir dosya içermez, yalnız tsconfig.app.json ve tsconfig.node.json'a yol gösterir. --noEmit bu yol göstermeyi izlemez; denetleyecek dosya bulamaz ve sessizce biter. Doğru komut npx tsc -b: referansları izleyip src'deki dosyaları denetler. npm run build de aynı denetimi yapar.

Soru 7

Ekranda bir kartın rolü boş görünüyor. React Developer Tools ile sorunun SampiyonKarti'de mi, ona veri veren yerde mi olduğunu nasıl anlarsın?

Cevabı göster

⚛ Components sekmesinde o kartın SampiyonKarti'sini seç ve sağdaki props'a bak. rol prop'u boşsa ya da hiç yoksa sorun yukarıda: rendered by satırındaki bileşen (KartListesi) ya da veri dosyası. rol dolu geliyorsa sorun SampiyonKarti'nin JSX'inde, örneğin {rol} yazmayı unutmuşsundur. (Bu derste gördüğün gibi, eksik prop'u npx tsc -b de TS2741 ile yakalar.)

Kayıt noktası

Commit'ten önce son denetim: tarayıcıda konsol temiz, ikinci terminalde npx tsc -b sessiz olmalı. Görevler için App.tsx'e eklediğin satırları istersen bırak, istersen sil; dosyaları kalsın. Vite, node_modules ve dist klasörlerini dışarıda tutan bir .gitignore'u projeye zaten koydu.

Sıradaki derste sayfan canlanıyor: state ile değişen veriyi tutacak, tıklamalara ve yazılan metne tepki veren bileşenler yazacaksın.

İşaret yalnızca bu tarayıcıda saklanır, kimseye gönderilmez.