Kısım II · İnşaat · Bölüm 31
Framework'ler, React ve Bileşenler
Ekranı elle değil, durumdan çizmek (Frameworks, React, components)
Neden burada: Bölüm 26'da DOM'u elle yönettik: her olayda hangi öğenin değişeceğini sen yazdın. Puan, seri, can, ilerleme çubuğu, soru kartı ve leaderboard ekrana yayılınca "hangi olay hangi öğeyi güncelliyor" listesi yönetilemez olur. Bu bölüm cevabı veriyor: ekranı bir durumdan çizen ve güncellemeyi kendisi yapan bir katman. Bölüm 32 (telefon) ve 33 (sunucu verisi) bunun üstüne kurulur.
Neye dayanıyor: Bölüm 26 (DOM, olay, durum); Bölüm 27 (her bileşen bir modüldür); Bölüm 29 (dönüştürücü JSX'i JavaScript'e çevirir); Bölüm 30 (özelliklerin tipi); Bölüm 21 (bildirimsel, buyurgan); Bölüm 10 (değer ve referans).
- Kat 10Bizim uygulamamızSoru kartı, puan rozeti, leaderboard: kodlarının düzeni burada kurulur.
- Kat 9Web ve tarayıcı: HTML, CSS, JavaScriptReact bir JavaScript kütüphanesidir; sonunda DOM'a yazar. Bölüm 20, 23–26.
- Kat 8Ağ ve internetBölüm 19–20.
- Kat 7İşletim sistemiBölüm 13–18.
- Kat 6Gömülü yazılım ve açılışBölüm 12.
- Kat 5Programlama dilleri ve derleyiciJSX'i araç zinciri JavaScript'e çevirir. Bölüm 21, 25, 29.
- Kat 4Makine dili ve assemblyBölüm 7.
- Kat 3İşlemci ve bellekBölüm 6, 8.
- Kat 2Devreler: hesap ve hafızaBölüm 4–5.
- Kat 1Transistör ve mantık kapılarıBölüm 3.
- Kat 0Elektrik ve bit (zemin)Bölüm 2.
Bu bölümün sonunda şunları bileceksin: kütüphane ile çerçevenin farkını (kontrolün tersine çevrilmesi; kütüphane burada Bölüm 9'daki derlenmiş kütüphane değil, tasarım anlamında); bileşen, özellik ve durumun ne olduğunu; JSX'in HTML olmadığını; "yeniden çizim" ile "DOM'un güncellenmesi" farkını; useState ve useEffect'in ne zaman çalıştığını; yönlendirmenin neden React'in çekirdeğinde olmadığını. Bir agent sana React kodu gösterdiğinde hangi satırın "çizme", hangisinin "olay", hangisinin "etki" olduğunu ayırabilmek hedef. Bölümdeki kodları gerçekten çalıştırdım (React 19.3.1, Node 22, jsdom adlı DOM benzetimi); çıktılar olduğu gibi duruyor.
31.1 Kütüphane ile çerçeve; kontrolün tersine çevrilmesi
Önce sorun. Bölüm 26'daki yöntemi bizim uygulamaya uygulayalım. Aşağıdaki, prototipinden değil kafadan yazılmış bir taslak (prototipin içini görmedik); yalnızca yapıyı gösteriyor:
function cevapVerildi(dogruMu) {
if (dogruMu) {
puan += puanHesapla(true, seri);
seri += 1;
} else {
seri = 0;
can -= 1;
}
puanEl.textContent = puan;
seriEl.textContent = seri;
canEl.textContent = can;
ilerlemeEl.style.width = ilerleme + "%";
}
Önce değişkenler güncelleniyor, sonra her gösterge için DOM'daki karşılığı elle yazılıyor. Dört gösterge ve üç olay (doğru, yanlış, bölüm değişimi) varsa en fazla 4 × 3 = 12 hücrelik bir "hangi olay hangi göstergeyi günceller" tablosunu akılda tutman gerekir; unutulan her hücre ekranda eski bilgi demektir. React belgesi bunu şöyle söyler: elle yönetim yalıtılmış örneklerde yeter, ama karmaşık sistemlerde üstel olarak zorlaşır; yeni bir öğe, hata sokmadığından emin olmak için mevcut kodun tamamını kontrol etmeyi gerektirir.6 React bu soruna cevap olarak yapıldı.
Kütüphane: sen çağırırsın. Çerçeve: o çağırır.
Martin Fowler'ın tanımı kısadır. Kütüphane (library) çağırabileceğin bir fonksiyonlar kümesidir; her çağrı bir iş yapar ve kontrolü çağırana geri verir.20 [3, 1, 2].sort() yazdığında akış sendedir. Çerçeve (framework) ise soyut bir tasarımı içinde taşır; sen kendi kodunu onun belirli noktalarına takarsın ve çerçeve o noktalarda kodunu çağırır.20 Fowler buna kontrolün tersine çevrilmesi (inversion of control) der ve "Hollywood ilkesi"ni aktarır: "Bizi arama, biz seni ararız."20 Çıplak halini küçük bir oyuncak çerçevede görelim (kendi yazdığım, çalıştırdığım kod):
const kayitli = [];
function olayDinle(ad, fonksiyon) {
kayitli.push({ ad, fonksiyon });
}
function anaDongu(olay) {
for (const k of kayitli) {
if (k.ad === olay) k.fonksiyon();
}
}
olayDinle("tikla", () => console.log("Gönder'e basıldı"));
anaDongu("tikla");
console.log([3, 1, 2].sort().join(","));
kayitli bir liste; olayDinle ad ile fonksiyonu listeye yazar (çalıştırmaz); anaDongu bir olay gelince adı uyan fonksiyonları kendisi çağırır. Çıktı: Gönder'e basıldı ve 1,2,3. Fonksiyonu başka bir fonksiyona değer gibi vermek, Bölüm 21'de fonksiyonel programlamada gördüğün "fonksiyonlar değer gibi dolaştırılabilir" fikridir: kontrolü çevirmek, çağırma işini sonra yapacak birine bir fonksiyon bırakmaktır. Hukuk benzetmesi: kanun kitabından istediğin maddeye bakmak kütüphane gibidir; duruşmada ise sırayı mahkeme belirler, sen söz sırası geldiğinde söyleyeceğin metni hazırlarsın. Çerçeve duruşma düzenidir.
React hangisi?
Dürüst cevap: sınırda. React'in belgesi onu açıkça "JavaScript kütüphanesi" diye adlandırır.3 Ama çizmede kontrol tersine çevrilmiştir: çizme "React'in bileşenlerini çağırması"dır;8 sen SoruKarti() yazmazsın, React çağırır. Olay işleyicileri de öyle. Öte yandan yönlendirme, veri çekme, derleme gibi kararları React vermez. Belgeler yeni projeye "çerçeveyle başlamayı" önerir; Next.js, React Router ve Expo'yu sayar, Expo'yu "gerçek yerel arayüzlü Android, iOS ve web uygulamaları yapan bir React çerçevesi" diye tarif eder.18 Derleme aracıyla sıfırdan başlamak ise "çoğu zaman kendi çerçeveni yazmak"tır; yönlendirme ve veri çekme için ek çözüm gelmez.19
| Kütüphane | Çerçeve | |
|---|---|---|
| Akışı kim yönetir? | Sen çağırırsın | O, senin kodunu çağırır |
| Bizim yığında | TanStack Query (Bölüm 33) | Expo (Bölüm 32) |
| Kazancı / bedeli | Esneklik / kararlar sende | Hazır kararlar / kurallarına uyarsın |
Agent kod önerdiğinde sor: "bu çağrıyı ben mi yapıyorum, çerçeve mi çağıracak?" Cevap kodun ne zaman çalışacağını söyler; 31.4'te kancaların neden "hemen" değil "ekran güncellendikten sonra" çalıştığı buradan çıkar.
31.2 Bileşen, özellikler, durum; bildirimsel kullanıcı arayüzü
Bileşen
Bileşen (component), belgenin tanımıyla, kendi mantığı ve görünümü olan bir kullanıcı arayüzü parçasıdır; düğme kadar küçük, sayfa kadar büyük olabilir. React bileşenleri işaretleme döndüren JavaScript fonksiyonlarıdır ve adları büyük harfle başlar (küçük harfle başlayan HTML etiketidir).1 Bileşenler iç içe geçip bir ağaç kurar.25 Bölüm 23'teki DOM ağacıyla karıştırma: bileşen ağacı bizim kurduğumuz mantıksal parçalar, DOM sonunda çıkan HTML düğümleri. Her bileşen Bölüm 27'deki anlamıyla bir modüldür: bir dosya, bir export, dar bir arayüz ve içeride gizli ayrıntı.
SoruEkrani (puan ve seri burada yaşar)
├── PuanRozeti
└── SoruKarti (soru, şıklar, Gönder düğmesi)
Özellikler
Özellikler (props), bir JSX etiketine verdiğin bilgilerdir.4 Bileşen açısından fonksiyonun parametresidir (Bölüm 9); React hepsini tek nesne olarak verir, sen { soru, seri, onCevap } diye parçalarsın. İki kural: özellikler değiştirilemez; bileşen başka bir değer istiyorsa üst bileşenden yenisini göndermesini "ister".4 Ve bir fonksiyon da özellik olabilir: belgenin örneğinde Button bileşeni onClick adlı bir özellik alır ve tıklanınca React ona verilen fonksiyonu çağırır;27 bizdeki onCevap aynı kalıptır (adı benim seçimim): çocuk üste "bir şey oldu" der. Böylece veri aşağı (özellikle), olay yukarı (fonksiyon çağrısıyla) akar; belge buna tek yönlü veri akışı der.28
Durum
Bileşenin hatırlaması gereken şeyler var: seçili şık, puan. Sıradan yerel değişken yetmez; belge iki neden söyler: yerel değişken çizimler arasında kalıcı değildir (bileşen her seferinde baştan çağrılır) ve değişmesi React'e yeniden çizim gerektiğini bildirmez.7 Durum (state) bu iki işi yapar: değeri çizimler arasında saklar ve değişince yeniden çizimi tetikler. Bileşene yereldir: aynı bileşenin iki kopyasının durumu birbirinden bağımsızdır.7
Bildirimsel kullanıcı arayüzü
31.1'deki elle yazım buyurgan (imperative)dir: "şunu kapat, şunu göster." Belgenin benzetmesi: yanındaki sürücüye "şurada sağa dön" demek. Bildirimsel kullanıcı arayüzü (declarative UI) taksiye binip gideceğin yeri söylemektir.6 Yabancı fikir değil: Bölüm 21'de HTML ve SQL'i "ne istediğini söyle" diye bildirimsel programlama saymıştık. Sen her durum için ekranın nasıl görüneceğini yazarsın, durum değişince React ekranı o tarife uydurur: ekran, özellik ve durumun bir fonksiyonudur.
Belgenin önerdiği yol: önce görsel durumları say. Soru kartı: (1) şık seçilmedi (Gönder kapalı), (2) şık seçildi, (3) gönderildi, doğru, (4) gönderildi, yanlış; cevap sunucuya gidince bir de (5) "gönderiliyor".6 Sonra bunları en az değişkenle temsil et. Neden önemli, sayıyla (Python ile saydım): dört doğru/yanlış bayrak (seçildi, gönderiliyor, doğru, yanlış) 2⁴ = 16 birleşim verir, yalnızca 5'i anlamlıdır; 11 "imkânsız durum" (aynı anda hem doğru hem yanlış gibi) kodda yazılmasa bile bir yerden sızabilir. Belge bu yüzden birbirinden türetilebilen durumu ayrı tutmamayı öğütler.6 Aşağıdaki kodda yalnızca iki durum değişkeni var (secili, dogruMu); "kilitli" gibi gerisi bunlardan hesaplanır.
İlk bileşenimiz
Soru kartı, TypeScript ile (Bölüm 30). puanHesapla, Bölüm 27'deki fonksiyonun tipli halidir: (dogruMu: boolean, seri: number) => number.
import { useState } from "react";
import { puanHesapla } from "./puan";
type Soru = { id: number; metin: string; sikler: string[]; dogru: number };
type SoruKartiProps = {
soru: Soru;
seri: number;
onCevap: (kazanilan: number) => void;
};
export function SoruKarti({ soru, seri, onCevap }: SoruKartiProps) {
const [secili, setSecili] = useState<number | null>(null);
const [dogruMu, setDogruMu] = useState<boolean | null>(null);
const kilitli = dogruMu !== null;
function gonder() {
if (secili === null) return;
const sonuc = secili === soru.dogru;
setDogruMu(sonuc);
onCevap(puanHesapla(sonuc, seri));
}
return (
<section>
<h2>{soru.metin}</h2>
<ul>
{soru.sikler.map((sik, i) => (
<li key={sik}>
<button disabled={kilitli} onClick={() => setSecili(i)}>
{sik}{secili === i && " ✓"}
</button>
</li>
))}
</ul>
<button onClick={gonder} disabled={secili === null || kilitli}>Gönder</button>
{dogruMu === true && <p>Doğru!</p>}
{dogruMu === false && <p>Yanlış.</p>}
</section>
);
}
Satır satır: 1–2: kancayı ve puan fonksiyonunu modül olarak alıyoruz. 4: Soru tip takma adı, verinin şekli. 6–10: bileşenin "arayüzü" olan üç özellik; sonuncusu fonksiyon. 12: özellikleri parçalayarak alan, büyük harfle başlayan fonksiyon. 13–14: iki durum; useState<number | null>(null) "başlangıçta seçilmedi" demek. 15: kilitli durum değil, durumdan hesaplanan değer. 17–22: gonder olay işleyici; doğru mu diye bakar, durumu yazar, onCevap ile kazanılan puanı üste bildirir. 24–40: döndürülen JSX; && koşullu gösterim, map şıklardan düğme listesi (31.3).
import { useState } from "react";
import { SoruKarti } from "./SoruKarti";
const soru = {
id: 1,
metin: "Sözleşme hangi durumda kurulur?",
sikler: ["Yalnızca yazılı olursa", "İcap ve kabul birleşince", "Noter onaylarsa"],
dogru: 1,
};
export function SoruEkrani() {
const [puan, setPuan] = useState(0);
const [seri] = useState(7);
return (
<main>
<p id="puan">Puan: {puan}</p>
<SoruKarti soru={soru} seri={seri} onCevap={(k) => setPuan(puan + k)} />
</main>
);
}
puan burada yaşıyor, kartta değil. Belge: iki bileşenin aynı bilgiye ihtiyacı varsa durumu en yakın ortak üst bileşene taşı, aşağı özellik olarak ver (lifting state up); puanın tek bir sahibi olur.26 İki dosyayı tsc ile strict kipte denetledim (hata yok) ve jsdom'da çalıştırdım: başta Puan: 0; ikinci şık seçilince "✓" çıktı ve Gönder açıldı; Gönder'den sonra Puan: 15 (seri 7, yani 10 + 5 bonus), Doğru! göründü, şıklar kilitlendi. Hiçbir yerde textContent yazmadık.
Kerem'in prototipinde muhtemelen soru kartını gösteren, şık rengini değiştiren ve puanı güncelleyen JavaScript var (içini görmedik). Bu bölümdeki yapı onun karşılığıdır: soru kartı bileşen, puan ve seri üst bileşenin durumu, "Gönder" olay işleyici. PuanRozeti, CanGostergesi, LeaderboardSatiri aynı kalıpla gelir. Yeni bir gösterge, 31.1'deki tabloya yeni satır değil, durumdan beslenen yeni bir bileşendir.
Belgeye göre bileşen "saf" olmalı: aynı girdiyle aynı çıktıyı vermeli ve çizim sırasında önceden var olan nesneleri değiştirmemeli.5 Çünkü React bileşeni gerektiğinde birden çok kez çağırır (31.3). Hukukta aynı olayın aynı normla aynı sonuca bağlanması öngörülebilirlik sağlar; burada da aynı özellik ve durum, aynı ekran. Ağ isteği, zamanlayıcı gibi değişiklikler olay işleyicide ya da etkide yapılır (31.4).5
31.3 JSX; yeniden çizim (render ve commit)
JSX HTML değildir
JSX, JavaScript içine HTML benzeri işaretleme yazmaya yarayan sözdizimi uzantısıdır; React'ten ayrıdır.3 HTML'den farkları: bileşen tek kök döndürür (gerekirse <>...</> boş sarmalayıcıyla), etiketler kapatılır (<br />), class yerine className yazılır.3 Süslü parantez JavaScript'e kaçıştır: {soru.metin}.1 Tarayıcı JSX'i anlamaz; Bölüm 29.3'teki dönüştürücü düz JavaScript'e çevirir. Küçük bir parçayı esbuild ile dönüştürdüm (Vite'ın iç aracı farklı olabilir, iş aynı):
// girdi (JSX)
function Kart({ soru, secili, onSec }) {
return <li className="soru" onClick={() => onSec(soru.id)}>{soru.metin}{secili && <b> ✓</b>}</li>;
}
// çıktı (düz JavaScript; yorum işaretleri çıkarıldı)
import { jsx, jsxs } from "react/jsx-runtime";
function Kart({ soru, secili, onSec }) {
return jsxs("li", { className: "soru", onClick: () => onSec(soru.id), children: [
soru.metin,
secili && jsx("b", { children: " ✓" })
] });
}
Her etiket bir fonksiyon çağrısı oldu; özellikler bir nesneye, içerik children özelliğine girdi. Eski adı createElement olan fonksiyonu çalıştırınca type: "h1", props: { className: "baslik", children: "Merhaba" } ve key: null alanlı bir nesne döndü.24 Yani bileşenin döndürdüğü şey ne HTML ne DOM düğümü: ekranın tarifini taşıyan düz bir nesne. DOM'a çevirmek React'in sonraki işi.
Koşul için özel sözdizimi yoktur; if, ? :, && gibi sıradan JavaScript kullanılır.2 Listede dizinin map'i kullanılır ve her öğeye key verilir: kardeşler arasında benzersiz, değişmeyen ve çizim sırasında üretilmeyen (Math.random() yanlış) bir değer; dizi sırasını anahtar yapmak, öğe eklenip silinince ince hatalara yol açar.29 Masaüstündeki dosyalar adsız olsa "ikinci dosya" derdin; biri silinince "ikinci" başka dosya olurdu. Anahtar öğenin adıdır (yukarıdaki kodda şık metnini kullandık; aynı soruda iki şık aynı metni taşımaz varsayımıyla).
Yeniden çizim: tetikle, çiz, işle
Ekranda görünmek için React üç adım atar: tetikleme, çizme (render) ve DOM'a işleme (commit).8 Tetikleme ya ilk çizimdir ya da bir bileşenin (veya atasının) durumunun güncellenmesi. Çizme, belgenin deyişiyle "React'in bileşenlerini çağırması"dır ve çağrılan bileşen başka bileşen döndürüyorsa onları da çağırarak sürer. İşlemede React, DOM'u yeni çıktıyla eşleştirmek için gereken asgari işlemleri uygular; fark yoksa düğüme dokunmaz.8 (Buradaki "commit", Git'inkiyle ilgisiz; yalnızca ad benzerliği.) Sayıyla gördüm: Ust bir Rozet ve üç Kart çiziyor; +10 düğmesi Ust'un durumunu değiştiriyor.
| Olay | Çağrılan bileşen fonksiyonları | DOM'da değişiklik |
|---|---|---|
| İlk çizim | Ust 1, Rozet 1, Kart 3 | tüm düğümler yaratıldı |
+10 tıklandı | Ust 1, Rozet 1, Kart 3 | 1 (tek metin düğümü: "10 puan") |
Beş fonksiyon yeniden çağrıldı (çizme), ekranda yalnızca bir metin düğümü değişti (işleme). Kartlar değişmediği halde çağrıldı: belgeye göre varsayılan, güncellenen bileşenin içindeki bütün bileşenleri çizmektir; sorun çıkarsa seçenekler var ama "erken eniyileme yapma".8 Demek ki yeniden çizim ucuz hesap, pahalı olan DOM işlemidir ve React onu azaltır. Geliştirmede Strict Mode her bileşeni bir kez daha çağırır; saf olmayan bileşeni yakalamak içindir.30
Durum bir anlık görüntüdür
Durum 0 iken bir düğme setSayi(sayi + 1)'i üç kez çağırıyor. Ekranda ne görünür? Çalıştırdım: 1. Her çizim kendi durum değerlerinin sabit bir anlık görüntüsüyle çalışır: o çizimin olay işleyicisinde sayi hep 0'dır, üç çağrı üç kez "1 yap" demektir.9 React ayrıca işleyicideki bütün çağrılar bitene kadar bekler, tek çizim yapar.10 Öncekine bağlı art arda güncelleme için değer yerine fonksiyon verilir: setSayi(x => x + 1) (güncelleyici fonksiyon). Aynı düğme böyle yazılınca ekranda 3 göründü.10
Bölüm 10'da dizi değişkeninin dizinin kendisini değil referansını tuttuğunu görmüştük. React yeni durumu eskisiyle Object.is ile karşılaştırır; aynıysa yeniden çizmez.12 Belge bu yüzden durumu salt okunur saymanı, her seferinde yeni nesne ya da dizi vermeni ister.11 Denedim: durumda ['a']; bir düğme l.push('x'); setL(l) yapıyor. Sonuç: yeniden çizim 0, ekranda uzunluk hâlâ 1 (bellekteki dizi artık 2 elemanlı). Sonra setL([...l, 'x']) yapan düğmeye basınca ekranda 3 göründü: gizli ekleme de ortaya çıktı. Yerinde değiştirmek ekranı güncellemedi ve ekranı durumdan sessizce ayırdı. Kural: push yerine [...l, 'x'], nesnede { ...eski, alan: yeni }.
31.4 Kancalar: useState ve useEffect
Kanca ve kuralı
useState ve adı use ile başlayan öteki fonksiyonlara kanca (hook) denir; yalnızca React bileşeni çizerken çalışırlar ve React'in özelliklerine "takılmanı" sağlarlar.7 Kural: bileşenin (ya da kendi kancanın) en üst düzeyinde çağrılırlar; koşul, döngü, iç içe fonksiyon ya da try/catch içinde çağrılmazlar.17 Neden? useState(0) çağrısında hangi değişkene ait olduğunu söyleyen bir ad geçmez. Belgeye göre kancalar her çizimde sabit bir çağrı sırasına dayanır: React her bileşen için bir durum çiftleri dizisi ve bir sıra sayacı tutar, her çağrıda sıradaki çifti verir.7 Bunu bir oyuncak modelde denedim (benim basitleştirmem): iki useState (puan 0, seri 3); setPuan(10) ve setSeri(4) sonrası çağrılar sırayla 0 3, 10 3, 10 4 verdi. Birinci çağrı hep birinci kutuyu, ikincisi ikinciyi aldı. Bir useState koşula girse sıra kayar ve puan kutusundan seri okunurdu.
Olay işleyici ve etki
Bileşenin içinde üç tür kod var. Çizme kodu (en üst düzey) saf olmalı: özellik ve durumdan JSX hesaplar. Olay işleyici (event handler), belirli bir kullanıcı eylemi (tıklama, yazma) yüzünden çalışan iç içe fonksiyondur; yan etkiler burada serbesttir.13 Üçüncüsü etki (Effect): çizmenin kendisinin doğurduğu yan etki; bir işlemenin sonunda, ekran güncellendikten sonra çalışır.13 (Yan etki (side effect): programın dışındaki dünyayı değiştiren her şey; ağ isteği, zamanlayıcı, başlık.) Ayırma sorusu: bu kod neden çalışıyor? "Gönder'e basıldığı için" ise olay işleyici; "ekranda bu bileşen göründüğü için" ise etki.14 Cevabı sunucuya yollamak gonder'in işi (olay); soru ekranı görünürken geri sayım yürütmek etkidir.
Etki: bildir, bağımlılıkları yaz, temizle
Belge üç adım sayar: etkiyi bildir, bağımlılıklarını belirt, gerekirse temizlik ekle.13 Örnek: 30 saniyelik geri sayım (özellik varsayımsal; yalnızca etkinin biçimini gösteriyor).
import { useEffect, useState } from "react";
export function GeriSayim() {
const [kalan, setKalan] = useState(30);
useEffect(() => {
const id = setInterval(() => {
setKalan((k) => k - 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <p id="k">Kalan süre: {kalan}</p>;
}
4: kalan durumu 30'la başlar. 6: useEffect etkiyi bildirir. 7–9: saniyede bir kalan'ı azaltan setInterval; güncelleyici biçim (k => k - 1) kullanıldı, çünkü etki bir kez kurulur ve eski anlık görüntüye takılmamalı. 10: etkinin döndürdüğü temizlik fonksiyonu (cleanup); bileşen ekrandan kalkarken ya da etki yeniden kurulmadan önce çağrılır, zamanlayıcı durur. 11: [] bağımlılık dizisi: "ekrana gelince bir kez kur".
Bağımlılık dizisi üç biçimde yazılır: hiç yoksa etki her çizimden sonra çalışır; [] ise yalnızca bileşen ekrana gelince (mount); [a, b] ise ekrana gelince ve a ya da b bir öncekinden Object.is ile farklıysa çalışır.13 Bağımlılıkları sen "seçmezsin": etkinin içinde okuduğun her özellik ve durumu yazarsın, eksikse denetleyici (Bölüm 29.4) uyarır.13 (Bu "bağımlılık", Bölüm 27'deki modül bağımlılığı değil, etkinin okuduğu değerlerdir.) Başlığı puana bağlayan ikinci bir etki yazdım: useEffect(() => { document.title = `Puan: ${puan}`; }, [puan]). Sahte saatle çalıştırdım: ekrana gelince Kalan süre: 30, başlık Puan: 0, kurulan zamanlayıcı 1, temizlenen 0. Saat 3 sn ilerleyince Kalan süre: 27. Puan 15 olunca başlık Puan: 15 oldu, ama süre 27'de kaldı ve zamanlayıcı yeniden kurulmadı (bağımlılıklar ayrı). Soruyu değiştirmek için GeriSayim'e yeni key verince (anahtar listelerin dışında da kimliktir32) eski bileşen kalktı: kurulan 2, temizlenen 1; süre yine 30. Kapatınca temizlenen 2. Her kurulumun bir temizliği var.
Temizlik ve Strict Mode
Temizlik, etkinin yaptığını durdurur ya da geri alır: bağlanmanın karşılığı bağlantıyı kesmek, abonenin abonelikten çıkmak, veri çekmenin iptal ya da sonucu yok saymak.13 Geliştirmede Strict Mode her etkiyi bir kez daha çalıştırır: kur, temizle, tekrar kur;30 ölçüt, kullanıcının "bir kez çalışma" ile bu sıra arasındaki farkı ayırt edememesidir.13 Denedim: soruId bağımlılıklı bir etki "kur" ve "temizle" yazıyor. Strict Mode kapalıyken sıra kur 1 | temizle 1 | kur 2 | temizle 2 (ilk soru, değişim, kapanış); açıkken kur 1 | temizle 1 | kur 1 | temizle 1 | kur 2 | temizle 2: baştaki fazladan "temizle, kur" çifti geliştirmeye özgü sınavdır. Etkinin iki kez çalışmasını useRef ile engellemek ise belgeye göre tuzaktır; hatayı çözmez, gizler.13 Etkiyi bileşenin "gel, güncelle, kalk" yaşam döngüsüyle değil ayrı bir süreç olarak düşün: yalnızca iki şey yapar, eşitlemeye başlar ve durdurur; bağımlılık değişince "temizle, yeni değerle kur" olur.16
Çoğu zaman etki gerekmez
Etki React'in kaçış kapısıdır: yalnızca bileşeni dış bir sisteme (ağ, tarayıcı API'si) eşitlemek için.15 Bir durumu başka durumdan hesaplamak için etki yazma. Cevaplanan 3, toplam 8 soru ise ilerleme 3 / 8 × 100 = 37,5 yüzdedir; ayrı durum ve etki yerine çizim sırasında hesapla:
const yuzde = (cevaplanan / toplam) * 100;
Belge aynı ilkeyi (ad ve soyadı birleştirmek) şöyle söyler: hesaplanabiliyorsa durum yapma; kod daha hızlı, kısa ve hataya kapalı olur.15 İleri bir araç da var: useEffectEvent, etkinin içinde "en son değeri oku ama bağımlılık yapma" demenin yoludur; çünkü etkiler tepki verir, olay işleyiciler vermez.1431 Kurduğum React 19.3.1 bunu dışa aktarıyor; ayrıntı resmî sayfada.
Leaderboard'u sunucudan çekmek de ilk bakışta bir etki gibi durur: ekran görününce istek at. Belge bunu yaygın bir yol olarak anlatır ve bedellerini sayar: istekler arası yarış, önbelleğin olmaması, ardışık bekleyiş ("şelale").13 Bölüm 33'te bu yüzden veri çekmeyi TanStack Query'ye bırakacağız. Bu bölümde öğrendiğin etki; zamanlayıcı, başlık, abonelik gibi dış dünyaya eşitleme işleri içindir.
31.5 Yönlendirme (routing) ve sayfalar
Yönlendirme (routing), belgenin tanımıyla, kullanıcı belirli bir URL'yi ziyaret ettiğinde hangi içeriğin ya da sayfanın gösterileceğine karar vermektir; URL'leri uygulamanın parçalarına eşlemek, iç içe rotaları, rota ve sorgu parametrelerini yönetmek gerekir.19 URL'yi Bölüm 20'den, tek sayfa uygulamayı Bölüm 22.1'den hatırla. Sayfa (page) burada bir rotaya (URL kalıbına) bağlı, ekranı kaplayan bileşendir; Bölüm 15'teki bellek sayfasıyla ilgisizdir.
Yönlendirme React'in çekirdeğinde yok
Belge açık: Vite gibi derleme araçlarının verdiği başlangıç istemci tarafında çalışan bir tek sayfa uygulamasıdır ve yönlendirme, veri çekme, stil için başka çözüm içermez; yönlendirici olarak React Router ve TanStack Router önerilir, yönlendiriciler kodda ya da bileşen klasörü ve dosya yapısından tanımlanabilir.19 Yönlendirme, 31.1'deki çerçeve kararlarından biridir (Bölüm 9'daki derlenmiş kütüphane ile karıştırma). Durumla da yazabilirsin:
const [sayfa, setSayfa] = useState("ana");
return sayfa === "ana" ? <AnaSayfa onGit={setSayfa} /> : <Leaderboard />;
Çalışır, ama adres çubuğu hiç değişmez: Geri düğmesi uygulamadan çıkarır, yenileyince ana sayfaya dönersin, "şu bölüme bak" bağlantısı paylaşamazsın. Tarayıcıda bunu çözen History API'dir: oturum geçmişi yığınının içeriğini değiştirmene izin verir ve history.back() tam olarak Geri düğmesine basmış gibi davranır.23 Yönlendirici kütüphaneleri bu işi yönetip URL ile bileşeni eşler.
Köprü: Expo Router
Bölüm 32'de kullanacağımız Expo Router, dosya tabanlı bir yönlendiricidir: src/app klasöründeki dosyalar rotaları tanımlar, her sayfanın bir URL yolu vardır ve her ekran otomatik olarak derin bağlantıyla (deep link) paylaşılabilir.2122 [userName].tsx parametreli rota, index.tsx klasörün varsayılanı, _layout.tsx iskelet bileşenidir.22 Expo belgesi şöyle der: React Native tarihsel olarak uygulamaların nasıl kurulacağı konusunda kural koymamıştır; Expo Router ise React Native için "görüşlü" (opinionated) bir çerçevedir, web'de Remix ve Next.js'in rolüne benzer.21 Bizim uygulamaya uyarlanmış varsayımsal bir ağaç (gerçeği Bölüm 32.3'te, Emre ile belirleriz):
src/app/
├── _layout.tsx ortak iskelet (üst bar, sekmeler)
├── index.tsx → /
├── leaderboard.tsx → /leaderboard
└── bolum/
└── [bolumId].tsx → /bolum/3 gibi
"Hangi sayfa" sorusunun cevabı kodun içinde değil, dosya sisteminde (Bölüm 16). TanStack Router da aynı işi çözer; ona Bölüm 33.3'te kısaca bakacağız.
Plan Expo + TypeScript olduğuna göre, bugün "sayfa" gibi gördüğün her ekran ileride src/app altında bir dosya olacak; React'in kendisinde "sayfa" yok. Prototipte ayrı HTML dosyaları ya da gizle/göster yöntemi kullanıldıysa (görmedik), "durumla yönlendirme" ile "URL ile yönlendirme" farkı tam bu noktada işe yarar.
Bölüm 26: DOM'a elle yazmak yerine durumdan çizmek. 27: her bileşen bir modül; özellikler dar arayüz, durum gizli ayrıntı. 29: JSX'i dönüştürücü çevirir. 30: özelliklerin tipi bileşenin sözleşmesidir. 21 ve 10: fonksiyonu değer gibi taşımak (olay işleyici), referans karşılaştırması (durum güncellemesi). İleriye: Bölüm 32 aynı bileşen fikrini telefonda yerel görünümlere çevirir; Bölüm 33 sunucu verisini yönetir.
Bu bölümde neler kuruldu
Kütüphane senin çağırdığındır, çerçeve senin kodunu çağıran (kontrolün tersine çevrilmesi); React sınırdadır: bileşenleri o çağırır, yönlendirme gibi kararları çerçevelere bırakır. Ekran bileşenlerden kurulur; bileşen özellikleri (dışarıdan, değiştirilemez) ve durumu (hafıza) alıp ekranın tarifini döndüren saf bir fonksiyondur: bu bildirimsel yazımdır. JSX o tarifin yazımıdır. Durum değişince React tetikler, çizer (bileşenleri çağırır), DOM'a işler (yalnızca farkı uygular); durum anlık görüntüdür, yerinde değiştirilmez. Kancalar çağrı sırasına dayanır; useState hafıza, useEffect çizmenin doğurduğu yan etkidir ve bağımlılık dizisi ile temizlik ister. Yönlendirme URL'yi sayfaya eşler ve React çekirdeğinde değildir.
Terimler sözlükte, Bölüm 31 başlığı altında.
react.dev, Quick Start, sonra Thinking in React (bir tasarımı bileşen ve duruma bölmenin beş adımı). Derinleşmek için State as a Snapshot ve You Might Not Need an Effect.
Takıldığın her yeri yapay zekâ öğretmenine sor. Öneri: SoruKarti'na "kalan can" özelliği eklemesini iste, sonra eklediği her satırı "çizme / olay işleyici / etki" diye etiketletip gerekçe sor.
Bölüm sonu soruları
Kitaba bakmadan cevapla. Kâğıt kalem serbest.
Örnek cevabı göster
Ekranda ne göründüğü özellikler ve durumdan hesaplanır; aynı özellik ve durumla aynı ekran çıkar. Ekranı elle güncellemem, durumu güncellerim (31.2). Durum değişince React önce bir çizim tetikler; sonra bileşen fonksiyonlarını çağırır (çizme) ve ekranın yeni tarifini nesne olarak alır; en sonda DOM'u bu tarife eşitlemek için gereken asgari işlemleri uygular (işleme): denememizde 5 fonksiyon çağrıldı ama yalnızca bir metin düğümü değişti (31.3).
Örnek cevabı göster
(a) olay işleyici: kod, belirli bir kullanıcı eylemi (Gönder'e basmak) yüzünden çalışıyor. (b) etki: bunun sebebi tek bir tıklama değil, bileşenin ekranda olması; çizmenin kendisinin doğurduğu yan etki. Temizlik yazılmazsa bileşen ekrandan kalkınca ya da soru değişince eski zamanlayıcı çalışmaya devam eder; denememizde her kurulumun bir temizliği vardı (kurulan 2, temizlenen 2). Temizliksiz zamanlayıcılar birikir (31.4).
Kaynaklar
- react.dev, Quick Start: bileşen, JSX, süslü parantez, koşul ve liste, olay, durum, durumu paylaşma özeti.
- react.dev, Conditional Rendering: koşullar için sıradan JavaScript (
if,&&,? :). - react.dev, Writing Markup with JSX: JSX bir sözdizimi uzantısıdır, React bir kütüphanedir; JSX kuralları.
- react.dev, Passing Props to a Component: özellikler, değiştirilemezlik.
- react.dev, Keeping Components Pure: saflık, çizim sırasında dış nesneleri değiştirmemek, yan etkilerin yeri.
- react.dev, Reacting to Input with State: buyurgan ve bildirimsel kullanıcı arayüzü, görsel durumları sıralamak.
- react.dev, State: A Component's Memory: yerel değişken yetmez,
useState, kancalar ve çağrı sırası. - react.dev, Render and Commit: tetikleme, çizme, DOM'a işleme; Strict Mode'da çift çağrı.
- react.dev, State as a Snapshot: çizimin anlık görüntüsü, olay işleyicideki durum değeri.
- react.dev, Queueing a Series of State Updates: toplu güncelleme, güncelleyici fonksiyon.
- react.dev, Updating Objects in State: durumu salt okunur saymak, yeni nesne vermek.
- react.dev, useState (başvuru):
Object.isile karşılaştırma, aynı değerde yeniden çizmeme. - react.dev, Synchronizing with Effects: etki, bağımlılık dizisi üç biçimi, temizlik, Strict Mode,
useReftuzağı, veri çekme. - react.dev, Separating Events from Effects: olay işleyici ile etki ayrımı, reaktif değerler.
- react.dev, You Might Not Need an Effect: çizim sırasında hesaplama.
- react.dev, Lifecycle of Reactive Effects: etkinin yaşam döngüsü bileşeninkinden ayrıdır.
- react.dev, Rules of Hooks: kancalar yalnızca en üst düzeyde çağrılır.
- react.dev, Creating a React App: yeni proje için çerçeveyle başlamak; Next.js, React Router, Expo.
- react.dev, Build a React app from Scratch: yönlendirme tanımı, derleme araçlarının yönlendirme içermemesi, "kendi çerçeveni yazmak".
- Martin Fowler, Inversion Of Control (2005): kütüphane ve çerçeve farkı, Hollywood ilkesi.
- Expo, Introduction to Expo Router: dosya tabanlı yönlendirici; React Native için "görüşlü" çerçeve; her ekran derin bağlantılı.
- Expo, Expo Router notation ve Core concepts:
index,[param],_layout; her sayfanın URL'si olması. - MDN, History API: oturum geçmişi yığını,
back(),pushState(). - react.dev, createElement (başvuru):
type,props,keyalanlı eleman nesnesi. - react.dev, Understanding Your UI as a Tree: bileşen ağacı.
- react.dev, Sharing State Between Components: durumu yukarı taşımak, tek doğruluk kaynağı.
- react.dev, Responding to Events: olay işleyici,
onClicközelliği olarak fonksiyon geçirmek. - react.dev, Thinking in React: tek yönlü veri akışı, ters veri akışı.
- react.dev, Rendering Lists:
mapvekeykuralları. - react.dev, StrictMode (başvuru): geliştirmede ek çizim ve ek etki çalıştırma.
- react.dev, useEffectEvent (başvuru). Ayrıca bu bölümdeki çıktıların hepsi, React 19.3.1, react-dom 19.3.1, Node 22 ve jsdom ile yazarın kendi çalıştırdığı kodlardandır.
- react.dev, Preserving and Resetting State:
keyile durumu sıfırlamak.