---
title: 'React Native'
source: 'https://academia.sh/tr/kurslar/react-native'
language: tr
updated: '2026-10-21T05:02:53+00:00'
license: 'CC BY-SA 4.0'
lessons: 20
outcomes:
    - 'Çekirdek bileşenler ve biçem sistemiyle arayüz kurar'
    - 'Liste başarımını ölçerek iyileştirir'
    - 'Yerel modüllerle platform yeteneklerine erişir'
---

# React Native

React Native, ortak JavaScript kodunun bildirimsel bir görünüm ağacı tarif ettiği ve her güncellemede bu ağacın yerel görünümlere komut olarak yansıdığı köprü tabanlı bir çerçevedir. Kurs her konuyu tek bir soruyla ölçer — bu güncelleme görünüm ağacındaki düğüme ne yapıyor? Bir düğüm korunur, güncellenir, kurulur ya da sökülür; çekirdek bileşenler, biçem sistemi, gezinme, durum, veri ve depolama katmanları, liste başarımı, profilleme, test, yerel modüller ve yayın aynı çerçeveden geçirilir.

## Temel Yapı

React Native'in çalışma modeli, ortam kurulumu, çekirdek bileşenler, biçem sistemi ve esnek kutu düzeni, dokunma ve etkileşim, platforma özgü kod.

### React Native Nedir

Source: https://academia.sh/tr/kurslar/react-native/react-native-nedir

Köprü tabanlı bir çerçevede ortak kod ekranı çizmez, görünüm ağacını tarif eder. Ders bir güncellemenin ağacın her düğümüne ne yaptığını dört akıbetle sınıflar: korunan, güncellenen, kurulan, sökülen.

Önceki kurs, çapraz platform geliştirmenin dört yolunu sınıfladı ve köprü tabanlı yaklaşımı tanıttı: ortak kod bir arayüz düğümünü platformun kendi görünümüne eşler ve köprü geçişiyle yerel tarafa ulaşır. Bu kurs o yaklaşımı bildirimsel bir bileşen modeliyle çalışan tek bir çerçevede açar. Çerçevenin adı yalnızca bu dersin başlığında ve birkaç yerinde geçer; ders çerçeveyi değil, onun bir sınıf kavramını anlatır.

Ortak kod ekranı doğrudan çizmez. Her güncellemede ekranın nasıl görünmesi gerektiğini bir **görünüm ağacı** olarak tarif eder. Çerçeve yeni ağacı öncekiyle karşılaştırır ve yerel tarafa yalnız değişen düğümler için komut gönderir. Kursun sorusu buradan doğar: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?**

Yanıtın dört biçimi var ve adları kurs boyunca değişmez. Düğüm iki ağaçta da aynı yerde, aynı tip ve aynı anahtarla duruyor, özellikleri de eşitse güncelleme düğümü **korunan** yapar ve yerel tarafa komut gitmez. Düğüm aynı yerde ve aynı tiple duruyor ama en az bir özelliği değişmişse düğüm **güncellenen**dir: yerinde kalır, yerel görünümü güncellenir. Düğümün önceki ağaçta eşi yoksa **kurulan**dır ve yerel görünümü sıfırdan oluşturulur. Düğümün sonraki ağaçta eşi yoksa **sökülen**dir ve yerel görünümüyle birlikte içindeki durum da yok olur. Dört yanıtın üst adı **akıbet**tir. Çıktıda ASCII yazılır: `korunan`, `guncellenen`, `kurulan`, `sokulen`.

#### Ekranı Tarif Etmek

Bildirimsel bir arayüzde kod "şu görünümü oluştur, sonra şunu güncelle" demez; durum verilince ekranın o durumdaki hâlini döndürür. Aşağıdaki parça bu işlevlerin biçimini gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```jsx
// Platform kodu: Orders
function Orders({ orders, loading }) {
  return (
    <View>
      <Text>{orders.length} siparis</Text>
      {loading ? <ActivityIndicator /> : null}
    </View>
  );
}
```

Aynı tarif iki platformda çalışır; her platform tarifteki düğümleri kendi yerel görünümlerine dönüştürür. iOS'ta ve Android'de ekranda görünen şey platformun kendi kontrolüdür, ortak kodun çizdiği bir resim değildir. Ortak kodun elinde yalnız tarif vardır.

İşlevin döndürdüğü şey bir görünüm değil, bir tariftir: bir kapsayıcı, içinde bir metin ve duruma göre bir yükleniyor göstergesi. Çerçeve bu tarifi alır, bir önceki tarifle yan yana koyar ve farkı yerel tarafa komut olarak iletir. Komut, köprüden geçen iş birimidir: "şu görünümü kur", "şu görünümün şu özelliğini değiştir", "şu görünümü sök". Ağacı yeniden üretmek bir çizim (render) işidir ama tek başına komut değildir. Yeni ağaç öncekine eşit çıkarsa köprüden hiçbir şey geçmez.

Bir kayıt tutalım. Buradaki **özellik** sözcüğü çerçevenin prop dediği şeydir: bir düğümün dışarıdan aldığı değer, örneğin metin düğümünün yazısı. Kurs boyunca "özellik" bu anlamda kalır; ürünün bir yeteneği anlatılacaksa "uygulama özelliği" denir.

#### Çekirdek

Akıbeti etiket değil kural verir. Aşağıdaki çekirdekte `el` bir düğüm kurar: tip, özellikler, çocuklar ve isteğe bağlı bir anahtar. `reconcile` iki ağacı karşılaştırır ve her düğüm için bir `{ fate, path }` kaydı üretir. `printOps` kayıtları basar, `countFates` dört akıbeti sabit sırayla sayar ve son satırda komut toplamını verir. Kural sırayla sorar: önceki düğüm yoksa düğüm ve alt ağacı kurulandır; sonraki düğüm yoksa sökülendir; tip ya da anahtar farklıysa eski alt ağaç sökülür, yenisi kurulur ve özelliklere bakılmaz; tip ve anahtar aynıysa özellikler içerikle karşılaştırılır, eşitse korunan, değilse güncellenen olur. Ardından çocuklar eşlenir: anahtarı olan çocuk anahtarıyla, anahtarı olmayan çocuk sırasıyla.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

#### Bir Güncellemenin Sekiz Düğümü

İlk ölçüm bir sipariş ekranının tek güncellemesidir. Dördüncü sipariş gelir ve yükleniyor göstergesi kalkar. Ağaç bu ölçümde doğrudan `el` ile kurulur.

```javascript
// main.mjs
import { el, reconcile, printOps, countFates } from "./fate.mjs";

const before = el("Screen", {}, [
  el("Header", { title: "Siparisler" }),
  el("Text", { value: "3 siparis" }),
  el("List", {}, [
    el("Row", { name: "Kahve" }, [], "a"),
    el("Row", { name: "Cay" }, [], "b"),
  ]),
  el("Spinner", { visible: true }),
]);

const after = el("Screen", {}, [
  el("Header", { title: "Siparisler" }),
  el("Text", { value: "4 siparis" }),
  el("List", {}, [
    el("Row", { name: "Kahve" }, [], "a"),
    el("Row", { name: "Cay" }, [], "b"),
    el("Row", { name: "Su" }, [], "c"),
  ]),
]);

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Header
guncellenen: /Screen/Text
korunan: /Screen/List
korunan: /Screen/List/Row[a]
korunan: /Screen/List/Row[b]
kurulan: /Screen/List/Row[c]
sokulen: /Screen/Spinner
korunan: 5
guncellenen: 1
kurulan: 1
sokulen: 1
komut: 3
```

Sekiz düğümün beşi korunan, biri güncellenen, biri kurulan, biri sökülendir; komut üçtür. Ekran, başlık ve liste aynı tip, aynı yer ve eşit özelliklerle durur. Sayaç metninin `value` özelliği `"3 siparis"`ten `"4 siparis"`e döner, bu yüzden `/Screen/Text` güncellenen olur. Anahtarı `a` ve `b` olan iki satır anahtarlarıyla eşleşir ve değişmez. Anahtarı `c` olan satırın önceki ağaçta eşi yoktur, bu yüzden kurulandır. Dördüncü sıradaki `Spinner`'ın sonraki ağaçta eşi yoktur, bu yüzden sökülendir. Komut güncellenen, kurulan ve sökülenin toplamıdır; sekiz düğümden beşi yerel tarafa hiç dokunmaz.

Çıktıdaki yol, kökten düğüme tip adlarıyla kurulur: `/Screen/List/Row[c]` ekranın içindeki listenin içindeki, anahtarı `c` olan satırdır. Tip adları (`Screen`, `Text`, `Row`) modelin etiketleridir; çerçevenin kendi bileşen adları değildir. Aynı tipten birden çok kardeş olduğunda yolu ayırt eden şey anahtardır.

Anahtarın işi burada görünür. Satırlar anahtarlarıyla eşleştiği için `c` satırının eklenmesi `a` ve `b` satırlarını etkilemedi. Anahtarsız olsaydılar sırayla eşlenirlerdi; o durum, performans ve üretim konusunun liste başarımı dersinde ayrıca ölçülecek.

#### Yeniden Çizim Komut Değildir

Her ders ağacı elle kurmaz. Gerçek çerçevede ağacı kod durumdan üretir, bu yüzden bundan sonraki ölçümler durumdan ağaç üreten bir işlev kullanır. İşlev aşağıdaki yardımcı dosyada durur; bir şey basmaz.

```javascript
// view.mjs — siparis durumundan gorunum agaci uretir
import { el } from "./fate.mjs";

export function renderOrders(state) {
  const rows = state.orders.map((order) => el("Row", { name: order.name }, [], order.id));
  const children = [
    el("Header", { title: "Siparisler" }),
    el("Text", { value: `${state.orders.length} siparis` }),
    el("List", {}, rows),
  ];
  if (state.loading) children.push(el("Spinner", { visible: true }));
  return el("Screen", {}, children);
}
```

Şimdi aynı durumdan ağacı iki kez üretelim. Durum değişmedi; iki ağaç iki ayrı nesnedir ama içerikleri aynıdır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderOrders } from "./view.mjs";

const state = {
  orders: [
    { id: "a", name: "Kahve" },
    { id: "b", name: "Cay" },
  ],
  loading: false,
};

const before = renderOrders(state);
const after = renderOrders(state);
countFates(reconcile(before, after));
```

```text
korunan: 6
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Ağaç yeniden üretildi, altı düğümün altısı korunan çıktı ve komut sıfır oldu. Yeniden çizim bir iştir, akıbet değildir. Bir ekranın sık yeniden çizilmesi yerel tarafa sık komut gittiği anlamına gelmez; bunu söyleyen şey çizimin sayısı değil, ağaçların farkıdır. Kursun sonraki derslerinde, yeniden çizimin sayısı ile komutun sayısı birbirinden ayrı sayılacak.

**TY1.** Model, çerçevenin karşılaştırma adımını yalnız düğüm ağacı olarak kurar. Gerçek çerçevede bir düğümün özellikleri ayrıca işlev değerleri, biçem nesneleri ve yerel görünüme özgü alanlar taşıyabilir; model bunları metin ve sayı gibi `JSON.stringify` ile karşılaştırılabilen değerlere indirir. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Yerel tarafta bir komutun kaç görünüm oluşturduğu, ne kadar sürdüğü ve kaç kare tuttuğu modelde yoktur.

#### Durum Bir Düğümü Söker ya da Kurar

Aynı işlev bir düğümün ağaçtan çıkmasını da, girmesini de tarif eder. Yükleme bitince yükleniyor göstergesi tariften düşer; yükleme yeniden başlayınca geri döner. Aşağıdaki ölçüm iki yönü art arda koşar ve her seferinde yalnız komut doğuran kayıtları basar.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderOrders } from "./view.mjs";

const orders = [
  { id: "a", name: "Kahve" },
  { id: "b", name: "Cay" },
];

const loadingTree = renderOrders({ orders, loading: true });
const doneTree = renderOrders({ orders, loading: false });

console.log("yukleme bitti");
const finished = reconcile(loadingTree, doneTree);
printOps(finished.filter((op) => op.fate !== "korunan"));
countFates(finished);

console.log("yukleme basladi");
const started = reconcile(doneTree, loadingTree);
printOps(started.filter((op) => op.fate !== "korunan"));
countFates(started);
```

```text
yukleme bitti
sokulen: /Screen/Spinner
korunan: 6
guncellenen: 0
kurulan: 0
sokulen: 1
komut: 1
yukleme basladi
kurulan: /Screen/Spinner
korunan: 6
guncellenen: 0
kurulan: 1
sokulen: 0
komut: 1
```

Gösterge ağaçtan çıkınca tek düğüm sökülen oldu; geri dönünce aynı düğüm kurulan oldu. İki yönde de komut birdir ve altı düğüm korunan kaldı. Akıbet güncellemenin yönüne bağlıdır: aynı düğüm bir yönde sökülen, öbür yönde kurulandır. Kuruluş sıfırdan olur. Sökülen düğümün durumu yok olduğu için geri dönen gösterge öncekinin devamı değil, yenisidir.

Burada bir ayrım var. Göstergeyi ağaçtan çıkarmak ile `visible` özelliğini `false` yapmak aynı şey değildir. İkincisinde düğüm ağaçta kalır, tek özelliği değişir ve düğüm güncellenen olur; yerel görünümü yaşar. Hangisinin doğru olduğu ekrana bağlıdır, ama komutun türü ve düğümün durumunun akıbeti bu seçimden doğar.

#### Sınırlayıcı Ölçüm

Kural ağacın yapısını görür, yerel tarafın işini görmez. Kural yalnız ağaçta görüneni görür: özellik olarak ağaca girmeyen bir değer değiştiğinde düğüm korunan çıkar. Bu bir çelişki değil, ölçünün kendisidir.

Sipariş sayacını ağaca girmeyen bir yerel değişkende artıralım. Değişken bir işlevin içindeki geçici bir değerdir; durum nesnesine yazılmaz ve `renderOrders` onu okumaz. Sayaç artar, ağaç yeniden üretilir. Sorulan düğüm `/Screen/Text`'tir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderOrders } from "./view.mjs";

const state = {
  orders: [
    { id: "a", name: "Kahve" },
    { id: "b", name: "Cay" },
  ],
  loading: false,
};

let received = 0;
const before = renderOrders(state);
received += 1;
const after = renderOrders(state);

const ops = reconcile(before, after);
printOps(ops.filter((op) => op.path === "/Screen/Text"));
countFates(ops);
console.log(`gelen: ${received}`);
```

```text
korunan: /Screen/Text
korunan: 6
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
gelen: 1
```

Sayaç bir arttı ve `gelen: 1` basıldı, ama `/Screen/Text` korunan çıktı ve komut sıfır kaldı. Kullanıcı ekranda eski sayıyı görür: değişen değer ağaca özellik olarak girmediği için çerçeve ona bakmaz. Çerçeve değişiklikten habersiz olduğu için yerel tarafa komut göndermez; sorun kuralda değil, değerin ağaca hiç yazılmamasındadır. Düzeltme, sayacı durum nesnesine yazmaktır: o zaman `Text` düğümünün `value` özelliği değişir ve düğüm güncellenen olur, ilk ölçümde olduğu gibi.

Bu sınırın ikinci yüzü de vardır. Yerel taraf kendi durumunu kendi tutabilir: bir giriş alanının imleç konumu, bir kaydırma listesinin kaydırma payı. Bu değerler ağaçta değildir ve kural onları görmez. Kuralın baktığı şey yalnızca tarifteki özelliktir.

#### Özet

- Bu dersin akıbeti: korunan. Sınırlayıcı ölçümde `/Screen/Text` düğümünün sayacı ağaca girmeyen bir değişkende arttı; düğüm korunan çıktı ve komut sıfır kaldı.
- Köprü tabanlı çerçevede ortak kod ekranı çizmez, görünüm ağacını tarif eder; çerçeve yeni ağacı öncekiyle karşılaştırır ve yerel tarafa yalnız değişen düğümler için komut gönderir.
- Dört akıbet kurs boyunca aynıdır: korunan, güncellenen, kurulan, sökülen. İlk ölçümde sekiz düğümün beşi korunan, biri güncellenen, biri kurulan, biri sökülen çıktı; komut üç oldu.
- Komut güncellenen, kurulan ve sökülenin toplamıdır. Ağacı yeniden üretmek komut değildir: aynı durumdan iki ağaç altı korunan ve sıfır komut verdi.
- Kural yalnız ağaçta görüneni görür; ağaca girmeyen bir değer değişince düğüm korunan kalır.

#### Sonraki Adım

Bu derste ağaç hazırdı ve yalnız karşılaştırıldı. Sıradaki ders, `Ortam Kurulumu`, ağacın ilk kez nereden geldiğini sorar: uygulama ilk açıldığında önceki ağaç yoktur ve bütün ağaç kurulandır.

### Ortam Kurulumu

Source: https://academia.sh/tr/kurslar/react-native/ortam-kurulumu

Ortam kurulumu, ortak kodun iki platformda ilk kez çalışabilmesi için gereken araç zincirini hazırlar. Ders ilk açılışta önceki ağacın olmadığını ve bütün ağacın kurulan olduğunu ölçer.

Önceki derste ağaç hazırdı: iki ağaç elimizdeydi ve çerçevenin yaptığı iş yalnız aralarındaki farktı. Gerçek bir uygulamada ilk ağaç da bir yerden gelir, ve ondan önce hiçbir ağaç yoktur. Bu ders ortam kurulumunu anlatır: ortak kodun iki platformda ilk kez çalışabilmesi için hazırlanması gereken araçları. Sonra o ilk çalıştırmanın görünüm ağacına ne yaptığını ölçer.

Kurulum bir iştir, akıbet değildir. Bu dersin "kurulan"ı, araçların bilgisayara kurulmasıyla ilgili değildir; ağaçta ilk kez beliren bir düğümün yerel görünümünün sıfırdan oluşturulmasıdır. İki anlam aynı sözcükle anılır ve karışmamaları için kursun akıbeti her zaman bir düğüm hakkında konuşur.

#### Araç Zinciri

Köprü tabanlı bir çerçevede iki ayrı dünya bir arada çalışır. Ortak kod JavaScript ile yazılır ve bir JavaScript motoru içinde koşar. Yerel taraf ise her platformun kendi dilinde ve kendi araç zinciriyle derlenir. Ortam kurulumu bu iki dünyayı birlikte hazırlar. Genel olarak dört parça gerekir.

Birincisi, ortak kodu çalıştıracak JavaScript çalışma ortamı ve bağımlılıkları indirip yöneten bir paket yöneticisidir. İkincisi, ortak kodu tek bir pakete toplayan ve geliştirme sırasında bu paketi sunan paketleyicidir. Üçüncüsü, iOS için ve Android için ayrı ayrı yerel derleme araçlarıdır; bunlar yerel tarafın kodunu derler ve uygulamayı bir çalıştırılabilir pakete dönüştürür. Dördüncüsü, uygulamanın çalışacağı yerdir: emülatör ya da gerçek aygıt.

Bu parçaların hepsini aynı anda öğrenmek gerekmez; iş akışının seçimi, hangisinin ekibe ait olduğunu belirler. Ayrım ekip için bir iş bölümüdür: JavaScript tarafı her iki platformda da aynı kalır, yerel tarafın araçları ise platforma göre ayrılır. Kurulum doğru bittiğinde en küçük uygulama bile iki platformda açılır ve ekranda tek bir metin gösterir. O metin ortak koddan gelir; yani köprü kurulmuş, ortak kod yerel tarafa ilk komutlarını göndermiştir.

#### İki İş Akışı

**Yönetilen iş akışında** yerel proje dizinleri ekibin elinde değildir. Araç zinciri yerel tarafı yönetir; ekip yalnız ortak kodu ve bir yapılandırma dosyasını yazar. Yerel derleme gerektiğinde bir yönetilen derleme servisi bu işi üstlenebilir. Bu iş akışının kazancı, kurulumun hızlı başlamasıdır: bir ekip iki platformun araçlarını kendi bilgisayarında yönetmeden ortak kodu denemeye başlayabilir. Bedeli de aynı yerden gelir: yerel koda dokunmak gerektiğinde, örneğin platformun henüz yönetilen akışa girmemiş bir yeteneği istendiğinde, ekibin elindeki yapı yetmeyebilir.

**Yerel projeli iş akışında** iOS ve Android proje dizinleri ekibin kendi deposunda durur. Ekip yerel kodu doğrudan yazabilir, yerel bağımlılık ekleyebilir ve derleme ayarlarını değiştirebilir. Kazanç denetimdir. Bedel, iki platformun araç zincirinin ekibe düşmesidir: iki ayrı derleme aracı, iki ayrı emülatör, iki ayrı imzalama düzeni.

Seçim çoğunlukla bir sorunun yanıtıdır: ekibin yerel koda dokunması beklenir mi? Uygulama yalnız hazır görünümleri ve ortak kodu kullanacaksa yönetilen akış işi sade tutar. Uygulama platformun henüz hazır olmayan bir yeteneğine bağlıysa ya da yerel bir modül yazılacaksa yerel projeli akış gerekir. Bu karar ileride değişebilir; yönetilen bir projeden yerel projeli bir projeye geçilebilir, ama geçiş de başlı başına bir iştir ve planlanır.

İki iş akışı arasında ağaç açısından fark yoktur. Her ikisinde de ortak kod aynı tarifi üretir ve çerçeve aynı karşılaştırmayı yapar. Fark ağaçtan önce, yerel tarafın nasıl derlendiğindedir.

Yapılandırma dosyası bu seçimin ilk görünen yüzüdür. Aşağıdaki parça yönetilen bir akışta bulunabilecek türden bir yapılandırmanın biçimini gösterir. Parça ölçülmez; alan adları örnektir.

```jsonc
// Platform kodu: yapilandirma
{
  "name": "siparisler",
  "entry": "App",
  "platforms": ["ios", "android"],
  "orientation": "portrait"
}
```

Dosya uygulamanın adını, ortak kodun giriş noktasını ve hedef platformları söyler. Ortak kodun kendisi bu dosyada yoktur; dosya, kodun hangi ortamda koşacağını tarif eder. Yapılandırmadaki değerler de ağaca özellik olarak girmez. Bu, ilk dersteki sınırın bir başka biçimidir: ekranın yönü gibi bir ayar değiştiğinde ağaçtaki hiçbir düğümün özelliği değişmez ve kural bir akıbet üretmez; ayarın etkisi yerel taraftadır.

#### Geliştirme Döngüsü

Geliştirme sırasında ortak kod bir geliştirici sunucusundan sunulur. Sunucu paketleyiciyi çalıştırır; uygulama başlayınca sunucuya bağlanıp güncel paketi çeker. Ortak kodda bir dosya değişince paket yeniden hazırlanır ve çalışan uygulamaya iletilir. Bu döngü, emülatörde de gerçek bir aygıtta da aynı biçimde işler.

Her platformun kendi emülatörü ve kendi gerçek aygıtı olduğu için sınama iki ayrı hat gerektirir. Aynı ortak kod iki platformda koşar ama her platform tarifi kendi yerel görünümlerine çevirir; bu yüzden bir ekranı yalnız bir platformda denemek, öteki platformda da aynı göründüğünü kanıtlamaz.

Emülatör, bilgisayarın içinde çalışan bir platform kopyasıdır. Aygıt gerektirmez ve ekran boyutlarını değiştirerek denemek kolaydır. Gerçek aygıt ise kamera, sensör, bildirim ve dokunma gibi donanıma bağlı davranışları olduğu gibi sunar. İki ortam birbirinin yerine geçmez: emülatörde çalışan bir ekranın gerçek aygıtta aynı davrandığı varsayılmaz.

Döngünün bir sonucu önemlidir: ortak koddaki bir değişiklik çoğunlukla yerel tarafı yeniden derlemeyi gerektirmez. Yerel tarafa dokunan bir değişiklik, örneğin yerel bir bağımlılık eklemek, yerel projeli iş akışında yeniden derleme ister. Hangi değişikliğin hangi döngüye ait olduğunu bilmek, kurulumun günlük işteki asıl faydasıdır. Yeniden yüklemenin ağaca ne yaptığı kursun son dersinde ölçülür.

Burada ölçülen şey kurulumun kendisi değil, kurulumun sonunda ortaya çıkan ilk güncellemedir. Uygulama ilk açıldığında çerçevenin elinde önceki bir ağaç yoktur.

#### Önceki Ağaç Yokken

İlk açılışta kural ilk sorusunu sorar: önceki düğüm yoksa düğüm ve bütün alt ağacı kurulandır. Önceki ağaç olmadığı için karşılaştırılacak hiçbir şey yoktur; her düğüm, yerel görünümüyle birlikte sıfırdan oluşturulur. Ölçüm, ilk dersteki karşılaştırma çekirdeğiyle yapılır; çekirdek her derste aynı biçimde yeniden verilir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu dersin yardımcı dosyası, kurulum aşamasından görünüm ağacı üreten bir işlevdir.

```javascript
// app.mjs — uygulama durumundan gorunum agaci uretir
import { el } from "./fate.mjs";

export function renderApp(state) {
  const children = [el("Shell", { theme: state.theme ?? "acik" })];
  if (state.ready) {
    children.push(
      el("Home", {}, [
        el("Header", { title: "Siparisler" }),
        el("Text", { value: `${state.count} siparis` }),
      ]),
    );
  }
  return el("App", {}, children);
}
```

`Shell` uygulamanın boş kabuğudur: renk kuralını taşıyan ve başka bir şey göstermeyen bir düğüm. `Home` ana ekrandır ve ancak uygulama hazır olduğunda ağaca girer. İlk açılışı ölçmek için önceki ağaç olarak `null` veriyoruz.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const first = renderApp({ ready: true, count: 3 });

const ops = reconcile(null, first);
printOps(ops);
countFates(ops);
```

```text
kurulan: /App
kurulan: /App/Shell
kurulan: /App/Home
kurulan: /App/Home/Header
kurulan: /App/Home/Text
korunan: 0
guncellenen: 0
kurulan: 5
sokulen: 0
komut: 5
```

Beş düğümün beşi kurulan çıktı; korunan ve güncellenen sıfırdır, komut beştir. İlk ağaçta hiçbir düğümün eşi olmadığı için komut sayısı düğüm sayısına eşittir. Bu bir ilk açılışın bedelidir: yerel tarafta beş görünüm sıfırdan oluşturulur. Bedelin boyutunu model söylemez; yerel tarafın işi kuralın dışındadır.

**TY2.** Model ilk açılışı `reconcile(null, tree)` olarak kurar. Gerçek bir uygulamada ilk açılıştan önce de yerel taraf bir pencere ve bir kök görünüm hazırlar; bunlar ortak kodun ağacında yer almaz, bu yüzden modelde de yoktur. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

#### İlk Ağaç ve Sonraki Güncelleme

Gerçek uygulamalar çoğunlukla ana ekranı ilk ağaçta hemen göstermez. Önce boş bir kabuk durur; kaynaklar yüklenir, ardından ana ekran gelir. Bu iki adımı ayrı ayrı ölçersek ilk güncellemenin ağaçtaki etkisi değişir. Önceki ağaç bu kez boş kabuktur; sonraki ağaç ana ekranı da taşır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const shell = renderApp({ ready: false, count: 0 });
const home = renderApp({ ready: true, count: 3 });

const ops = reconcile(shell, home);
printOps(ops);
countFates(ops);
```

```text
korunan: /App
korunan: /App/Shell
kurulan: /App/Home
kurulan: /App/Home/Header
kurulan: /App/Home/Text
korunan: 2
guncellenen: 0
kurulan: 3
sokulen: 0
komut: 3
```

İkinci güncellemede kabuk korunan kaldı: `/App` ve `/App/Shell` aynı yerde, aynı tiple ve eşit özelliklerle durdu. Ana ekran ise önceki ağaçta eşi olmadığı için kurulan oldu; iki çocuğu da onunla birlikte kurulan çıktı ve kurulan düğüm sayısı üç oldu. İlk ölçümde beş olan komut sayısı burada üçe indi, çünkü kabuğun iki düğümü ilk açılışta zaten kurulmuştu.

#### Sorun Nerede Başlar

Kurulumun işe yaradığı bir yer de hata ayıklamadır. Bir uygulama açılmıyorsa ilk soru, sorunun hangi katmanda olduğudur. Üç katman ayrılır.

Birincisi araç zinciridir: bağımlılıklar indirilmemiştir, yerel derleme araçlarından biri eksiktir ya da emülatör hiç başlamamıştır. Bu durumda uygulama çalışmaz, dolayısıyla ortak kod hiç koşmaz ve ağaç hiç üretilmez. Akıbet sorusu bu katmanda sorulamaz, çünkü karşılaştırılacak ağaç yoktur.

İkincisi köprüdür: yerel taraf açılmış ama ortak koda ulaşamıyordur. Geliştirme sırasında bunun sık görülen biçimi, uygulamanın geliştirici sunucusuna bağlanamamasıdır. Burada da ağaç üretilmez; ekranda çoğunlukla yerel tarafın kendi hata görünümü belirir.

Üçüncüsü ortak koddur: köprü kurulmuş ve kod koşmuştur ama ürettiği ağaç beklenenden farklıdır. Ancak bu katmanda kursun sorusu anlam kazanır. Ağaç var, ağaçtaki düğümlere bir güncelleme yapılıyor ve güncelleme her düğüme bir akıbet yazıyor.

Bu ayrım bir sıra verir. Önce araç zinciri, sonra köprü, en sonda ağaç denetlenir. Sırayı tersine çevirmek, örneğin emülatör açılmazken ağaçta bir düğüm aramak, hiç var olmamış bir şeyi aramaktır.

#### Aynı Güncellemede İki Akıbet

Bir güncelleme ağacın her düğümüne aynı şeyi yapmak zorunda değildir. Ana ekran gelirken kabuğun rengi de değişebilir. Bu durumda kabuk, tipi ve yeri aynı olduğu için yerinde kalır ama özelliği değişir; ana ekran ise yeni olduğu için kurulan olur. `renderApp` durumdaki `theme` değerini kabuğa özellik olarak yazar; varsayılan değer `acik`tir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const shell = renderApp({ ready: false, count: 0 });
const home = renderApp({ ready: true, count: 3, theme: "koyu" });

const ops = reconcile(shell, home);
printOps(ops);
countFates(ops);
```

```text
korunan: /App
guncellenen: /App/Shell
kurulan: /App/Home
kurulan: /App/Home/Header
kurulan: /App/Home/Text
korunan: 1
guncellenen: 1
kurulan: 3
sokulen: 0
komut: 4
```

Beş düğümün her biri kendi akıbetini aldı: kök korunan, kabuk güncellenen, ana ekran ve çocukları kurulan. Komut dörttür. Bu ölçüm kuralın düğüm düğüm çalıştığını gösterir. Bir güncellemenin tek bir etiketi yoktur; etiket her düğüme ayrı yazılır, bu yüzden dersin akıbeti her zaman bir odak düğümün akıbetidir.

**TY3.** Model yönetilen ve yerel projeli iş akışlarını ağaç açısından aynı sayar. Gerçek bir akışta yerel tarafın başlangıcı, yani pencerenin ve kök görünümün hazırlanışı, iş akışına göre değişebilir; bu fark ağaçta görünmez ve model onu ölçmez.

#### Sınırlayıcı Ölçüm

Bu dersin odak düğümü `/App/Home`'dur. Sınırlayıcı ölçüm iki güncellemeyi yan yana koyar: kabuktan ana ekrana geçen güncelleme ve onu izleyen, sayacın üçten dörde çıktığı güncelleme. İlkinde yalnız odak düğümün satırı, ikincisinde ana ekranın bütün alt ağacı basılır; sayım ikinci güncellemeyi sayar.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const shell = renderApp({ ready: false, count: 0 });
const three = renderApp({ ready: true, count: 3 });
const four = renderApp({ ready: true, count: 4 });

const mount = reconcile(shell, three);
const next = reconcile(three, four);
printOps(mount.filter((op) => op.path === "/App/Home"));
printOps(next.filter((op) => op.path.startsWith("/App/Home")));
countFates(next);
```

```text
kurulan: /App/Home
korunan: /App/Home
korunan: /App/Home/Header
guncellenen: /App/Home/Text
korunan: 4
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

İlk satır dersin akıbetidir: ana ekranın ağaca girdiği güncellemede `/App/Home` kurulan çıktı, çünkü önceki ağaçta eşi yoktu. Sınır sonraki satırlardadır: sayaç değişince aynı `/App/Home` korunan kaldı ve yalnız `/App/Home/Text` güncellenen oldu. Bir düğümün akıbeti güncellemeye bağlıdır, düğümün kendisine değil. Kuruluş bir düğümün ömründe bir kez olur; ömrün geri kalanı korunan ve güncellenen güncellemelerle geçer.

Bu ayrım kurulumu yanlış yerde aratmamak için gereklidir. Araç zincirinin bir sorunu, örneğin emülatörün açılmaması, ağaçta hiçbir düğümü etkilemez: çünkü ağaç henüz üretilmemiştir. Kuralın baktığı şey araçların durumu değil, iki ağaçtır.

#### Özet

- Bu dersin akıbeti: kurulan. `/App/Home` ana ekranı, kabuktan sonra gelen ilk güncellemede önceki ağaçta eşi olmadığı için bütün alt ağacıyla kurulan çıktı ve sınırlayıcı ölçümün ilk satırı bunu basar; sonraki güncellemede aynı düğüm korunan kaldı.
- Ortam kurulumu JavaScript çalışma ortamı, paketleyici, iki platformun yerel derleme araçları ve bir çalışma yeri (emülatör ya da gerçek aygıt) gerektirir; yönetilen iş akışı yerel tarafı araç zincirine, yerel projeli iş akışı ekibe bırakır.
- İlk açılışta önceki ağaç yoktur: beş düğümün beşi kurulan, komut beş oldu.
- Boş kabuktan ana ekrana geçen güncellemede iki düğüm korunan, üç düğüm kurulan çıktı; komut üç oldu.
- Kurulum bir iştir, kurulan ise bir akıbettir; araç zincirinin durumu ağaçta görünmez.

#### Sonraki Adım

Bu derste ana ekran tek bir kapsayıcıdan oluşuyordu ve düğümlerin tipleri etiket olarak durdu. Sıradaki ders, `Çekirdek Bileşenler`, görünüm, metin, görsel ve giriş alanı gibi çekirdek tipleri ele alır ve bir yer tutucu metin yerine görsel gelince tip değişiminin ağaçta ne yaptığını ölçer.

### Çekirdek Bileşenler

Source: https://academia.sh/tr/kurslar/react-native/cekirdek-bilesenler

Çekirdek bileşenler görünüm, metin, görsel ve giriş alanıdır; hepsi görünüm ağacında bir düğümdür. Ders yer tutucu metnin görselle değiştiği güncellemede tip değişiminin ağaca ne yaptığını ölçer.

Önceki ders ilk ağacın nereden geldiğini sordu ve ana ekranı bütün alt ağacıyla kurulan buldu. Bu ders o ana ekranın içine bakar. Bir ekran, çerçevenin hazır sunduğu birkaç çekirdek tipten kurulur: kapsayıcı bir görünüm, metin, görsel ve yazı girilen bir alan. Hepsi ağaçta bir düğümdür ve düğümün tipi, akıbet kuralının üçüncü sorusudur: aynı yerde, ama başka tipte bir düğüm gelirse eskisi sökülür, yenisi kurulur.

Aşağıdaki parça bir profil kartının tarifidir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.

```jsx
// Platform kodu: Profile
function Profile({ name, photo }) {
  return (
    <View>
      {photo ? <Image source={photo} /> : <Text>Yukleniyor</Text>}
      <Text>{name}</Text>
      <TextInput value={name} />
    </View>
  );
}
```

`View` bir kapsayıcıdır ve kendi başına bir şey göstermez; çocuklarını bir arada tutar. `Text` yazıyı gösterir. `Image` bir görsel kaynağından çizilir. `TextInput` kullanıcının yazı girdiği alandır. İşlev, görsel hazırsa `Image`, değilse bir yer tutucu `Text` döndürür; ad ise her iki durumda da bir `Text` olarak gelir. Bu dört tip, bu derste ele alınan çekirdek bileşenlerdir; sonraki derslerde dokunma ve platform seçimiyle yenileri eklenir.

#### Dört Tip, Bir Kural

Çekirdek tiplerin her biri yerel tarafta bir görünüme karşılık gelir. Ortak kod `Text` düğümü tarif ettiğinde iOS'ta ve Android'de platformun kendi yazı görünümü kurulur. Yerel görünüm düğümün tipine bağlıdır; tip değişirse yerel görünüm de değişmek zorundadır. Bir yazı görünümü, yerinde bir görsel görünümüne dönüşemez. Bu yüzden kuralın üçüncü sorusu tipe bakar ve tip farklıysa özelliklere bakmaz: eski alt ağaç sökülür, yeni alt ağaç kurulur.

Ölçüm için önce çekirdek gerekir. Karşılaştırma çekirdeği her derste aynı biçimde yeniden verilir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu dersin yardımcı dosyası, profil durumundan görünüm ağacı üretir. Kart bir kapsayıcıdır; ilk çocuğu görsel hazırsa `Image`, değilse yer tutucu `Text`'tir. Adı taşıyan metnin anahtarı `ad`dır, bu yüzden yeri kardeşlerinin değişiminden etkilenmez. Giriş alanı biyografiyi taşır. İkinci işlev, yer tutucuyu da bir görsel yapan alternatif bir tariftir; aşağıda karşılaştırma için kullanılır. Görselin erişilebilirlik etiketi adı taşır; ad bu yüzden kartta iki yerde görünebilir.

```javascript
// profile.mjs — profil durumundan gorunum agaci uretir
import { el } from "./fate.mjs";

export function renderProfile(state) {
  const slot = state.photo === null
    ? el("Text", { value: "Yukleniyor" })
    : el("Image", { source: state.photo, label: state.name });
  return el("Card", {}, [
    slot,
    el("Text", { value: state.name }, [], "ad"),
    el("Input", { value: state.bio }),
  ]);
}

export function renderProfileImage(state) {
  return el("Card", {}, [
    el("Image", { source: state.photo, label: state.name }),
    el("Text", { value: state.name }, [], "ad"),
    el("Input", { value: state.bio }),
  ]);
}
```

#### Aynı Tip, Değişen Özellik

İlk ölçümde kullanıcı adını değiştirir. Görsel henüz yüklenmemiştir; yer tutucu durur. Tip ve anahtar her düğümde aynı kaldığı için kural özelliklere bakar.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: null, bio: "Kahve sever" });
const after = renderProfile({ name: "Ayse Yilmaz", photo: null, bio: "Kahve sever" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Card
korunan: /Card/Text
guncellenen: /Card/Text[ad]
korunan: /Card/Input
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Dört düğümün üçü korunan, biri güncellenen çıktı; komut birdir. `/Card/Text[ad]` aynı yerde, aynı tip ve aynı anahtarla durdu ama `value` özelliği değişti, bu yüzden güncellenen oldu. Yer tutucu `Text` ve giriş alanı değişmedi ve yerel tarafa hiçbir komut gitmedi.

#### Veri Nereye Varıyor

Aynı ad değişikliğini, görsel yüklenmişken yapalım. Ad artık iki yere ulaşır: yazı düğümüne ve görselin etiketine.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" });
const after = renderProfile({ name: "Ayse Yilmaz", photo: "profil.png", bio: "Kahve sever" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Card
guncellenen: /Card/Image
guncellenen: /Card/Text[ad]
korunan: /Card/Input
korunan: 2
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
```

İlk ölçümde aynı değişiklik tek bir düğümü güncelledi, burada iki düğümü. Veri aynı, tek durum alanı. Ağaçtaki yolu da aynıdır: kökte tutulan durumdan kartın çocuklarına, aşağı doğru. Değişen şey verinin vardığı yerde ne olduğudur. Yer tutucu olduğunda ad yalnız yazı düğümüne ulaşıyordu; görsel olduğunda görselin etiketine de ulaşıyor.

Bu, M31/K03'te sorduğumuz "bu veri arayüz ağacında hangi yöne gidiyor" sorusundan ayrıdır. Orada verinin ağaçtaki yönü sınıflanıyordu: aşağı mı, yukarı mı. Bu kurs verinin yolunu değil, vardığı düğümde ne olduğunu sınıflar. Yönü aynı iki veri, bir düğümü güncelleyebilir ya da hiç etkilemeyebilir; yönü farklı iki veri aynı akıbeti de verebilir.

**TY4.** Model girişi kontrollü sayar: giriş alanının değeri ağaçta bir özelliktir. Gerçek bir giriş alanı yazılan metni yerel tarafta da tutar; yazı ile ağaçtaki değer ayrışırsa bunu kural görmez. Bu, ilk dersteki sınırın bir uygulamasıdır: ağaçta görünmeyen şey akıbet almaz.

#### Tip Değişince

Şimdi görsel yüklenir ve yer tutucunun yerine geçer. Kartın ilk çocuğu aynı yerde durur ama tipi `Text`'ten `Image`'a döner. Kural üçüncü soruda durur: tip farklıdır, özelliklere bakılmaz.

#### Parçayı Satır Satır Okumak

Başta verilen profil kartı parçasına bir kez daha dönelim. `Profile` işlevi iki girdi alır: `name` ve `photo`. Dönen ağacın kökü `View`'dır; çocukları sırayla bir görsel ya da yer tutucu, bir metin ve bir giriş alanıdır. Üçlü işleç, `photo` doluysa `Image`, değilse `Text` seçer. İşlev bir koşul içerir ama ağaç koşulu taşımaz: ağaçta yalnız seçimin sonucu bulunur. Bir önceki tarif ile bir sonraki tarif arasındaki fark, bu koşulun hangi dalının seçildiğine göre okunur. `TextInput`'a verilen `value` özelliği ise girişin içeriğidir; kullanıcının yazdığı ile ağaçta duran aynı olmak zorunda değildir, bu da yukarıdaki varsayımda söylenir.

Burada bir yanılgıyı ayıklayalım: "Görsel yüklenince yer tutucu ile görsel aynı düğümdür, sadece içeriği değişir." Okurun gözünde ikisi kartın aynı köşesinde durur ve aynı şeyi temsil eder. Kuralın gözünde ise bir `Text` ile bir `Image` iki ayrı şeydir: yerel tarafta biri yazı görünümü, öbürü görsel görünümüdür ve birini diğerine dönüştürmek mümkün değildir. Düğümün ağaçtaki konumu aynı olsa da kimliği tipiyle birlikte gelir.

#### Yer Tutucu Da Bir Görsel Olsaydı

Karşı örnek için tarifi değiştirelim: yer tutucu da bir `Image` olsun, yalnız kaynağı boş olsun. Görsel gelince tip aynı kalır ve yalnız kaynak değişir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfileImage } from "./profile.mjs";

const before = renderProfileImage({ name: "Ayse", photo: null, bio: "Kahve sever" });
const after = renderProfileImage({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Card
guncellenen: /Card/Image
korunan: /Card/Text[ad]
korunan: /Card/Input
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Aynı kullanıcı için aynı değişim, bu tarifle tek komut. Yer tutucunun yerel görünümü sökülmedi; aynı görsel görünümü yeni kaynağı aldı. Kural tarifin içindeki niyeti bilmez, yalnız tipe ve özelliğe bakar; tarifi yazan kişi hangi tip değişiminin kaçınılabilir olduğunu görmek istiyorsa ölçüm ona bunu söyler. Her yer tutucunun görsel olabileceği söylenemez: bir yükleniyor yazısını bir görsele çevirmek anlam değiştirir. Ama tip değişiminin bir maliyeti olduğunu bilmek, tarifi yazarken seçim yapmayı mümkün kılar.

#### Sınırlayıcı Ölçüm

Odak düğüm, eski çocuk `/Card/Text`'tir. Görsel yüklenince kartın durumundaki `photo` değeri `null`dan bir kaynağa döner ve ilk çocuk yer tutucu `Text` yerine `Image` olur.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: null, bio: "Kahve sever" });
const after = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Card
sokulen: /Card/Text
kurulan: /Card/Image
korunan: /Card/Text[ad]
korunan: /Card/Input
korunan: 3
guncellenen: 0
kurulan: 1
sokulen: 1
komut: 2
```

`/Card/Text` sökülen, `/Card/Image` kurulan çıktı; komut iki. İki düğüm aynı konumda, ama tipleri farklı olduğu için kural onları aynı düğümün iki hâli saymadı. Yer tutucunun özelliği ile görselin özelliği arasında karşılaştırma yapılmadı. Ad metni anahtarı sayesinde kendi eşiyle buluştu ve korunan kaldı; yanındaki tip değişimi ona dokunmadı. Giriş alanı da korunan kaldı.

Sökülmenin bedeli yalnız komut sayısı değildir. Yer tutucu `Text`'in yerel görünümü ve içinde tuttuğu her durum yok olur; yeni `Image` hiçbir şeyi devralmaz. Yer tutucuda durum yoktu, bu yüzden burada bir şey kaybedilmedi. Aynı değişim bir giriş alanını bir yazı görünümüyle yer değiştirseydi, alandaki imleç konumu ve seçim yok olurdu. Bu kayıp ağaçta görünmez ve modelde ölçülmez; düzyazıda söylenir.

Şimdi karşılaştırma için bunun tersini yapalım: tip aynı kalsın, yalnız görselin kaynağı değişsin.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" });
const after = renderProfile({ name: "Ayse", photo: "profil2.png", bio: "Kahve sever" });

const ops = reconcile(before, after);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
guncellenen: /Card/Image
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Aynı yerde, aynı tipte ve aynı anahtarla duran `/Card/Image` güncellenen çıktı; komut bir. Görselin yerel görünümü yerinde kaldı ve yeni kaynağı aldı. Tip değişimi ile özellik değişimi arasındaki fark komut sayısında görünür: ilki iki komut, ikincisi bir komut ve yerel görünümün kimliği yalnız ikincisinde yaşar. İki komutun birini ödemek istemiyorsak yer tutucuyu da `Image` yapabiliriz. Yer tutucu bir `Image` ise görsel gelince tip aynı kalır ve yalnız kaynak özelliği değişir. Bu bir tasarım seçimidir ve ağaçta nasıl göründüğü bu ölçümlerle okunur.

**TY5.** Model tip karşılaştırmasını ad üzerinden yapar. Gerçek çerçeve iki farklı tarif işlevini farklı sayar ve işlev kimliğini kullanır; model bu inceliği tip adına indirir. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

Bu ölçümlerin ortak dersi şudur: bir düğümün yaşamını belirleyen, ekranda nasıl göründüğü değil, tarifte hangi tiple ve hangi anahtarla yazıldığıdır. İki farklı görünüm aynı tipin farklı özellikleri olabilir; iki benzer görünüm farklı tipler olabilir. Tarifi yazan kişi tipi seçerken aslında şunu seçer: bu düğüm bir yazı görünümü olarak mı yaşayacak, bir görsel görünümü olarak mı? Yaşam boyu aynı kalacak olanı tek tipte tutmak, güncellemeleri ucuz yapar; farklı yaşayacak olanları ayrı tiplerde tutmak ise sökmeyi ve kurmayı bilerek kabul etmektir.

#### Özet

- Bu dersin akıbeti: sökülen. Sınırlayıcı ölçümde yer tutucu `/Card/Text`, görsel gelince tipi değiştiği için özelliklerine bakılmadan sökülen çıktı; yerine `/Card/Image` kurulan oldu.
- Bu derste ele alınan çekirdek bileşenler görünüm, metin, görsel ve giriş alanıdır; hepsi ağaçta tipli bir düğümdür ve yerel tarafta tipine uyan bir görünüme karşılık gelir.
- Aynı tipte özellik değişimi düğümü güncellenen yapar: ad değişince `/Card/Text[ad]` güncellenen oldu ve komut bir kaldı.
- Aynı veri değişikliği vardığı düğüme göre farklı akıbet verir: ad yer tutucuyla tek, görselle iki düğümü güncelledi.
- Tip değişimi iki komut doğurdu (bir sökülen, bir kurulan); görselin yalnız kaynağı değişince tek komut gitti ve düğüm güncellenen oldu.

#### Sonraki Adım

Bu derste özellikler metin ve kaynak gibi düz değerlerdi. Sıradaki ders, `Biçem Sistemi`, düğümün görünüşünü belirleyen biçemi ele alır: biçem de ağaca bir özellik olarak girer ve içeriği aynı kalan yeni bir biçem nesnesinin ağaçta ne yaptığını ölçer.

### Biçem Sistemi

Source: https://academia.sh/tr/kurslar/react-native/bicem-sistemi

Biçem, düğümün görünüşünü belirleyen bir özelliktir ve ağaçta başka her özellik gibi karşılaştırılır. Ders içeriği aynı olan yeni bir biçem nesnesinin düğümü korunan bıraktığını, tek alan değişince güncellenen çıktığını ölçer.

Önceki dersin sonunda bir düğümün özelliklerinin düz değerler olduğunu söyledik: bir metin, bir görsel kaynağı. Biçem de bir özelliktir, ama değeri düz bir değer değil, bir nesnedir: genişlik, renk, kenar boşluğu gibi alanları bir arada taşır. Soru şudur: karşılaştırma kuralı bir nesneyi nasıl okur? Aynı alanları taşıyan iki ayrı nesne aynı mıdır, farklı mı? Cevap kuralın özelliklere baktığı dördüncü sorusunda gizlidir ve bu ders onu ölçer.

Biçemler çerçevede genellikle bir yardımcıyla tanımlanır ve düğümün özelliği olarak verilir. Aşağıdaki parça ölçülmez; yalnız biçemin ekranda nerede durduğunu gösterir.

```jsx
// Platform kodu: styles
const styles = StyleSheet.create({
  row: { flexDirection: "row", padding: 8 },
  box: { width: 40, height: 40, backgroundColor: "teal" },
});

function Strip() {
  return (
    <View style={styles.row}>
      <View style={styles.box} />
      <Text>Etiket</Text>
    </View>
  );
}
```

Parçayı satır satır okuyalım. `StyleSheet.create` alan adlarıyla bir nesne alır ve biçemleri tek bir yerde, adlandırılmış olarak toplar; `row` ve `box` birer biçem adıdır. `flexDirection: "row"` esnek kutu düzeninin ana eksenini belirler: çocuklar yan yana dizilir. Esnek kutu düzeni, bir kapsayıcının çocuklarını bir eksen boyunca yerleştiren düzen kuralıdır; çocukların konumunu tek tek yazmak yerine kapsayıcıya yönü söyleriz. `padding: 8` kapsayıcının içinde boşluk bırakır. `box` biçemi kutunun boyunu ve rengini verir. İşlevin içinde iki yer önemlidir: `style={styles.row}` kapsayıcının biçemini, `style={styles.box}` kutunun biçemini düğümün `style` özelliğine bağlar. `Text` düğümüne biçem verilmemiştir; çerçevenin varsayılanı geçerlidir.

#### Biçem Bir Özelliktir

Bu parçanın sonucu önemlidir. Biçem ayrı bir dil ya da ayrı bir kanal değildir; düğümün özellikleri arasında yer alan bir nesnedir. Biçem değişince ağaçta olan şey, başka bir özelliğin değişmesinden farklı değildir. Düğümün tipi aynı, anahtarı aynı ise kural özelliklere bakar; biçem dahil hepsi aynıysa korunan, biri farklıysa güncellenen çıkar. Ölçüm için önce çekirdek gerekir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu dersin yardımcı dosyası, yön, genişlik ve renk durumundan bir şerit ağacı üretir; ikinci işlev aynı biçemi iki kutuya birden verir. Şerit bir `Row` düğümüdür; yönü özellik olarak taşır. İlk çocuğu `Box`, ikincisi etiket metnidir. Kutunun biçemi her çağrıda yeni bir nesne olarak oluşturulur, tıpkı bir çizim işlevinin her çalışmasında olduğu gibi.

```javascript
// strip.mjs — yon, genislik ve renkten serit agaci uretir
import { el } from "./fate.mjs";

export function renderStrip(state) {
  return el("Row", { direction: state.direction ?? "row" }, [
    el("Box", { style: { width: state.width, color: state.color } }),
    el("Text", { value: state.label }),
  ]);
}

export function renderPair(state) {
  const style = { width: state.width, color: state.color };
  return el("Row", { direction: "row" }, [
    el("Box", { style }),
    el("Box", { style }),
  ]);
}
```

#### Birkaç Alan Birden

Bir güncelleme biçem nesnesinin birden çok alanını değiştirebilir. İlk ölçümde kutunun hem genişliği hem rengi değişir; kardeş metin ve kapsayıcı aynı kalır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStrip } from "./strip.mjs";

const before = renderStrip({ width: 40, color: "teal", label: "Etiket" });
const after = renderStrip({ width: 80, color: "orange", label: "Etiket" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Row
guncellenen: /Row/Box
korunan: /Row/Text
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

İki alan değişti ama komut bir. Akıbet düğüm başınadır: bir düğümde kaç alanın değiştiği, kaç komut gittiğini artırmaz. Gerçek bir uygulamada güncelleme komutu hangi alanların değiştiğini taşır ve yerel taraf onları tek tek uygular; o iş bu derste ölçülmez. Burada ölçülen, düğümün güncellenen çıkmasıdır.

#### Kapsayıcının Biçemi

Yaygın bir yanılgı, biçem değişince altındaki her şeyin de değiştiğini sanmaktır. Kapsayıcının yönü satırdan sütuna dönünce çocuklar ekranda başka yerlere gider; bu yüzden ağaçta da hepsi değişmiş gibi görünür. Ölçelim.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStrip } from "./strip.mjs";

const before = renderStrip({ direction: "row", width: 40, color: "teal", label: "Etiket" });
const after = renderStrip({ direction: "column", width: 40, color: "teal", label: "Etiket" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
guncellenen: /Row
korunan: /Row/Box
korunan: /Row/Text
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Yalnız `/Row` güncellenen çıktı; çocukların kendi özellikleri değişmediği için ikisi de korunan. Ekranda yer değiştirmeleri, ağaçta bir akıbet değildir. Düzeni yeniden hesaplamak yerel tarafın işidir ve kapsayıcının güncellenmesinin sonucudur. Ağaç, kimin özelliğinin değiştiğini söyler; kimin ekranda kaydığını söylemez. Bu, biçemin bu kurstaki ölçüsünün sınırıdır.

Burada M31/K01'den bir ayrım yapmak gerekir. Orada iki değerin eşit olup olmadığı dilin kendi sorusuydu: eşitlik, bir değerin başka bir değerle aynı olup olmadığını soran bir işlemdi ve sorunun cevabı kendi başına bir sonuçtu. Burada eşitlik yalnızca kuralın girdisidir. Kural iki biçem nesnesinin içeriğini eşit bulursa bununla bir şey yapar: düğümü korunan bırakır. Eşitliğin cevabı değil, ondan çıkan akıbet ölçülür.

#### Ortak Biçem, İki Düğüm

Biçemin bir kez tanımlanıp birçok yerde kullanılması, bu işin asıl nedenidir. Bir kenar boşluğunu ya da bir rengi her düğümde ayrı ayrı yazmak yerine adı olan tek bir biçem kullanırız. Ağaçta ise bu paylaşım görünmez: her düğüm kendi özelliğini taşır. Biçem değişince, o biçemi kullanan her düğüm kendi içinde bir fark bulur.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderPair } from "./strip.mjs";

const before = renderPair({ width: 40, color: "teal" });
const after = renderPair({ width: 40, color: "orange" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Row
guncellenen: /Row/Box
guncellenen: /Row/Box
korunan: 1
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
```

Tek bir biçem değişti, ama iki düğüm güncellenen çıktı ve komut iki oldu. Biçemin ortak olması komutu paylaştırmaz: kural düğümlere bakar, biçem adlarına değil. Bu, biçemleri bir yerde toplamanın bakım kolaylığı sağladığını, ama güncelleme maliyetini azaltmadığını gösterir. İki düğümü bir arada tutmak istiyorsak, onları tek bir kapsayıcıya bağlayıp kapsayıcının biçemini değiştirmek gerekir; önceki bölümlerde yön değişimi tam böyle tek komutla bitmişti.

İki biçem yaklaşımını yan yana düşünmek yararlıdır. Birinde ortak bir özelliği her düğüme yazarız ve değiştirince hepsi güncellenir. Ötekinde özelliği kapsayıcıya koyarız ve çocukları yerleşim kuralına bırakırız. Hangisi doğrudur sorusunun cevabı, görünüşün gerçekten ortak mı yoksa tesadüfen aynı mı olduğuna bağlıdır. Ağaç bu ayrımı yapmaz; sayı yalnız tercihin sonucunu gösterir. Karar yine yazan kişiye aittir.

Bir yanılgıyı daha açalım: "biçemi hiç değiştirmedim, o halde düğüm hiçbir zaman güncellenmez" düşüncesi. Düğümün biçemi aynı kalsa bile metni, görsel kaynağı ya da basılı durumu değişirse düğüm güncellenen çıkar. Kural özelliklerin hepsine birlikte bakar; biçem yalnız bu özelliklerden biridir. Tersi de doğrudur: yalnız biçemi değişen düğümün içeriği aynı kalır ve yine güncellenen olur.

Durumdan gelen biçem de aynı kurala bağlıdır. Bir düğmenin seçili olduğunda başka renkte görünmesini istediğimizde, biçemi bir koşulla seçeriz: durum seçiliyse bir biçem, değilse ötekini veririz. Ağaç yine yalnız sonucu görür. Seçimin hangi koşuldan geldiği, koşulun nerede yazıldığı ya da kaç satır tuttuğu kuralı ilgilendirmez; ilgilendiren, düğümün ürettiği biçem nesnesinin önceki nesneden içerik olarak ayrılıp ayrılmadığıdır. Koşul her seferinde aynı biçemi seçiyorsa düğüm korunan kalır; farklı bir biçem seçiyorsa güncellenen çıkar. Biçemin tanımı ile biçemin seçimi ayrı işlerdir, ve ağaç yalnız seçimin sonucunu taşır.

#### Sınırlayıcı Ölçüm

Odak düğüm `/Row/Box`'tır ve ölçüm iki adımdır. Birincisi, hiçbir şey değişmeden ağacı ikinci kez üretir. Her çağrı kutu için yeni bir biçem nesnesi yaratır: bellekte iki ayrı nesne, ama alanları birebir aynı.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStrip } from "./strip.mjs";

const state = { width: 40, color: "teal", label: "Etiket" };
const ops = reconcile(renderStrip(state), renderStrip(state));
printOps(ops);
countFates(ops);
```

```text
korunan: /Row
korunan: /Row/Box
korunan: /Row/Text
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Üç düğüm de korunan, komut sıfır. Model özellikleri alan alan, içeriklerine bakarak karşılaştırır; iki nesnenin ayrı olması bir fark sayılmaz. `/Row/Box` yeni bir biçem nesnesi almasına rağmen yerel tarafa hiçbir komut gitmedi. İkinci adımda yalnız kutunun rengi değişir; genişlik, kardeş metin ve kökün durumu aynı kalır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStrip } from "./strip.mjs";

const before = renderStrip({ width: 40, color: "teal", label: "Etiket" });
const after = renderStrip({ width: 40, color: "orange", label: "Etiket" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Row
guncellenen: /Row/Box
korunan: /Row/Text
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Biçem nesnesinin tek alanı değişti ve `/Row/Box` güncellenen çıktı; bu, dersin ilk gerçek özellik değişimidir. İki adım birlikte okunur: içerik aynıysa sıfır komut, bir alan farklıysa bir komut. Aradaki farkı nesnenin yeniden yaratılması değil, içeriği belirler. Bu yüzden bu dersin akıbeti korunandır: yeni nesne gelmiş olması hiçbir şeyi değiştirmez; güncellenen yalnız bir alan gerçekten farklı olduğunda çıkar.

**TY6.** Model biçem nesnelerini içerik üzerinden karşılaştırır. Gerçek bir çerçeve özellikleri nesne kimliğiyle karşılaştırabilir; bu durumda her çalışmada yeniden oluşturulan bir biçem nesnesi her seferinde fark sayılabilir. Bu modelde nesne kimliğiyle karşılaştıran bir yer yoktur ve bu ders o davranışı ölçmez. Neyin ölçüldüğü bellidir: içerik aynıyken kural ne yapıyor. Biçemi bir kez tanımlayıp yeniden kullanmanın gerçek çerçevede neden önerildiğinin bir sebebi de budur; ama bu gerekçenin sayısı burada verilmez.

**TY7.** Gerçek biçemler bir dizi olarak da verilebilir ve çerçeve onları tek bir nesnede birleştirir. Model biçemi tek bir düz nesne sayar. Dizilerin birleştirilmesi ve geçersiz alanların elenmesi ağaçta görünmez.

Biçem değişimi hiçbir düğümü söküp kurmadı. Tip ve anahtar aynı kaldığı sürece görünüş ne kadar değişirse değişsin düğüm en fazla güncellenen çıkar. Sökmenin yolu tipi ya da anahtarı değiştirmektir; tip değişimini önceki ders ölçtü. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

Kısaca, biçem sistemi ağacın dışında bir mekanizma değildir. Biçemler tanım yeri bakımından ayrı bir dosyada, adlandırılmış bir tabloda durabilir; ağaca girdiklerinde ise yalnız bir özellik değeri olurlar. Karşılaştırma, biçemin nerede tanımlandığına bakmaz, düğümün taşıdığı değere bakar. Bu yüzden biçemle ilgili her soru aynı üç soruya iner: tip aynı mı, anahtar aynı mı, özelliklerin içeriği aynı mı? Üçüne de evet ise düğüm korunan kalır ve biçemin nerede, nasıl yazıldığı sonucu değiştirmez.

Biçemle ilgili son bir not: bir biçemin tek bir alanını değiştirmek ile biçem nesnesinin tamamını başka bir nesneyle değiştirmek ağaçta aynı sonucu verebilir. Değişen alan sayısı kuralı ilgilendirmez; ilgilendiren, nesnenin önceki nesneyle içerik olarak eşit olup olmadığıdır.

#### Özet

- Bu dersin akıbeti: korunan. Sınırlayıcı ölçümde içeriği aynı olan yeni biçem nesnesi `/Row/Box`'ı korunan bıraktı ve komut sıfır oldu; ardından tek bir alan değişince aynı düğüm, dersin ilk gerçek özellik değişimiyle güncellenen çıktı.
- Biçem, düğümün özellikleri arasında duran bir nesnedir; ağaçta başka her özellik gibi karşılaştırılır.
- Model nesneleri içerikle karşılaştırır: içerik aynıysa yeni nesne bir fark sayılmaz. Nesne kimliğiyle karşılaştırma bu modelde yoktur ve ölçülmez.
- Bir ya da birkaç alan değişince düğüm güncellenen çıkar ve tek komut gider; kardeşler ve çocuklar korunan kalır.
- Kapsayıcının yönü değişince yalnız kapsayıcı güncellenir; çocukların ekranda kayması ağaçta bir akıbet değildir.

#### Sonraki Adım

Şimdiye kadar ağacı biz değiştirdik: durumu elle iki ayrı değerle verdik. Sıradaki ders, `Dokunma ve Etkileşim`, ağacı değiştiren olayı ele alır: bir dokunuş bir özelliği değiştirir ve bu değişimin hangi düğümlere ulaştığını ölçer.

### Dokunma ve Etkileşim

Source: https://academia.sh/tr/kurslar/react-native/dokunma-ve-etkilesim

Bir dokunuş durumu değiştirir, durum bir özelliği değiştirir ve özellik bir düğümü güncellenen yapar. Ders basılı durumun yalnız düğmeyi güncellediğini, kardeşlerin korunan kaldığını ölçer.

Şimdiye kadar iki ağacı biz yan yana koyduk: önceki ve sonraki. Gerçek bir ekranda sonraki ağacı üreten şey bir olaydır, çoğu zaman bir dokunuş. Parmak ekrana değer, çerçeve olayı ilgili düğümün işleyicisine iletir, işleyici durumu değiştirir ve ekran yeni durumdan yeniden tarif edilir. Bu zincirin sonunda kural yine aynı soruyu sorar: hangi düğüm korunan, hangisi güncellenen?

Aşağıdaki parça dokunmaya duyarlı bir düğmeyi gösterir. Parça ölçülmez.

```jsx
// Platform kodu: Action
function Action({ label, onPress }) {
  const [pressed, setPressed] = useState(false);
  return (
    <Pressable
      onPressIn={() => setPressed(true)}
      onPressOut={() => setPressed(false)}
      onPress={onPress}
    >
      <Text>{pressed ? "Basili" : label}</Text>
    </Pressable>
  );
}
```

`Pressable` dokunmaya duyarlı bir kapsayıcıdır. Parmak değince `onPressIn`, çekilince `onPressOut`, bir basış tamamlanınca `onPress` çalışır. İşleyiciler bir durum değeri değiştirir ve bu değer düğümün içeriğini belirler. Dokunma, ağaçta bir düğüm değildir; ağaçta yalnız onun bıraktığı iz görünür.

#### Olaydan Düğüme

Zincir dört halkalıdır: olay, durum, özellik, düğüm. Olay ağaç dışındadır. Durum ağaç dışında tutulur ve ekranı yeniden tarif eder. Özellik, tarifin bir düğüme verdiği değerdir. Düğüm, kuralın akıbet verdiği şeydir. Modelde olaya yer yoktur; yalnız ardışık iki tarif vardır. Bunun bir yararı var: olayın nasıl geldiği ne olursa olsun, akıbet iki tarifin farkından okunur.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu dersin yardımcı dosyası, basılı durumdan bir düğme ağacı üretir. Ekran bir başlık, bir düğme ve bir not satırından oluşur. Düğme `pressed` özelliğini taşır; bu özellik basılı durumun ağaçtaki yüzüdür. Başlığın ve notun içeriği olaydan etkilenmez.

```javascript
// button.mjs — basili durumdan ekran agaci uretir
import { el } from "./fate.mjs";

export function renderButton(state) {
  return el("Screen", {}, [
    el("Text", { value: "Siparis" }),
    el("Button", { label: state.label, pressed: state.pressed }),
    el("Text", { value: state.note }),
  ]);
}
```

#### Parmak Çekilince

Parmak çekilince `onPressOut` çalışır ve `pressed` tekrar yanlış olur. Ağaç ilk haline döner.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderButton } from "./button.mjs";

const pressedTree = renderButton({ label: "Onayla", pressed: true, note: "Iki urun" });
const releasedTree = renderButton({ label: "Onayla", pressed: false, note: "Iki urun" });

const ops = reconcile(pressedTree, releasedTree);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
guncellenen: /Screen/Button
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Basış ve bırakış simetriktir: her biri tek bir güncellenen ve tek komut. Bu modelde bir dokunuşun tamamı iki komuttur ve ikisi de aynı düğüme gider. Düğme hiçbir anda sökülüp kurulmadı; aynı yerel görünüm iki kez güncellendi.

#### Dokunulan Düğüm Değil, Değişen Düğüm

Sık düşülen bir yanılgı, dokunulan düğümün güncellenen çıkacağını varsaymaktır. Parmak düğmeye değdi, o halde düğme değişti. Oysa kural dokunuşa değil, tarifler arasındaki farka bakar. Bir düğmeye dokunmak, ekranda başka bir yerdeki bir metni değiştirebilir ve düğmenin kendisi hiçbir özelliğini değiştirmeyebilir. Ölçelim: dokunuş yalnız not satırını günceller, düğme aynı kalır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderButton } from "./button.mjs";

const before = renderButton({ label: "Onayla", pressed: false, note: "Iki urun" });
const after = renderButton({ label: "Onayla", pressed: false, note: "Uc urun" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Text
korunan: /Screen/Button
guncellenen: /Screen/Text
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Düğme dokunuşun hedefiydi ve korunan çıktı; güncellenen, notu taşıyan metin düğümü oldu. Dokunma hedefi, yani parmağın değdiği alan, ağacın bir özelliği değildir; ağaçta yalnız düğümlerin özellikleri durur. Hedef ile akıbet arasında bir bağ yoktur, aradaki bağı işleyicinin değiştirdiği durum kurar. Bu yüzden komut sayısını tahmin etmeden önce "hangi durum değişti" sorusu sorulur, "neye dokunuldu" sorusu değil.

Bir parçayı daha satır satır okuyalım. Başta verilen `Action` işlevi `useState` ile bir `pressed` değeri tutar. `onPressIn` değeri doğru, `onPressOut` yanlış yapar; `onPress` ise dışarıdan gelen işleyiciyi çalıştırır ve basış tamamlandığında bir kez tetiklenir. `Text` düğümü basılıysa sabit bir yazı, değilse etiketi gösterir. İşlev tek bir ağaç döndürür; basılı hâl ile normal hâl iki ayrı ağaçtır ve fark yalnız o metnin içeriğindedir. Aynı iş, düğmeye bir `pressed` özelliği vererek de yapılabilir; ağaçta hangi biçimin tercih edildiği kuralı değiştirmez, yalnız farkın hangi düğümde göründüğünü değiştirir.

#### Bir Dokunuş, Birçok Durum

Şimdi dokunuş yalnız basılı durumu değil, bir de notu değiştirir: basış tamamlanınca işleyici etiketi değiştirir ve sipariş sayısını güncelleyerek not satırına yazar.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderButton } from "./button.mjs";

const before = renderButton({ label: "Onayla", pressed: true, note: "Iki urun" });
const after = renderButton({ label: "Onaylandi", pressed: false, note: "Uc urun" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Text
guncellenen: /Screen/Button
guncellenen: /Screen/Text
korunan: 2
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
```

Tek olay üç alanı değiştirdi: düğmenin etiketi, düğmenin basılı özelliği ve notun içeriği. Ağaçta iki düğüm güncellenen çıktı; komut iki. Düğmenin iki alanı tek komut sayıldı; akıbet düğüm başınadır. Başlık yine korunan kaldı. Bir dokunuşun kaç düğüme ulaştığı, işleyicinin hangi durumları değiştirdiğine bağlıdır; dokunuşun kendisi bir sayı belirlemez.

Burada bir ayrım yapılmalı. Sayıyı belirleyen, olay değil, olayın bıraktığı durum farkıdır. Aynı dokunuş bir ekranda sıfır komut, bir başkasında beş komut doğurabilir. Dokunmaya bakarak komut sayısı tahmin edilmez; iki tarifi karşılaştırmak gerekir.

#### Aynı Durum Yeniden Gelirse

Dokunma olayları sık gelir. Parmak ekranda hafifçe kayarken çerçeve aynı olayı arka arkaya birden çok kez iletebilir ve işleyici her seferinde durumu yeniden yazar. Durumun yazılması ile durumun değişmesi aynı şey değildir: işleyici `pressed` değerini doğru yaptığında değer zaten doğruysa, ağaçta bir fark oluşmaz. Ölçelim.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderButton } from "./button.mjs";

const first = renderButton({ label: "Onayla", pressed: true, note: "Iki urun" });
const again = renderButton({ label: "Onayla", pressed: true, note: "Iki urun" });

const ops = reconcile(first, again);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Text
korunan: /Screen/Button
korunan: /Screen/Text
korunan: 4
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Dört düğümün hepsi korunan, komut sıfır. Aynı değeri tekrar yazmak yeni bir tarif üretir, ama tarif öncekine eşit olduğu için kural hiçbir komut göndermez. Bu, ilk dersteki yeniden çizim ile komutun ayrı şeyler olduğu bilgisinin dokunma tarafındaki karşılığıdır: olay ne kadar sık gelirse gelsin, komutu belirleyen farktır. Yoğun olay akışının ağaca yük bindirip bindirmediği sorusu, olayın sayısına değil, olayların bıraktığı durumun ne kadar farklı olduğuna bağlıdır.

#### Basış Üç Olaydır

Bir basışın bütününü anlamak için olayları ayırmak gerekir. Parmak değince `onPressIn`, çekilince `onPressOut` çalışır. Bu ikisi arasında parmak hedefin dışına kayarsa basış iptal edilir ve `onPress` hiç çalışmaz. Parmak hedefin içinde çekilirse basış tamamlanmış sayılır ve `onPress` çalışır. Yani basılı geri bildirimi her zaman geri alınır, ama işlem yalnız tamamlanan basışta yapılır. İki ayrı şey birbirine karıştırılmamalıdır: geri bildirim ağaçta bir özellik değişimidir, işlem ise uygulamanın durumunu değiştiren asıl iştir.

Bu ayrım ağaçtaki sonuca da yansır. İptal edilen bir basış, ağaçta basılı hâlin açılıp kapanmasından ibarettir: iki güncelleme, hiçbir kalıcı fark. Tamamlanan bir basış ise bu iki güncellemeye ek olarak işlemin durumu değiştirmesiyle gelen farkı da taşır; yukarıda ölçtüğümüz birçok durum bu türdendi. İki basışı yalnız ağaca bakarak ayırt etmek, sonuçta bir şey değiştiyse mümkündür; hiçbir şey değişmediyse ikisi de aynı iki komutu bırakır.

Dokunma hedefinin büyüklüğü de bir tasarım konusudur. Küçük bir hedef parmakla isabet ettirmeyi zorlaştırır ve kullanıcı yanlış yere dokunabilir. Çerçeve hedefi genişletmek için ek bir boşluk tanımlamaya izin verir; bu boşluk görünümü büyütmeden parmağın değebileceği alanı büyütür. Bu değer ağaçta bir özellik olarak görünebilir, ama bu derste modele alınmadı. Neden: kural yalnız özelliğin değişip değişmediğine bakar ve hedef büyüklüğünün tek başına değişmesi nadir görülen bir durumdur. Önemli olan, ağaçta yazılı olmayan hiçbir şeyin akıbet almadığını hatırlamaktır; hedefi büyütmek yerel tarafın işidir ve ağaca ya hiç girmez ya da bir özellik olarak girer.

#### Sınırlayıcı Ölçüm

Odak düğüm `/Screen/Button`'dır ve ölçülen an `onPressIn` anıdır. Durum yalnız bir alanı değiştirir: `pressed` yanlıştan doğruya geçer.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderButton } from "./button.mjs";

const before = renderButton({ label: "Onayla", pressed: false, note: "Iki urun" });
const after = renderButton({ label: "Onayla", pressed: true, note: "Iki urun" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Text
guncellenen: /Screen/Button
korunan: /Screen/Text
korunan: 3
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Dört düğümün üçü korunan, biri güncellenen; komut bir. Dokunuş kökten başlayıp bütün ekranı yeniden tarif ettirir, ama kural yalnız `/Screen/Button`'da fark bulur. Başlık, not ve kapsayıcı yeniden tarif edilir, ağaçta karşılaştırılır ve korunan çıkar. Yeniden tarifin ucuz olmasının sebebi budur: pahalı olan yerel komuttur ve o yalnız farkın olduğu yerde gider.

Aynı geri bildirimi yalnız yerel tarafta tutmak da mümkündür: basılı görünüm yerel görünümün kendi içinde değişir ve ortak koda hiç dönmez. O durumda ağaçta hiçbir şey değişmez ve düğme korunan kalır. Bu ölçülmez, çünkü ölçülecek bir fark yoktur: kural yalnız ağaçta görüneni görür. Basılı hâlin ağaca özellik olarak yazılması bir tercihtir, zorunluluk değildir; bu derste yazılmıştır ki dokunuşun ağaçta bıraktığı iz gösterilebilsin.

**TY8.** Model `pressed`'i sıradan bir özellik olarak taşır. Gerçek çerçevede basılı durum çoğu zaman yerel tarafta işlenir ve ortak koda hiç dönmeden görünümü değiştirir. O durumda ağaçta bir komut da görünmez. Model, bu yolu kapalı tutar ve basılı durumu ağaca bilerek sokar; ölçülen, durum ağaca girdiği takdirde olan şeydir.

**TY9.** Model olayın işleyicisini çalıştırmaz. İşleyicinin ürettiği durumu doğrudan tarif işlevine verir. Gerçek çerçevede durum güncellemeleri toplanabilir ve birkaç dokunuş tek bir yeniden tarifte birleşebilir; model her durumu ayrı bir tarif sayar.

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

Bu dersin ölçümlerinden çıkan pratik kural kısadır: dokunmanın ağaçta bıraktığı izi görmek için önce hangi durumun değiştiğini, sonra o durumu hangi düğümün okuduğunu sormak gerekir. İki sorunun cevabı aynı düğümü gösterirse güncellenen o düğümdür. Farklı düğümleri gösteriyorsa, dokunulan düğüm korunan kalır. Hiçbir düğümü göstermiyorsa, yani değişen durumu ağacı üreten işlev okumuyorsa, ağaç aynı çıkar ve komut sıfır olur; ilk dersteki sayaç ölçümü tam bu durumdu.

Son olarak, ölçümlerin hepsinde dokunuşun kendisinin bir akıbeti olmadığını not edelim. Akıbet yalnız düğümlerindir. Dokunuş ağaçta bir ize dönüşmediği sürece, yani bir özelliğin değerini değiştirmediği sürece, kural için gerçekleşmemiş sayılır.

#### Özet

- Bu dersin akıbeti: güncellenen. Sınırlayıcı ölçümde basılı durum ağaca özellik olarak yazıldığı için `/Screen/Button` güncellenen çıktı, kardeşleri ve kapsayıcı korunan kaldı; aynı geri bildirim yalnız yerel tarafta tutulsaydı düğüm korunan kalırdı.
- Zincir olay, durum, özellik ve düğümdür; ağaçta yalnız özellik ve düğüm görünür.
- Basılı durum bir özellik olarak taşındığında dokunuş tek düğümü güncellenen yapar ve tek komut gider.
- Basış ve bırakış ayrı birer güncelleme, her biri bir komuttur; düğme hiç sökülüp kurulmaz.
- Komut sayısını dokunuş değil, dokunuşun bıraktığı durum farkı belirler.

#### Sonraki Adım

Şimdiye kadar iki tarif aynı çerçevenin aynı koşuluna aitti. Son derste, `Platforma Özgü Kod`, aynı ekranın iki platformda farklı tarif edildiği durumu ele alırız ve fark ağaçta ne bırakır diye ölçeriz.

### Platforma Özgü Kod

Source: https://academia.sh/tr/kurslar/react-native/platforma-ozgu-kod

Aynı ekran iki platformda farklı tarif edilebilir. Ders iki platform ağacını karşılaştırır, yalnız bir platformda bulunan anahtar düğmesinin kurulan çıktığını ve bunun çalışma anı güncellemesi olmadığını açıkça gösterir.

Önceki derslerde tek bir ağacın zamanla nasıl değiştiğini ölçtük. Bu derste iki ağaç var ve ikisi aynı anda var olmaz: biri iOS'ta, biri Android'de çalışır. Ortak kod bir ekranı tarif ederken platforma bakıp farklı düğümler üretebilir. O zaman soru şudur: iki platformun ağacı birbirine ne kadar benzer, ve fark ağaçta nasıl görünür?

Bir şeyi baştan açık söyleyelim. Bir uygulama bir anda ya iOS'ta ya Android'dedir; çalışma anında bir platformdan öbürüne geçmez. İki ağacı karşılaştırmak, yani bir platformun ağacından öbürünün ağacına akıbet çıkarmak, bir çalışma anı güncellemesi değildir. Burada karşılaştırma bir ölçü aracıdır: iki ağacın nerede ayrıldığını gösterir.

Platformu soran kod çerçevede şöyle görünür. Parça ölçülmez.

```jsx
// Platform kodu: Row
function Row({ label }) {
  return (
    <View>
      <Text>{label}</Text>
      {Platform.OS === "ios" ? <Switch /> : <Checkbox />}
    </View>
  );
}
```

`Platform.OS` çalıştığı platformun adını verir. İfade bir koşuldur: `ios` ise bir anahtar düğmesi, değilse bir onay kutusu döner. Aynı kapsayıcı ve aynı etiket, ama son çocuğun tipi platforma göre farklıdır.

#### Ortak Kod, Ayrışan Tarif

Çerçevenin çekirdek tipleri iki platformda ortaktır; platforma özgü olan, o tiplerin hangisinin seçildiğidir. Ortak kod bu seçimi tarif işlevinin içinde yapar ve iki ayrı ağaç doğar. Ölçüm için önce çekirdek gerekir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu dersin yardımcı dosyası, platform adından bir ayarlar ekranı ağacı üretir. Her iki platformda da ekran bir başlık ve bir etiket taşır. Android'de ek olarak bir anahtar düğmesi bulunur; iOS'ta bu düğüm yoktur. İkinci işlev aynı satırı iki platformda farklı tiple üretir; aşağıda karşılaştırılır.

```javascript
// settings.mjs — platform adindan ayarlar ekrani agaci uretir
import { el } from "./fate.mjs";

export function renderSettings(platform) {
  const children = [
    el("Text", { value: "Ayarlar" }),
    el("Text", { value: "Bildirimler" }),
  ];
  if (platform === "android") {
    children.push(el("Toggle", { on: true }));
  }
  return el("Settings", {}, children);
}

export function renderChoice(platform) {
  return el("Row", {}, [
    el("Text", { value: "Bildirimler" }),
    platform === "ios" ? el("Switch", { on: true }) : el("Checkbox", { on: true }),
  ]);
}
```

#### Ortak Olan Ne Kadar

İlk ölçüm, aynı platformun ağacını kendisiyle karşılaştırır. Bu, ortak kodun kendi içinde tutarlı olduğunu gösteren bir referanstır: platform değişmez, akıbet de değişmez.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("ios"), renderSettings("ios"));
printOps(ops);
countFates(ops);
```

```text
korunan: /Settings
korunan: /Settings/Text
korunan: /Settings/Text
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Üç düğüm korunan, komut sıfır. Referans budur: iOS tarifi iOS tarifiyle karşılaştırılırsa fark yoktur. Şimdi iki platformu karşılaştıralım.

#### İki Platformun Farkı

İkinci ölçüm iOS ağacını Android ağacıyla karşılaştırır. Kapsayıcı ve iki metin her ikisinde de vardır; anahtar düğmesi yalnız Android'dedir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("ios"), renderSettings("android"));
printOps(ops);
countFates(ops);
```

```text
korunan: /Settings
korunan: /Settings/Text
korunan: /Settings/Text
kurulan: /Settings/Toggle
korunan: 3
guncellenen: 0
kurulan: 1
sokulen: 0
komut: 1
```

Ortak üç düğüm korunan, `/Settings/Toggle` kurulan. Ortak kodun payı üç düğüm, platforma özgü payı bir düğümdür. Bunu şöyle okuruz: iki platformun ekranı dört düğümden üçünde birbirine eşittir. Bu bir oran değil, bir sayımdır; sayı yalnız bu iki ağaç için geçerlidir.

Kurulan sözcüğü burada dikkatle okunmalı. iOS'ta çalışan bir uygulama o anahtar düğmesini hiçbir zaman kurmaz, çünkü Android'in ağacını hiç görmez. Karşılaştırma bir geçiş değil, bir ayrımdır: "Android'de fazladan hangi düğüm var" sorusunun cevabı.

**TY10.** Model iki platformun ağacını aynı süreçte ve aynı anda üretir. Gerçekte yalnız biri üretilir ve çalıştığı platform uygulama boyunca sabit kalır. Bu model, bir karşılaştırma yapmak için gerekli bir kolaylıktır, çalışma anı davranışı değildir.

#### Platform Modülü

Dosya seçiminin yanında çerçeve, çalıştığı platformu soran küçük bir modül sunar. Modül platformun adını verir ve değerleri bir tablodan seçmeye yarayan bir yardımcı içerir: iOS için bir değer, Android için başka bir değer yazılır, çalışan platformunki döner. Bu iki yol aynı işi görür; birinde seçim bir ifadenin içinde, öbüründe dosya adında yapılır. Küçük farklar için, örneğin bir kenar boşluğunun iki platformda başka olması için, modül yeterlidir; bütün bir ekran parçasının iki platformda ayrı yazılması gerekiyorsa dosya yolu daha temiz kalır.

Karar verirken tek bir soru işe yarar: ayrışma görünümün bir ayrıntısı mı, yoksa davranışın kendisi mi? Ayrıntıysa ağaçta bir özellik farkı olarak görünür ve karşılaştırmada güncellenen çıkar. Davranışsa ağaçta farklı tipler ya da farklı düğümler doğar ve karşılaştırma kurulan ile sökülen verir. Özellik farkı ortak payı yüksek tutar; tip farkı onu düşürür. Bu yüzden ayrışmayı mümkün olan en küçük düğümde yapmak, ortak payı korumanın yoludur.

#### Dosya Uzantısıyla Seçim

Platforma göre ayrışmanın tek yolu bir koşul yazmak değildir. Çerçeve, aynı adı taşıyan ama platformu uzantısında belirten dosyalar arasında seçim yapabilir: bir dosyanın iOS için, bir başkasının Android için yazılmış hâli yan yana durur ve içe aktarma yalnız ortak adı yazar. Çalışan platform hangi dosyayı seçeceğini belirler; kodda hiçbir `if` yoktur.

```jsx
// Platform kodu: Toggle
// Toggle.ios.js    -> iOS icin yazilmis dosya
// Toggle.android.js -> Android icin yazilmis dosya
import Toggle from "./Toggle";

function Row() {
  return <Toggle on={true} />;
}
```

Parçadaki ilk iki satır dosya adlarını gösteren yorumdur; kod, yalnız ortak adı içe aktarır. Hangi dosyanın kullanılacağı uygulama paketlenirken seçilir: her platform için ayrı bir paket kurulur, iOS paketine `.ios` uzantılı dosya, Android paketine `.android` uzantılı dosya girer. Bu yöntemin yararı, platforma özgü kodun ayrı dosyalarda durması ve ortak dosyaların koşullarla dolmamasıdır. Bedeli ise iki dosyanın birbirinden sapabilmesidir: biri güncellenir, öbürü unutulursa iki platform sessizce farklılaşır. Ağaç bu sapmayı, iki platformun ağacını karşılaştırdığımızda gösterir.

#### Aynı Yerde, Farklı Tip

Platforma özgü ayrışma yalnız fazladan bir düğümden ibaret değildir. Aynı konumdaki düğüm iki platformda farklı tipte olabilir: iOS'ta bir anahtar düğmesi, Android'de bir onay kutusu. Bu durumda karşılaştırma, bir önceki dersin tip değişimini bu kez platformlar arasında yeniden görür.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderChoice } from "./settings.mjs";

const ops = reconcile(renderChoice("ios"), renderChoice("android"));
printOps(ops);
countFates(ops);
```

```text
korunan: /Row
korunan: /Row/Text
sokulen: /Row/Switch
kurulan: /Row/Checkbox
korunan: 2
guncellenen: 0
kurulan: 1
sokulen: 1
komut: 2
```

Kapsayıcı ve etiket korunan, anahtar düğmesi sökülen, onay kutusu kurulan çıktı; komut iki. Dört düğümde ikisi ortak. Önceki ölçümde platforma özgü pay tek bir düğümdü, burada iki düğümdür: aynı konum, ama bir tip yerine başka bir tip. Aynı ekranın iki platformda "aynı" olması, ağaçta düğümlerin aynı olması anlamına gelmez. Tek kod tabanı aynı ekranı garanti etmez; garanti ettiği, ekranın iki platformda aynı kaynaktan üretilmesidir.

Burada bir yanılgıyı ayıklayalım: "Ortak kod yazdım, o halde iki platform aynı ekranı gösteriyor." Ortak kod iki ağaç üretebilir ve ağaçlar farklıysa yerel taraf farklı görünümler kurar. Ortak payı artırmak isteyen kişi bu karşılaştırmayı bir kontrol aracı olarak kullanabilir: ayrışan her düğüm ya bilerek bırakılmış bir platform farkıdır ya da farkında olunmadan gelmiş bir sapmadır. Ağaç hangisinin hangisi olduğunu söylemez; yalnız nerede ayrıldığını gösterir.

#### Sınırlayıcı Ölçüm

Odak düğüm `/Settings/Toggle`'dır. Son ölçüm, ayrışmanın tersini de sınar: Android ağacından iOS ağacına bakıldığında anahtar düğmesi yok olur.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("android"), renderSettings("ios"));
printOps(ops);
countFates(ops);
```

```text
korunan: /Settings
korunan: /Settings/Text
korunan: /Settings/Text
sokulen: /Settings/Toggle
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 1
komut: 1
```

Yön değişince akıbet de değişti: Android'den iOS'a bakınca anahtar düğmesi sökülen, iOS'tan Android'e bakınca kurulan çıktı. Ortak üç düğüm iki yönde de korunan. Karşılaştırmanın bir yönü vardır ve akıbet o yöne bağlıdır; ama hangi yönden bakılırsa bakılsın, ortak ve ayrışan payın büyüklüğü aynıdır.

Bu dersin akıbeti kurulandır, ama sınırı baştan yazılmalıdır: bu kurulan, çalışma anında olan bir güncellemeden gelmedi. Bir ağacın öbürüyle karşılaştırılmasından geldi. Platforma özgü koda bakarken kullandığımız ölçü bu: iki ağaç kaç düğümde ortak, kaç düğümde ayrışıyor.

Bu, M32/K01'in sorusundan farklıdır. O derste paylaşımı ölçtük: kodun ne kadarı iki platformda ortak. Burada paylaşım ölçülmedi; ölçülen, iki platformun ağacındaki bir düğümün karşılaştırmada ne akıbet aldığıdır. Ortak olan bir düğümün de platformda aynı yerel görünüme karşılık geldiğini söylemiyoruz.

**TY11.** Model, ortak tipleri iki platformda aynı sayar. Gerçekte aynı tip, platforma göre farklı yerel görünüm üretir; ağaçta tip adı aynıdır ve fark görünmez.

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

Son bir ölçüt olarak iki ölçümü yan yana koyalım. Ayarlar ekranında dört düğümün üçü ortak, biri ayrışıyordu. Seçim satırında dört düğümün ikisi ortak, ikisi ayrışıyordu. İlk durumda ayrışma ekrana bir şey ekliyor, ikincisinde bir şeyi değiştiriyor. Bir ekranın ortak payını artırmak isteyen kişi önce hangi tür ayrışmanın olduğuna bakmalıdır; çünkü eklemeyi koşullu bir düğümle, değiştirmeyi ise yalnız o düğümün tipini seçerek çözmek mümkündür ve ikisi de kapsayıcıya ve kardeşlere dokunmaz.

Bu ölçümlerin sınırını da belirtelim. Karşılaştırdığımız ağaçlar yalnız tipleri ve özellikleri taşır; iki platformun yerel görünümlerinin ekranda nasıl göründüğü, hangi yazı tipini kullandığı ya da kenar boşluklarını nasıl hesapladığı ağaçta yoktur. Bu yüzden "ortak pay yüksek" sonucu, ekranların birbirine benzediği anlamına gelmez; yalnız tariflerin benzediği anlamına gelir. Görünümlerin benzemesi için ayrıca gözle bakmak gerekir, ve bu kursun ölçüsü onu kapsamaz.

Son olarak, bu dersin neden çalışma anı güncellemesi olmadığını yeniden vurgulayalım, çünkü ölçüm çıktısındaki `kurulan` sözcüğü yanıltabilir. Uygulama yüklendiği platformda baştan sona aynı ağacı üretir. Platform değişmediği için `renderSettings("android")` ile `renderSettings("ios")` bir uygulamanın yaşamında hiçbir zaman art arda çağrılmaz. Çalışma anında gerçekleşen güncellemeler, platform içinde, durum değiştikçe olur ve onları önceki derslerin ölçümleri gösterdi. Bu derste iki ağacı yan yana koymak, ayrışmayı görünür kılmak için kullanılan bir araçtır.

Ortak kodun payını sayarken küçük bir tuzak vardır: sayı, ağacın boyutuna bağlıdır. Dört düğümlü bir ekranda bir ayrışan düğüm büyük bir pay gibi görünür; yüz düğümlü bir ekranda aynı tek düğüm göze çarpmaz. Bu yüzden sayıları ekranlar arasında karşılaştırmadan önce düğüm sayılarına bakmak gerekir. Bu derste her sayı yalnız o ölçümün iki ağacı için geçerlidir ve genellenmez. Genelleme yapmak isteyen kişi aynı ölçümü kendi ekranlarında yeniden kurar.

Bir kontrol listesi de işe yarar. Ayrışma eklemeden önce şunlar sorulur: fark gerçekten platformdan mı geliyor, yoksa tasarım kararsızlığından mı? Ayrışan düğüm en küçük düğüm mü? Ayrışmanın iki ağaçtaki karşılığı ölçülüp ortak pay kaydedildi mi? Üç sorunun cevabı açıksa ayrışma bilerek yapılmış sayılır.

#### Özet

- Bu dersin akıbeti: kurulan. İkinci ölçümde yalnız Android'de bulunan `/Settings/Toggle` iOS ağacından Android ağacına bakılınca kurulan çıktı; bu bir çalışma anı güncellemesi değil, iki ağacın karşılaştırmasıdır.
- Platforma göre ayrışan tarif iki ayrı ağaç üretir; uygulama yalnız birini görür.
- Ortak kodun payı ortak düğümlerle, platforma özgü payı ayrışan düğümlerle sayılır: dört düğümde üç ortak, bir ayrışan.
- Karşılaştırmanın yönü akıbeti belirler: aynı düğüm bir yönde kurulan, öbür yönde sökülen çıkar.
- Aynı tip adı iki platformda aynı yerel görünümü garanti etmez; ağaç bu farkı göstermez.

#### Sonraki Adım

Çekirdek yapı burada tamamlanır: ağaç, bileşenler, biçem, dokunma ve platform farkı. Sıradaki konu, `Gezinme`, ekranlar arasında geçişin ağaca ne yaptığını ele alır: bir ekran gidip öbürü gelince hangi düğümler sökülür ve kurulur.

## Uygulama Katmanları

Gezinme, yerel ve paylaşılan durum, ağ ve çevrimdışı davranış, yerel ve güvenli depolama, bildirimler ve derin bağlantı, hareketler ve animasyonlar.

### Gezinme

Source: https://academia.sh/tr/kurslar/react-native/gezinme

Gezinme, uygulamanın ekranlarını yığın, sekme ya da çekmece olarak düzenler. Ders yeni bir rota itilince ve geri dönülünce görünüm ağacındaki düğümlerin akıbetini ölçer: alttaki ekran korunan kalır.

Önceki ders, tek platformda doğan bir anahtarın ortak kodun içinde nasıl durduğunu ele aldı. Bu dersten itibaren kurs tek ekranlı örneklerden çıkar ve uygulamanın katmanlarına geçer. İlk katman gezinmedir: kullanıcı bir ekrandan ötekine nasıl geçer ve geçerken eski ekrana ne olur.

Kursun sorusu ders boyunca değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtın dört biçimi var. Düğüm aynı kalır ve özellikleri de aynıysa **korunan**dır. Düğüm aynı kalır ama bir özelliği değişirse **güncellenen**dir. Düğüm ağaçta ilk kez belirirse **kurulan**, ağaçtan çıkarsa **sökülen**dir. Güncellenen, kurulan ve sökülen düğümlerin toplamına komut denir: çerçevenin yerel tarafa göndermesi gereken iş budur. Akıbet elle atanmaz. Aşağıdaki çekirdek iki ağacı karşılaştırır ve her düğümün akıbetini kural verir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Kural, alt ağaç yalnız sonraki ağaçtaysa kurulan, yalnız öncekindeyse sökülen der. Tip ya da anahtar farklıysa eski düğümün tamamı sökülen, yenisinin tamamı kurulandır. Aksi hâlde özellikleri karşılaştırır; çocukları anahtarla ya da sırayla eşler. Model bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.

#### Yığın: Rota İtmek ve Geri Dönmek

Gezinmenin en yaygın biçimi yığındır. Her ekran bir **rota**ya karşılık gelir: rota ekranın adıdır. Açık rotaların sıralı listesine **geri yığını** denir. Kullanıcı bir satıra dokununca yeni rota yığının sonuna itilir; geri düğmesine basınca son rota çıkarılır. Yığın gezinme kitaplığı bu listeyi tutar ve her rota için bir ekran kurar. Uygulama yazarının işi, rotaları ve hangi ekrana gideceklerini tanımlamaktır.

```jsx
// Platform kodu: RootStack
function RootStack() {
  return (
    <Stack>
      <Screen name="liste" component={ListScreen} />
      <Screen name="detay" component={DetailScreen} />
    </Stack>
  );
}
```

Bu parça ölçülmez; model kitaplığı çalıştırmaz. Modelde yığın, rota adlarının listesidir ve `renderStack` her rotayı, rota adıyla anahtarlanmış bir `Screen` düğümüne çevirir. Anahtar önemlidir: aynı adlı rota iki ağaçta aynı ekran sayılır.

**UK1.** Model, yığındaki her rotanın ekranını görünüm ağacında tutar; üstte kalan rota alttakini örtse de alttaki ekranın düğümü ağaçtan çıkmaz. Gerçek kitaplığın bu seçimi nasıl yaptığı kitaplığa bağlıdır; model bir seçim yapar ve bunu açıkça söyler.

```javascript
// stack.mjs — gezinme yiginin rotalarindan gorunum agaci kurar
import { el } from "./fate.mjs";

export function renderStack(routes) {
  return el("Stack", {}, routes.map((name) =>
    el("Screen", { baslik: name }, [el("Text", { metin: name })], name)));
}
```

Önce liste ekranındayız ve bir satıra dokunuyoruz. Yığın `liste` rotasından `liste`, `detay` rotalarına geçer.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";

const ops = reconcile(renderStack(["liste"]), renderStack(["liste", "detay"]));
printOps(ops);
countFates(ops);
```

```text
korunan: /Stack
korunan: /Stack/Screen[liste]
korunan: /Stack/Screen[liste]/Text
kurulan: /Stack/Screen[detay]
kurulan: /Stack/Screen[detay]/Text
korunan: 3
guncellenen: 0
kurulan: 2
sokulen: 0
komut: 2
```

Yeni rota itildiğinde `detay` ekranı ve içindeki metin kurulandır: iki düğüm. Alttaki `/Stack/Screen[liste]` korunandır, çünkü özellikleri değişmedi ve anahtarı aynı kaldı. Komut sayısı yalnız yeni ekranın düğümlerini sayar. Kural uygulamanın ne kadar ekranı olduğuna değil, bu güncellemede hangi düğümün değiştiğine bakar.

Geri dönüş aynı kuralın tersidir. Üç ekranlı bir yığından bir rota çıkarılır; odak satırı, yani dönülen ekran, ayrıca yazdırılır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";

const ops = reconcile(
  renderStack(["liste", "detay", "not"]),
  renderStack(["liste", "detay"]),
);
printOps(ops.filter((op) => op.path === "/Stack/Screen[detay]"));
countFates(ops);
```

```text
korunan: /Stack/Screen[detay]
korunan: 5
guncellenen: 0
kurulan: 0
sokulen: 2
komut: 2
```

Geri dönülen `detay` ekranı korunandır, çıkarılan `not` ekranı ve metni sökülendir. Sökülen bir ekranın durumu, yani üzerinde yazılmış bir giriş ya da kaydırma konumu, düğümle birlikte yok olur. Geri dönüp aynı rotaya yeniden gidildiğinde ekran yeniden kurulur. Bu yüzden ekran kapanırken kaybolmaması gereken bilgi ekranın dışında, sonraki derslerde ele alınan katmanlarda durur.

#### Rotayı Değiştirmek

Gezinmede bir de değiştirme vardır: yığının son rotası çıkarılır ve yerine başka bir rota konur. Bir ürün sayfasından başka bir ürün sayfasına geçmek bunun örneğidir. Ekranın kodu aynıdır, yalnız hangi ürünü gösterdiği değişir. Hangi ürünün gösterildiği rotaya parametre olarak girer.

**UK3.** Model rota adını ve parametresini tek anahtarda birleştirir: `urun-1` ve `urun-2` iki ayrı rotadır. Gerçek kitaplıklar anahtarı çoğunlukla rota adından ve kimliğinden kurar; model bu birleşimi tek dizgeye indirir.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderStack } from "./stack.mjs";

printOps(reconcile(renderStack(["liste", "urun-1"]), renderStack(["liste", "urun-2"])));
```

```text
korunan: /Stack
korunan: /Stack/Screen[liste]
korunan: /Stack/Screen[liste]/Text
kurulan: /Stack/Screen[urun-2]
kurulan: /Stack/Screen[urun-2]/Text
sokulen: /Stack/Screen[urun-1]
sokulen: /Stack/Screen[urun-1]/Text
```

Anahtar farklı olduğu için `urun-1` ekranı metniyle birlikte sökülür, `urun-2` ekranı metniyle birlikte kurulur; `liste` korunandır. Ekran tipi aynı olsa da kural düğümleri anahtarla eşler, bu yüzden iki ürün sayfası aynı düğüm sayılmaz. Sonuç kullanıcıya şöyle yansır: birinci ürün sayfasında kaydırılmış konum ya da yazılmış bir not ikinci ürüne taşınmaz. Taşınması isteniyorsa ekranın kimliği rota adı olur ve ürün bilgisi özellik olarak girer; o durumda aynı düğüm güncellenen olurdu. Hangisinin doğru olduğu uygulama özelliğine bağlıdır ve kural bu kararı vermez.

#### Sekme ve Çekmece

Sekme gezinmesinde rotalar bir yığın oluşturmaz, kardeştir: kullanıcı bir sekme çubuğundan birini seçer ve yalnız o sekmenin ekranı görünür. Çekmece gezinmesi aynı fikrin kenardan açılan biçimidir; rotalar kardeştir, seçimi çubuk yerine bir menü yapar. Üçünün gezinme olarak farkı geri yığınındadır; görünüm ağacı açısından asıl soru başka: görünmeyen sekmenin ekranı ağaçta kalıyor mu?

**UK2.** Bu soruyu model iki seçenekle ölçer. Birinde görünmeyen sekmeler ağaçta durur ve yalnız görünürlük özelliği değişir. Öteki seçenekte yalnız seçili sekme ağaçtadır. Gerçek kitaplıklar bu iki davranıştan birini varsayılan sunar ve çoğu ayar olarak değiştirmeye izin verir; model hangisinin varsayılan olduğunu söylemez.

```javascript
// tabs.mjs — sekme cubugunun gorunum agacini kurar; saklayan secenek gizli sekmeyi agacta tutar
import { el } from "./fate.mjs";

export function renderTabs(active, names, keepHidden) {
  const shown = keepHidden ? names : names.filter((name) => name === active);
  return el("Tabs", {}, shown.map((name) =>
    el("Screen", { gorunen: name === active }, [el("Text", { metin: name })], name)));
}
```

Kullanıcı `ana` sekmesinden `ara` sekmesine geçiyor. İki seçenek aynı geçişi yaşar ve yalnız akıbet sayıları karşılaştırılır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderTabs } from "./tabs.mjs";

const names = ["ana", "ara", "profil"];
console.log("saklayan:");
countFates(reconcile(renderTabs("ana", names, true), renderTabs("ara", names, true)));
console.log("saklamayan:");
countFates(reconcile(renderTabs("ana", names, false), renderTabs("ara", names, false)));
```

```text
saklayan:
korunan: 5
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
saklamayan:
korunan: 1
guncellenen: 0
kurulan: 2
sokulen: 2
komut: 4
```

Saklayan seçenekte iki ekran güncellenendir: biri görünür olur, öteki görünmez. Metin düğümleri ve üçüncü sekme korunandır. Saklamayan seçenekte `ana` ekranı sökülür, `ara` ekranı kurulur; her biri metniyle birlikte iki düğümdür. Aynı geçiş, iki seçenekte iki ayrı akıbet dağılımı verir. Saklayan seçenek sekmeye dönüldüğünde durumu korur ama ağaçta görünmeyen ekranların maliyetini taşır. Saklamayan seçenek ağacı küçük tutar ve geri dönüşte ekranı baştan kurar.

#### Kaynak Parçayı Satır Satır Okumak

Yukarıdaki yığın tanımı kısadır ama dört ayrı karar taşır. İlk karar, `Stack` kapsayıcısının kendisidir: ekranlar bir yığının çocuğu olarak tanımlanır, yani aralarındaki ilişki sıradır. İkinci karar, her `Screen` satırındaki `name` değeridir. Bu ad rotanın kimliğidir; ağaçta anahtara dönüşen şey de odur. Üçüncü karar, `component` satırıdır: rota yalnızca bir ad değil, o adla açılacak ekranın tanımıdır. Dördüncü karar, tanımların sırasıdır: ilk tanımlanan rota başlangıç ekranı olur, ötekilere ancak bir geçişle varılır.

Bu satırlardan hiçbiri bir düğüm kurmaz. Tanım, ekranların hangi adla var olabileceğini söyler; hangisinin şu an açık olduğunu söylemez. Açık olanlar yığının çalışma anındaki içeriğidir: rota listesi. Ağaç bu listeden türer ve kural, liste değişince ağacın ne yaptığını sınıflar. Tanımı değiştirmek ile yığını değiştirmek bu yüzden farklı işlerdir: ilki uygulamanın kodunu, ikincisi kullanıcının o anki yolunu etkiler.

#### Yığın Derinleştikçe

Kullanıcı bir listeden ayrıntıya, ayrıntıdan bir alt ayrıntıya iner. Her adımda yığına bir rota eklenir. Bu ölçümde itme alttaki ekranları korunan bıraktı ve yalnız yeni ekranın düğümlerini kurdu. Bu, gezinmeyi ucuz gösterir: bir adımın maliyeti, yeni ekranın büyüklüğüyle sınırlıdır, yığının uzunluğuyla değil.

Ama aynı sonucun bir bedeli vardır. Alttaki ekranlar ağaçta durduğu için her biri düğümlerini, durumunu ve yerel görünümünü taşımaya devam eder. Yığın derinleştikçe ağaç büyür. Kullanıcının geri dönüşte kaldığı yeri bulması bu seçimin kazancıdır; ağacın büyümesi bedelidir. Bir uygulama çok derin gezinmeye izin veriyorsa kazancın bedelini bilerek ödemesi gerekir. Gezinmeyi tasarlarken sorulacak soru "geri dönülebilir mi?" kadar "geri dönüldüğünde hangi durum duruyor olmalı?" sorusudur.

Rotayı değiştirmek bu dengeyi değiştirir. Değiştirme ölçümünde eski ekran söküldü, yenisi kuruldu ve eski ekranın durumu yok oldu. Kullanıcı bir ürünün ayrıntısından başka bir ürünün ayrıntısına geçiyorsa bu çoğu zaman istenen davranıştır: iki ürünün kaydırma konumunu paylaşması anlamsızdır. Aynı davranış, kullanıcı aynı ürüne dönmek istediğinde bir kayıptır. Hangi davranışın doğru olduğunu akıbet söylemez; akıbet yalnız hangisinin olduğunu söyler.

#### Sınırlayıcı Ölçüm

Yığında alttaki ekranın korunan çıkması gezinmenin kendisinden değil, modelin bir varsayımından gelir: UK1, alttaki ekranın özelliğine örtüldüğünü yazmıyor. Bir kitaplık alttaki ekrana `ustte` gibi bir özellik taşısaydı, aynı itme başka bir akıbet verirdi. Aşağıda aynı itme iki ağaç kuruluşuyla ölçülür.

```javascript
// main.mjs
import { el, reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";

function renderMarked(routes) {
  return el("Stack", {}, routes.map((name, i) =>
    el("Screen", { ustte: i === routes.length - 1 }, [el("Text", { metin: name })], name)));
}

const focus = (op) => op.path === "/Stack/Screen[liste]";
const plain = reconcile(renderStack(["liste"]), renderStack(["liste", "detay"]));
const marked = reconcile(renderMarked(["liste"]), renderMarked(["liste", "detay"]));
console.log("isaretsiz:");
printOps(plain.filter(focus));
console.log("isaretli:");
printOps(marked.filter(focus));
countFates(marked);
```

```text
isaretsiz:
korunan: /Stack/Screen[liste]
isaretli:
guncellenen: /Stack/Screen[liste]
korunan: 2
guncellenen: 1
kurulan: 2
sokulen: 0
komut: 3
```

Aynı itme, ağacın kurulum biçimine göre `/Stack/Screen[liste]` düğümünü korunan ya da güncellenen yaptı. Kural ekranın örtüldüğünü görmez; yalnız ağaçtaki özelliğin değiştiğini görür. Örtülme bilgisi ağaca özellik olarak yazılıyorsa her itme alttaki ekran için bir komut doğurur. Bu yüzden kitaplığın örtülmeyi nasıl bildirdiği, gezinme maliyetinin bir parçasıdır ve kural bu kararı vermez; ders bunu düzyazıda söyler.

#### Yaygın Yanılgılar

- "Geri dönmek önceki ekranı yeniden kurar." Geri dönme ölçümünde önceki ekranın düğümleri korunan çıktı; sökülen yalnızca çıkarılan rotanın ekranıydı. Yeniden kurulan bir şey olmadığı için kaydırma konumu da yerinde kalır.
- "Yığındaki her ekran ağaçtan çıkarılabilir, kullanıcı fark etmez." Alttaki ekran ağaçtan çıkarılırsa durumu da gider. Kullanıcı geri döndüğünde ekran sıfırdan kurulur ve yarım kalan iş kaybolur.
- "Rota adı yeterli kimliktir." Aynı ad farklı parametrelerle iki kez açılabiliyorsa ad tek başına anahtar olamaz; model bu yüzden adı ve parametreyi birlikte anahtar yapar. Anahtar eşleşmezse aynı ekran farklı bir ekran sayılır ve sökülüp yeniden kurulur.
- "Sekme ve yığın aynı geçiştir." Yığında yeni rota eklenir ve geri dönüş yolu oluşur. Sekmede rotalar kardeştir ve aralarındaki geçiş, yukarıdaki iki seçenekten hangisi kurulduysa onun akıbetini üretir.

#### Özet

- Bu dersin akıbeti: korunan. Yeni rota itilince `/Stack/Screen[liste]` düğümü, özellikleri ve anahtarı değişmediği için korunan çıktı.
- Rota itmek yeni ekranın düğümlerini kurar, geri dönmek çıkarılan ekranın düğümlerini sökerek bu ekranın durumunu da yok eder.
- Sekme geçişi, görünmeyen sekmenin ağaçta kalıp kalmamasına göre güncellenen ya da sökülen ile kurulan üretir.
- Alttaki ekranın korunan olması, örtülmenin ağaca özellik olarak yazılmadığı varsayımına bağlıdır; yazılırsa aynı düğüm güncellenen olur.
- Kural gezinme kitaplığının iç tutumunu görmez; yalnız iki ağacı karşılaştırır.

#### Sonraki Adım

Bu derste gezinme ekranların hangi düğümlerde kurulup söküldüğünü belirledi. Ekranlar arasında paylaşılan bilgi ise ayrı bir sorudur: bir sepet sayısı bir ekranda değişince başka bir ekrandaki rozet de değişmelidir. Sıradaki ders, `Durum Yönetimi`, tek bir değişikliğin hangi düğümü güncellediğini, hangisini korunan bıraktığını ölçer.

### Durum Yönetimi

Source: https://academia.sh/tr/kurslar/react-native/durum-yonetimi

Durum yönetimi, ekranın zamanla değişen verisinin nerede tutulacağını belirler: tek bileşende yerel ya da birçok ekranın okuduğu paylaşılan. Ders tek bir durum değişikliğinin sepet rozetini güncellediğini, ürün listesini korunan bıraktığını ölçer.

Gezinme ekranların ağaçta ne zaman kurulup söküldüğünü belirledi. Ekranın içindeki veri ise zamanla değişir: sepetteki ürün sayısı, bir giriş alanındaki metin, bir bölümün açık ya da kapalı olması. Bu verinin adı durumdur ve durum yönetimi, durumun nerede tutulacağına ve kimin okuyacağına karar vermektir.

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural ağaçlara bakar, durumun nerede durduğunu görmez.

#### Yerel Durum

Yerel durum tek bir bileşenin kendi içinde tuttuğu veridir. Sepetteki her satırın adet sayacı bunun örneğidir: satırın dışında kimse bu sayıyı okumaz. Çerçevede yerel durum bir durum kancasıyla tutulur ve kanca, durum değişince o bileşenin yeniden çizilmesini ister. Yeniden çizim (render), bileşenin yeni görünüm ağacını üretmesidir; ağaç üretmek tek başına komut sayılmaz.

```jsx
// Platform kodu: QuantityRow
function QuantityRow({ name }) {
  const [qty, setQty] = useState(1);
  return (
    <Pressable onPress={() => setQty(qty + 1)}>
      <Text>{name}: {qty}</Text>
    </Pressable>
  );
}
```

Bu parça ölçülmez. Modelde durum, ağacı kuran işlevin girdisidir: işlev durumdan ağaç üretir, kural önceki ve sonraki ağacı karşılaştırır.

**UK1.** Model, durumdan ağaca giden işlevlerin saf olduğunu varsayar: aynı durum aynı ağacı verir. Gerçek bileşenlerde kancalar ve yan etkiler de vardır, ama bu derste ölçülen yalnız ağaçtır.

İki satırlı bir liste kuruyoruz. Her satırın adedi kendi durumudur ve `kalem` satırının adedi 1'den 2'ye çıkıyor.

```javascript
// main.mjs
import { el, reconcile, printOps, countFates } from "./fate.mjs";

function renderRows(counts) {
  return el("List", {}, Object.keys(counts).sort().map((name) =>
    el("Item", { ad: name, adet: counts[name] }, [], name)));
}

const ops = reconcile(
  renderRows({ kalem: 1, defter: 1 }),
  renderRows({ kalem: 2, defter: 1 }),
);
printOps(ops);
countFates(ops);
```

```text
korunan: /List
korunan: /List/Item[defter]
guncellenen: /List/Item[kalem]
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Yerel durum değişince yalnız sahibi güncellenir. `Item[kalem]` güncellenendir, çünkü `adet` özelliği değişti; `Item[defter]` ve liste korunandır. Komut sayısı 1'dir. Durumun yerel kalması, değişikliğin etkisini de yerelde tutar.

#### Paylaşılan Durum ve Durumun Yukarı Taşınması

Sepet sayısını yalnız bir satır okumaz. Üst çubuktaki rozet, alt çubuktaki toplam ve ödeme düğmesi aynı sayıya bakar. Bu veri artık tek bir bileşenin içinde duramaz, çünkü kardeş bileşenler birbirinin durumunu okuyamaz. Çözüm durumu, onu okuyan bileşenlerin ortak atasına taşımaktır: bu işleme **durumun yukarı taşınması** denir. Ata durumu tutar ve aşağıya özellik olarak verir.

Atanın altındaki bileşenler arasında derin bir zincir varsa özelliği her katmandan geçirmek ağırlaşır. O zaman paylaşılan durum bir **sağlayıcı**ya konur: ağacın üstüne yerleştirilen bu bileşen, altındaki her bileşenin durumu doğrudan okumasına izin verir. Paylaşılan durum kitaplıkları bu düzeneği hazır sunar; okuyan bileşen yalnız bir kanca çağırır.

```jsx
// Platform kodu: CartBadge
function CartBadge() {
  const count = useCartCount();
  return <Text>{count}</Text>;
}
```

**UK2.** Model, sağlayıcıyı görünüm ağacında düğüm olarak göstermez; yalnız sayıyı okuyan düğümleri gösterir. Sepet sayısı paylaşılan durumdadır ve `renderCart(state)` ağacı bu durumdan kurar.

```javascript
// cart.mjs — sepet ekraninin gorunum agacini paylasilan durumdan kurar
import { el } from "./fate.mjs";

export function renderCart(state) {
  return el("Screen", {}, [
    el("Header", {}, [
      el("Title", { metin: "Sepet" }),
      el("Badge", { sayi: state.sayi }),
    ]),
    el("List", {}, state.urunler.map((ad) => el("Item", { ad }, [], ad))),
  ]);
}
```

Kullanıcı sepetteki bir ürünü bir kez daha ekliyor. Sepet sayısı 2'den 3'e çıkıyor, ürünlerin listesi değişmiyor.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderCart } from "./cart.mjs";

const urunler = ["kalem", "defter"];
const ops = reconcile(
  renderCart({ sayi: 2, urunler }),
  renderCart({ sayi: 3, urunler }),
);
printOps(ops);
countFates(ops);
```

```text
korunan: /Screen
korunan: /Screen/Header
korunan: /Screen/Header/Title
guncellenen: /Screen/Header/Badge
korunan: /Screen/List
korunan: /Screen/List/Item[kalem]
korunan: /Screen/List/Item[defter]
korunan: 6
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Tek bir durum değişikliği yapıldı ve iki düğüm farklı akıbet aldı. `/Screen/Header/Badge` güncellenendir, çünkü rozetin `sayi` özelliği değişti. Ürün satırları ve liste korunandır, çünkü onların özellikleri aynı kaldı. M30/K03'te bir yeniden hesaplamayı neyin tetiklediği sorulurdu. Bu kurs nedeni sormaz, etkiyi sınıflar: neden değil etki; aynı tetik bir düğümü günceller, ötekini korur.

Paylaşımın bedeli de bu ölçümde görünür. Bir durumu okuyan düğüm sayısı arttıkça aynı değişiklik daha çok komut doğurur. Bunu iki kurulumda karşılaştırıyoruz. Birinde rozet kendi sayacını tutar, alttaki toplam başka bir sayıyı gösterir. Ötekinde ikisi aynı paylaşılan durumu okur.

```javascript
// main.mjs
import { el, reconcile, countFates } from "./fate.mjs";

function renderPair(badge, total) {
  return el("Screen", {}, [
    el("Header", {}, [el("Badge", { sayi: badge })]),
    el("Footer", {}, [el("Total", { sayi: total })]),
  ]);
}

console.log("yerel:");
countFates(reconcile(renderPair(2, 2), renderPair(3, 2)));
console.log("paylasilan:");
countFates(reconcile(renderPair(2, 2), renderPair(3, 3)));
```

```text
yerel:
korunan: 4
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
paylasilan:
korunan: 3
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
```

Yerel kurulumda değişiklik tek düğümü günceller: komut 1. Paylaşılan kurulumda iki düğüm güncellenir: komut 2. Yerel kurulumda ekranın iki yeri farklı sayı gösterir: rozet 3, toplam 2. Paylaşılan kurulumda ikisi de 3 gösterir ve bunun bedeli bir komuttur. Hangi kurulumun doğru olduğu komut sayısından değil, ekranın tutarlı olmasından gelir. Kural bu yargıyı vermez; komutun bedelini söyler.

#### Listeye Bir Öge Eklemek

Şimdiye dek durum değişikliği var olan bir özelliği değiştirdi. Çoğu gerçek değişiklik ise sayıyı değiştirmekle kalmaz, listeye bir öge de ekler. Sepete bir ürün eklenince iki şey olur: rozetin sayısı artar ve listede yeni bir satır belirir. İki etki aynı durumdan gelir ama ağaçta farklı akıbet alır; bunu ölçelim.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderCart } from "./cart.mjs";

const ops = reconcile(
  renderCart({ sayi: 2, urunler: ["kalem", "defter"] }),
  renderCart({ sayi: 3, urunler: ["kalem", "defter", "silgi"] }),
);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
guncellenen: /Screen/Header/Badge
kurulan: /Screen/List/Item[silgi]
korunan: 6
guncellenen: 1
kurulan: 1
sokulen: 0
komut: 2
```

Rozet önceki ölçümlerdeki gibi güncellenendir. Yeni satır ise güncellenen değil kurulandır: önceki ağaçta bu anahtarla bir düğüm yoktu, kural da olmayan bir düğümü güncelleyemez. İlk iki ürünün satırları anahtarlarıyla eşleşir ve korunan kalır. Satırların anahtarı ürün adıdır; bu yüzden eklemenin eski satırlara dokunmadığını kural anahtar eşleşmesinden bilir, sıradan değil.

Bu ölçüm bir ayrımı da açıklar. Durum tek bir nesnedir, ama ağaçtaki etkisi parçalıdır: sayıyı gösteren düğüm güncellenir, listeye giren öge kurulur, dokunulmayan ögeler korunur. "Bir durum değişikliği bir komuttur" demek yanlıştır; komut sayısı, değişikliğin kaç düğüme yansıdığına bağlıdır.

#### Karşı Örnek: Yukarı Taşımanın Maliyeti

Durumu yukarı taşımak her zaman iyi bir hamle değildir. Bir satırın adedi yalnız o satırı ilgilendiriyorsa, onu ekranın en üstündeki paylaşılan duruma taşımak kazanç getirmez. Veri artık ekranın tamamının durumudur; değişiklik üstteki durumu değiştirir ve o durumdan beslenen her düğüm karşılaştırmaya girer. Yukarıdaki iki kurulum ölçümü bunu gösterdi: paylaşılan kurulumda tek değişiklik iki komuttu, yerelde bir. Düğüm sayısı arttıkça fark büyür.

Yerelde bırakmanın bedeli ise tutarsızlıktır: aynı sayıyı iki yer gösteriyorsa ikisinin farklı sayı göstermesi bir hatadır. Karar bu yüzden bir kural değil bir tartıdır: veriyi kaç yerin okuduğuna ve bu yerlerin tutarlı olmasının gerekip gerekmediğine bakılır. Tutarlılık gerekiyorsa paylaşılan durumun bir komut fazlası kabul edilir; gerekmiyorsa yerel kalır.

#### Durum Nerede Durmalı

Durumun yeri üç soruyla bulunur. Birincisi, kimin okuduğudur: yalnız bir bileşen okuyorsa yerel kalır. İkincisi, okuyanların ortak atasının nerede olduğudur: iki kardeş okuyorsa durum onların hemen üstündeki atada durur. Üçüncüsü, okuyanların ağacın uzak köşelerinde olup olmadığıdır: öyleyse sağlayıcı araya girer. Durumu gereğinden yukarıda tutmak ilk bakışta kolaylık gibi görünür, ama her değişiklikte o durumu okuyan tüm bileşenler yeniden çizim ister. Yeniden çizim komut değildir; kural ağaçları karşılaştırır ve yalnız farkı komut sayar. Yine de gereksiz yere çok bileşenin yeniden çizilmesi, çizim işinin boşuna artması demektir ve bu iş sonraki derslerde ayrı bir konu olarak ölçülür.

#### Yaygın Yanılgılar

İlk yanılgı, "durum değişince ekran değişir" düşüncesidir. Ekran, yalnız durumdan ağaca giden işlev değişikliği ağaca yazarsa değişir. Sınırlayıcı ölçümde görülecek, ekrana yazılmayan bir alan hiçbir düğümü güncellemez.

İkinci yanılgı, "paylaşılan durum her zaman daha iyidir" düşüncesidir. Paylaşılan durum bir veriyi birçok yerden okunur kılar, ama bedelini de getirir: değişiklik okuyan düğüm sayısı kadar komut doğurur ve verinin kim tarafından değiştirildiğini izlemek zorlaşır. Bir satırın adedi gibi yalnız kendi bileşenini ilgilendiren veri yerelde kalmalıdır.

Üçüncü yanılgı, yerel ile paylaşılanı bir kez seçip bırakmaktır. Bir veri başta yerel olabilir ve ekrana yeni bir okuyan eklenince yukarı taşınır. Taşıma, kod düzeninin değişmesidir; görünüm ağacındaki düğümler aynı kalır, değişen yalnız hangi düğümün hangi durumu okuduğudur.

Dördüncü yanılgı, türetilebilen değeri ayrıca saklamaktır. Rozetin sayısı listenin uzunluğundan hesaplanabilir. Onu ayrı bir durum olarak tutmak, iki değerin birbirinden ayrışma ihtimalini doğurur: liste üç ürün gösterirken rozet iki yazabilir. Türetilebilen değer saklanmaz, her çizimde hesaplanır. Böylece listeye öge eklendiğinde rozet de, ekleme ölçümünde görüldüğü gibi, aynı değişiklikle güncellenir ve ikisinin ayrışması mümkün olmaz.

Beşinci yanılgı, durum ne kadar küçükse o kadar zararsız olduğunu varsaymaktır. Küçük bir durum da çok sayıda düğümden okunabilir. Maliyeti durumun büyüklüğü değil, onu okuyan düğüm sayısı belirler.

Altıncı yanılgı, ölçümün tek bir durum değişikliğini bütün uygulamanın davranışı saymaktır. Burada ölçülen, bir sepet ekranında bir değişikliğin etkisidir. Başka bir ekranda, daha fazla okuyanla ya da daha derin bir ağaçta sayılar değişir; değişmeyen şey, akıbetin sınıflandırılma biçimidir.

#### Sınırlayıcı Ölçüm

Durumun değişmesi ile bir düğümün güncellenmesi aynı olay değildir. Aşağıda sepet durumuna ekranda gösterilmeyen bir alan, `kupon`, ekleniyor. Önce yalnız bu alan değişiyor, sonra sayı değişiyor.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderCart } from "./cart.mjs";

const urunler = ["kalem", "defter"];
const focus = (op) => op.path === "/Screen/Header/Badge";
const first = reconcile(
  renderCart({ sayi: 2, urunler, kupon: "yaz" }),
  renderCart({ sayi: 2, urunler, kupon: "kis" }),
);
const second = reconcile(
  renderCart({ sayi: 2, urunler, kupon: "kis" }),
  renderCart({ sayi: 3, urunler, kupon: "kis" }),
);
console.log("kupon degisti:");
countFates(first);
console.log("sayi degisti:");
printOps(second.filter(focus));
```

```text
kupon degisti:
korunan: 7
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
sayi degisti:
guncellenen: /Screen/Header/Badge
```

Kupon değişti, ama `renderCart` bu alanı ağaca yazmıyor; iki ağaç aynıdır ve komut 0'dır. Durum değişti, ağaç değişmedi. Sayı değişince `/Screen/Header/Badge` güncellenendir. Kural durumun yerini ya da ne kadar değiştiğini görmez; yalnız ağaçta görünen özelliklerin karşılaştırmasını görür. Bu yüzden durum yönetimi tek başına başarım ölçüsü değildir: durumun çok güncellenmesi, çok komut doğurması anlamına gelmez.

#### Özet

- Bu dersin akıbeti: güncellenen. Sepet sayısı değişince `/Screen/Header/Badge` düğümü, `sayi` özelliği değiştiği için güncellenen çıktı.
- Aynı durum değişikliği rozeti güncelledi, ürün listesini korunan bıraktı: aynı tetik iki düğüme iki ayrı akıbet verdi.
- Yerel durum değişikliğin etkisini sahibi olan düğümde tutar; paylaşılan durum okuyan düğüm sayısı kadar komut doğurur.
- Kupon gibi ağaca yazılmayan bir durum değişince hiçbir düğüm güncellenmez: durum değişikliği ile komut aynı şey değildir.
- Kural durumun yerini görmez; yalnız önceki ve sonraki ağacı karşılaştırır.

#### Sonraki Adım

Bu derste durum uygulamanın içinde değişti. Uygulamanın en önemli veri kaynağı ise dışarıdadır: sunucu. Sıradaki ders, `Ağ ve Veri`, bir istek yanıt verince yükleniyor göstergesinin ve listenin ağaçta ne yaşadığını ölçer.

### Ağ ve Veri

Source: https://academia.sh/tr/kurslar/react-native/ag-ve-veri

Ağ katmanı bir isteği yükleniyor, hata ve veri hâlleri üzerinden ekrana taşır; çevrimdışı önbellek ilk hâli değiştirir. Ders veri gelince yükleniyor göstergesinin sökülen, listenin kurulan olduğunu ölçer.

Önceki ders durumun uygulamanın içinde nasıl tutulduğunu ölçtü. Uygulamanın en önemli veri kaynağı ise dışarıdadır: bir sunucu. Sunucudan gelen veri ekrana hemen varmaz. İstek gider, yanıt gelir ve arada ekranın kullanıcıya bir şey göstermesi gerekir. Bu ders o arayı ve arayı kapatan önbelleği ele alır.

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural ağaçları karşılaştırır, isteğin ağda ne yaptığını görmez.

#### İstek Katmanı ve Üç Hâl

Uygulama ağ isteklerini dağınık yerlerden atmaz. İstek katmanı adresi, başlıkları ve hata işlemeyi tek yerde toplar; ekranlar yalnız "şu adresin verisini ver" der. Bir istek zamana yayılan bir iştir ve sonucu bir söz (`Promise`) olarak döner: ya veriyle çözülür ya da bir hatayla reddedilir. İstek sürerken ekranın üç hâlden birinde olduğu söylenir. **Yükleniyor** hâlinde yanıt henüz gelmemiştir. **Veri** hâlinde yanıt gelmiştir ve liste gösterilir. **Hata** hâlinde istek reddedilmiştir ve ekran kullanıcıya bunu bildirir.

Bu üç hâl ekranda üç farklı düğümdür: bir yükleniyor göstergesi, bir liste ve bir hata iletisi. Çerçevede istek genellikle bir yan etki kancasında başlatılır: ekran ilk kez görününce kanca çalışır, istek gider, yanıt gelince durum değişir ve ekran yeniden çizilir.

```jsx
// Platform kodu: OrderFeed
function OrderFeed() {
  const [status, setStatus] = useState({ phase: "loading", rows: [] });
  useEffect(() => {
    loadOrders().then((rows) => setStatus({ phase: "data", rows }));
  }, []);
  return <Feed status={status} />;
}
```

Bu parça ölçülmez; model çerçeveyi ve ağı çalıştırmaz. Modelde istek, kendi dosyasındaki bir `Promise` zinciridir ve sunucu bellekte tutulan sabit bir tablodur.

**UK1.** Model, gerçek ağı bellekteki bir tabloyla değiştirir: bilinen adres veriyle çözülür, bilinmeyen adres reddedilir. Gecikme ve kopma modele girmez; model yalnız yanıtın hangi hâlle geldiğini ölçer.

```javascript
// request.mjs — istek katmani: bellekteki sahte sunucuya Promise ile istek atar
const SERVER = {
  "/siparisler": [
    { id: "a1", metin: "Kalem" },
    { id: "b2", metin: "Defter" },
  ],
};

export function request(url) {
  return new Promise((resolve, reject) => {
    if (url in SERVER) resolve(SERVER[url]);
    else reject(new Error("bulunamadi"));
  });
}
```

Ekranın ağacını istek hâlinden `renderFeed(status)` kurar. Yükleniyor hâlinde başlığın altında bir gösterge, veri hâlinde bir liste, hata hâlinde bir ileti vardır. Satırlar kimlikle anahtarlanır.

```javascript
// feed.mjs — siparis akisi ekraninin gorunum agacini istek halinden kurar
import { el } from "./fate.mjs";

export function renderFeed(status) {
  let body;
  if (status.durum === "yukleniyor") {
    body = el("Spinner");
  } else if (status.durum === "hata") {
    body = el("Notice", { metin: status.mesaj });
  } else {
    body = el("List", {}, status.satirlar.map((row) =>
      el("Row", { metin: row.metin }, [], row.id)));
  }
  return el("Feed", {}, [el("Title", { metin: "Siparisler" }), body]);
}
```

Önce istek başarılı geçiyor. Ekran yükleniyor hâlinde açılır, yanıt gelince veri hâline döner.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderFeed } from "./feed.mjs";
import { request } from "./request.mjs";

const loading = renderFeed({ durum: "yukleniyor" });
request("/siparisler")
  .then((rows) => renderFeed({ durum: "veri", satirlar: rows }))
  .catch(() => renderFeed({ durum: "hata", mesaj: "yuklenemedi" }))
  .then((next) => {
    const ops = reconcile(loading, next);
    printOps(ops);
    countFates(ops);
  });
```

```text
korunan: /Feed
korunan: /Feed/Title
sokulen: /Feed/Spinner
kurulan: /Feed/List
kurulan: /Feed/List/Row[a1]
kurulan: /Feed/List/Row[b2]
korunan: 2
guncellenen: 0
kurulan: 3
sokulen: 1
komut: 4
```

Veri gelince `/Feed/Spinner` sökülendir. Yerine liste ve iki satırı kurulur. Başlık ve ekranın kendisi korunandır. Komut sayısı 4'tür ve bunun üçü yeni listenin düğümleridir. Gösterge ile liste ağaçta aynı yeri paylaştığı için kural onları aynı düğümün iki hâli saymaz: tipleri farklıdır, biri tamamen çıkar, ötekisi tamamen kurulur.

Hata hâli aynı geçişin başka bir sonucudur. Aynı akışı, sunucunun bilmediği bir adresle yineliyoruz.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderFeed } from "./feed.mjs";
import { request } from "./request.mjs";

const loading = renderFeed({ durum: "yukleniyor" });
request("/yok")
  .then((rows) => renderFeed({ durum: "veri", satirlar: rows }))
  .catch(() => renderFeed({ durum: "hata", mesaj: "yuklenemedi" }))
  .then((next) => {
    const ops = reconcile(loading, next);
    printOps(ops);
    countFates(ops);
  });
```

```text
korunan: /Feed
korunan: /Feed/Title
sokulen: /Feed/Spinner
kurulan: /Feed/Notice
korunan: 2
guncellenen: 0
kurulan: 1
sokulen: 1
komut: 2
```

Yine gösterge sökülendir, ama yerine kurulan tek düğüm hata iletisidir: komut 2. Yükleniyor hâlinden çıkan her yol göstergeyi söker. Hangi düğümün kurulacağını isteğin sonucu belirler.

#### Çevrimdışı Önbellek

Çevrimdışı önbellek, son başarılı yanıtı uygulamanın belleğinde ya da yerel depolamasında tutar. Ağ yoksa ya da yanıt gecikiyorsa ekran son bilinen veriyi gösterir. Bu, ekranın ilk hâlini değiştirir: önbellek varsa ekran yükleniyor göstergesiyle değil, doğrudan listeyle açılır.

```javascript
// cache.mjs — cevrimdisi onbellek: son basarili yaniti bellekte tutar
const store = new Map();

export function saveCache(url, rows) {
  store.set(url, rows);
}

export function readCache(url) {
  return store.has(url) ? store.get(url) : null;
}
```

Ekranın ilk kez açıldığı iki durumu yan yana ölçüyoruz: önbelleksiz açılış ve önbellekli açılış. Ağaç henüz yok, bu yüzden önceki ağaç `null`'dır ve her düğüm kurulandır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderFeed } from "./feed.mjs";
import { saveCache, readCache } from "./cache.mjs";

saveCache("/siparisler", [
  { id: "a1", metin: "Kalem" },
  { id: "b2", metin: "Defter" },
]);
console.log("onbelleksiz:");
countFates(reconcile(null, renderFeed({ durum: "yukleniyor" })));
console.log("onbellekli:");
countFates(reconcile(null, renderFeed({ durum: "veri", satirlar: readCache("/siparisler") })));
```

```text
onbelleksiz:
korunan: 0
guncellenen: 0
kurulan: 3
sokulen: 0
komut: 3
onbellekli:
korunan: 0
guncellenen: 0
kurulan: 5
sokulen: 0
komut: 5
```

Önbellekli açılış daha çok düğüm kurar: üç düğüm yerine beş. Ama ekran hiçbir an gösterge göstermez, dolayısıyla sonradan sökülecek bir gösterge de yoktur. Önbellek açılışta daha çok iş, sonraki geçişte daha az iş demektir: yanıt gelince listenin düğümleri yeniden kurulmaz, olduğu yerde karşılaştırılır.

#### Hata Sonrası Yeniden Deneme

Hata hâli yolun sonu değildir; kullanıcı çoğu zaman "yeniden dene" der. Yeniden deneme ağaç açısından bir geri dönüştür: hata bildirimi kalkar ve yükleniyor göstergesi yeniden belirir. Bu iki düğüm, kökün altında birbirinin yerine geçer.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderFeed } from "./feed.mjs";

const ops = reconcile(
  renderFeed({ durum: "hata", mesaj: "yuklenemedi" }),
  renderFeed({ durum: "yukleniyor" }),
);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
sokulen: /Feed/Notice
kurulan: /Feed/Spinner
korunan: 2
guncellenen: 0
kurulan: 1
sokulen: 1
komut: 2
```

Kök ve başlık korunandır, çünkü her hâlde aynı tiptedir ve aynı özellikleri taşır. Hata bildirimi sökülen, gösterge kurulandır; iki düğüm aynı konumda olsa da tipleri farklı olduğundan kural biri diğerinin güncellenmişi saymaz. Bu, yeniden denemenin ağaçtaki maliyetinin sabit olduğunu gösterir: hata mesajının uzunluğu ya da denemenin kaçıncı olduğu komut sayısını değiştirmez.

Burada ince bir nokta var. Yeniden deneme başarısız olursa ekran yeniden hata hâline döner ve bildirim yeniden kurulur. Kullanıcı art arda başarısız denemelerde göstergenin ve bildirimin sürekli kurulup söküldüğünü görür. Bu bir ağaç sorunu değil, bir tasarım sorusudur: yeniden deneme sürerken eski hata bildirimini tutmak ağacı sakinleştirir ama kullanıcıya sürenin bittiğini söylemez. Kural yalnız hangisinin kaç komut olduğunu söyler; hangisinin daha iyi olduğunu söylemez.

#### Sınırlayıcı Ölçüm

Önbellekten gelen değer yanıt gelene kadar eskimiş olabilir. M30/K04'te bir ekranın modelle uyuşup uyuşmadığı sorulur ve bayat bir ekran bir sonuçtur. Bu derste ölçülen akıbet uyumu ölçmez: bayat bir değer de korunan bir düğümde durabilir. Yeni yanıt eski değerle aynıysa düğüm korunandır, ister değer güncel olsun ister yanıt hiç gelmesin. Aşağıdaki ölçüm iki durumu karşılaştırır. Birinde yeni yanıt önbellekle aynı satırları taşır. Öteki durumda ikinci satırın metni değişmiştir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderFeed } from "./feed.mjs";

const cached = [
  { id: "a1", metin: "Kalem" },
  { id: "b2", metin: "Defter" },
];
const stale = renderFeed({ durum: "veri", satirlar: cached });
const same = renderFeed({ durum: "veri", satirlar: cached.map((row) => ({ ...row })) });
const changed = renderFeed({
  durum: "veri",
  satirlar: [cached[0], { id: "b2", metin: "Defter 2" }],
});

console.log("ayni yanit:");
countFates(reconcile(stale, same));
console.log("degisen yanit:");
printOps(reconcile(stale, changed).filter((op) => op.fate !== "korunan"));
```

```text
ayni yanit:
korunan: 5
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
degisen yanit:
guncellenen: /Feed/List/Row[b2]
```

Aynı yanıtta bütün düğümler korunandır ve komut 0'dır. Değişen yanıtta yalnız `Row[b2]` güncellenendir. İlk durumda kural "hiçbir şey olmadı" der, ama bu önbelleğin güncel olduğu anlamına gelmez: yanıt hiç gelmemiş olsaydı da aynı ağaç aynı biçimde korunan kalırdı. Bayat ile korunan aynı soru değildir. Birincisi değerin sunucudaki gerçeğe uyup uymadığıdır ve ağacın dışındadır. İkincisi güncellemenin düğüme yaptığıdır.

#### Yaygın Yanılgılar

İlk yanılgı, "yükleniyor göstergesi liste gelince gizlenir" düşüncesidir. Ağaç açısından gösterge gizlenmez, ağaçtan çıkar: düğüm sökülür ve yerel görünümü yok olur. Tekrar yükleme istenirse gösterge yeniden kurulur ve eskisinin hiçbir izi kalmaz.

İkinci yanılgı, "önbellek ağ yükünü azalttığı için ekranı da hafifletir" düşüncesidir. Önbellekli açılış, ölçümde görüldüğü gibi, daha çok düğüm kurar. Kazanç göstergenin ve bekleyişin ortadan kalkmasıdır, komut sayısının azalması değil.

Üçüncü yanılgı, hata hâlini bir istisna saymaktır. Hata istek katmanının olağan sonuçlarından biridir ve ağaçta olağan bir düğümdür. Ekranın hata düğümünü kurmayı bilmesi, hata hâlinde boş ekran kalmamasını sağlar.

Dördüncü yanılgı, yeniden denemenin ağaçta ucuz ve görünmez olduğunu varsaymaktır. Yeniden deneme ölçümünde hata bildirimi sökülür ve gösterge kurulur: iki komut. Her başarısız denemede bu iki komut tekrarlanır. Denemeler art arda başarısız oluyorsa ekran, kullanıcının gözünde, hata ile yükleniyor arasında gidip gelir. Ağaç açısından her gidiş geliş aynı iki komuttur; bunun kullanıcıya yorucu gelip gelmediği ise ölçümün dışında kalan bir sorudur.

Beşinci yanılgı, istek ve ekranı tek şey saymaktır. İstek bir söz döndürür ve üç sonuçtan birine varır; ekran ise bu sonucu bir ağaca çevirir. İstek katmanı hata mesajını üretir, ağaç yalnız bu mesajı bir düğümün özelliği olarak taşır. İki katman ayrı kalırsa istek katmanı ekrana dokunmadan test edilebilir ve ekran isteği çalıştırmadan çizilebilir.

Altıncı yanılgı, çevrimdışı önbelleği veri tazeliğinden bağımsız saymaktır. Önbellekteki liste son başarılı yanıttır ve sunucudaki güncel durumdan eski olabilir. Ağaç bunu görmez: önbellekten gelen satırlarla sunucudan gelen satırlar aynı biçimdeyse kural ikisini ayırt etmez. Eskiliği kullanıcıya göstermek istiyorsak bunu ağaca açıkça yazmak, örneğin ayrı bir düğüm eklemek gerekir.

Yedinci yanılgı, üç hâli birbirinden bağımsız ekranlar saymaktır. Yükleniyor, hata ve veri aynı kök düğümün altında gövdeyi değiştirir; kök ve başlık üç hâlde de korunandır. Bu yüzden hâller arası geçişin maliyeti ekranın büyüklüğüyle değil, gövdenin büyüklüğüyle ölçülür.

#### Özet

- Bu dersin akıbeti: sökülen. Veri gelince `/Feed/Spinner` düğümü, yerini listeye bıraktığı için sökülen çıktı; liste ve satırları kurulandır.
- Hata hâlinde de gösterge sökülür, yerine hata iletisi kurulur; yükleniyor hâlinden çıkan her yol göstergeyi söker.
- Önbellekli açılış daha çok düğüm kurar ama gösterge kurmaz; önbelleksiz açılış göstergeyi kurar ve sonradan söker.
- Yeni yanıt önbellekle aynı değeri taşıyorsa satırlar korunandır; yanıtın değişen satırı güncellenendir.
- Akıbet uyumu ölçmez: bayat bir değer korunan bir düğümde durabilir.

#### Sonraki Adım

Bu derste önbellek bellekte durdu ve uygulama kapanınca kayboldu. Kalıcı olması gereken veri için yerel depolama gerekir. Sıradaki ders, `Yerel Depolama`, açılışta depodan okunan bir taslağın ağaçta nasıl belirdiğini ölçer.

### Yerel Depolama

Source: https://academia.sh/tr/kurslar/react-native/yerel-depolama

Yerel depolama, uygulama kapansa da kalması gereken veriyi tutar: küçük değerler için anahtar–değer deposu, sorgulanan kayıtlar için gömülü veritabanı. Ders açılışta depodan okunan taslağın ağaçta kurulan olduğunu, depoya yazmanın ağacı değiştirmediğini ölçer.

Önceki ders sunucudan gelen veriyi ve bellekteki önbelleği ele aldı. Bellek uygulama kapanınca boşalır. Kullanıcının yazdığı bir taslağın, seçtiği bir ayarın ya da indirilmiş bir listenin uygulama yeniden açıldığında da orada olması isteniyorsa veri yerel depolamaya yazılır. Bu ders depolamanın iki türünü ve verinin depodan ağaca nasıl geldiğini ele alır.

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural ağaçları karşılaştırır, verinin nerede saklandığını görmez.

#### Anahtar–Değer Deposu

Anahtar–değer deposu bir adı bir değere bağlar: `taslak` adı bir metne, `tema` adı bir seçime. Okuma ve yazma tek adımdır ve adı bilen değeri alır. Küçük, bağımsız ve sorgulanması gerekmeyen veri için uygundur: ayarlar, son açılan ekran, yarım kalmış bir metin. Depoya erişim uygulamanın ana akışını bekletmemek için eşzamansızdır: okuma bir söz döndürür.

Eşzamansız okuma ekranın ilk çiziminde değerin henüz elde olmadığı anlamına gelir. Ekran önce değersiz açılır, depo yanıt verince durum değişir ve ekran yeniden çizilir.

```jsx
// Platform kodu: DraftEditor
function DraftEditor() {
  const [draft, setDraft] = useState(null);
  useEffect(() => {
    storage.getItem("draft").then(setDraft);
  }, []);
  return <Editor draft={draft} />;
}
```

Bu parça ölçülmez; model depoyu ve çerçeveyi çalıştırmaz.

**UK1.** Model, depoyu bellekteki bir `Map` olarak kurar ve okuma ile yazmayı bir `Promise` ile döndürür. Uygulamanın kapanıp yeniden açılması modele girmez; model yalnız okuma bitince ağaçta ne olduğunu ölçer.

```javascript
// store.mjs — anahtar-deger deposu: bellek ici Map uzerinde Promise ile okur ve yazar
const data = new Map();

export function setItem(key, value) {
  return Promise.resolve().then(() => {
    data.set(key, value);
  });
}

export function getItem(key) {
  return Promise.resolve().then(() => (data.has(key) ? data.get(key) : null));
}
```

**UK2.** Model, ekranın ilk çiziminde taslağı henüz okunmamış sayar; ağaç ilk olarak taslak olmadan kurulur. Taslak varsa `Draft` düğümü ağaca ekranın altında girer, yoksa hiç girmez. `renderEditor(state)` ağacı bu durumdan kurar.

```javascript
// editor.mjs — taslak ekraninin gorunum agacini durumdan kurar
import { el } from "./fate.mjs";

export function renderEditor(state) {
  const children = [el("Title", { metin: "Taslak" })];
  if (state.taslak !== null) children.push(el("Draft", { metin: state.taslak }));
  return el("Editor", {}, children);
}
```

Uygulama açılıyor. Depoda daha önce kaydedilmiş bir taslak var. Ekran önce taslaksız kurulur, sonra okuma bitince taslak durumu ağaca girer.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderEditor } from "./editor.mjs";
import { setItem, getItem } from "./store.mjs";

const opening = renderEditor({ taslak: null });
setItem("taslak", "Merhaba")
  .then(() => getItem("taslak"))
  .then((saved) => {
    const ops = reconcile(opening, renderEditor({ taslak: saved }));
    printOps(ops);
    countFates(ops);
  });
```

```text
korunan: /Editor
korunan: /Editor/Title
kurulan: /Editor/Draft
korunan: 2
guncellenen: 0
kurulan: 1
sokulen: 0
komut: 1
```

`/Editor/Draft` kurulandır. Başlık ve ekranın kendisi korunandır. Komut sayısı 1'dir ve o komut depodan okunan veridir. Depo ağacın dışındadır: veri yalnız durum aracılığıyla ağaca girer ve ağaç yalnız düğüm olarak bilir.

Depo boşsa durum değişmez. Okuma `null` döndürür ve ağaç açılıştaki ağaçla aynıdır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderEditor } from "./editor.mjs";
import { getItem } from "./store.mjs";

const opening = renderEditor({ taslak: null });
getItem("yok").then((saved) => {
  console.log("bos depo:");
  countFates(reconcile(opening, renderEditor({ taslak: saved })));
});
```

```text
bos depo:
korunan: 2
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Depo boşken okuma bir komut doğurmaz. Okumanın bitmiş olması, ağacın değişmesi için yeterli değildir; ağacı değiştiren, okunan değerin durumu değiştirmesidir.

#### Gömülü Veritabanı

Veri tek bir değer değil de birçok kayıtsa ve kayıtlar üzerinde sorgu yapılacaksa anahtar–değer deposu yetmez. Gömülü veritabanı uygulamanın içinde çalışır ve kayıtları tablolarda tutar. Kayıtlar kimliğiyle aranır, bir koşula göre süzülür, sıralanır. Bir taslak listesi, sabitlenmiş taslaklar, tarihe göre sıralı kayıtlar bunun örnekleridir.

**UK3.** Model veritabanını, kimlikli satırlardan oluşan sabit bir tablo olarak kurar ve sorguyu tek bir süzgeçle sınırlar: `query(onlyPinned)` tüm satırları ya da yalnız sabitlenmişleri döndürür.

```javascript
// db.mjs — gomulu veritabani: bellek ici satirlari Promise ile sorgular
const ROWS = [
  { id: "t1", baslik: "Giris", sabit: true },
  { id: "t2", baslik: "Sonuc", sabit: false },
  { id: "t3", baslik: "Ekler", sabit: true },
];

export function query(onlyPinned) {
  return Promise.resolve(ROWS.filter((row) => !onlyPinned || row.sabit));
}
```

Sorgu sonucu ekrana bir liste olarak girer. Satırlar kimlikle anahtarlanır; böylece bir süzgeç değişince hangi satırın kaldığı kimlikten bilinir.

```javascript
// main.mjs
import { el, reconcile, printOps, countFates } from "./fate.mjs";
import { query } from "./db.mjs";

function renderDrafts(rows) {
  return el("List", {}, rows.map((row) =>
    el("Item", { baslik: row.baslik }, [], row.id)));
}

Promise.all([query(false), query(true)]).then(([all, pinned]) => {
  const ops = reconcile(renderDrafts(all), renderDrafts(pinned));
  printOps(ops);
  countFates(ops);
});
```

```text
korunan: /List
korunan: /List/Item[t1]
korunan: /List/Item[t3]
sokulen: /List/Item[t2]
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 1
komut: 1
```

Süzgeç sabitlenmemiş satırı çıkardı. `Item[t2]` sökülendir, kalan iki satır ve liste korunandır. Satırlar kimlikle anahtarlandığı için `t3` satırı sıra kaydığı hâlde başka bir satır sayılmadı. Bu, deponun türünden bağımsız bir sonuçtur: gömülü veritabanı ağaca yalnız satırları verir, kuralın gördüğü satırdır.

#### Kaynak Parçayı Satır Satır Okumak

`DraftEditor` parçası, deponun ağaca nasıl bağlandığını üç satırda gösterir. İlk satır durumu `null` ile başlatır: ekran ilk çizildiğinde taslak henüz yoktur. Bu, yukarıdaki modelin ilk varsayımının çerçevedeki karşılığıdır. İkinci parça, açılışta bir kez koşan yan etkidir: depodan okunan söz bitince sonuç durum olarak yazılır. Üçüncü parça, çizimdir: durum ne ise ekran onu gösterir.

Parçada depodan ağaca giden tek yol durumdur. Depo ağaca doğrudan bir düğüm eklemez; okuma biter, durum değişir ve ekran o durumdan yeniden kurulur. Modelin `renderEditor(state)` işlevi bu son adımın karşılığıdır. Aralarındaki fark yalnız şudur: model okumanın bitişini bir söz zinciriyle sıraya koyar, çerçeve aynı bitişi bir yan etkiyle yakalar. Ağaçta görünen akıbet iki yolda da aynıdır, çünkü kural ağaçları karşılaştırır ve oraya hangi yolla varıldığına bakmaz.

Bu okumanın bir sonucu da şudur: ilk çizimde kullanıcı kısa bir an taslaksız bir ekran görür. Depo hızlıysa bu an fark edilmez; yavaşsa ekran önce boş açılır ve taslak sonradan belirir. Bunun bir ağaç karşılığı vardır, `Draft` düğümü ilk kurulumda değil, ikinci karşılaştırmada kurulandır ve ölçümümüz tam olarak bu iki adımı karşılaştırır.

#### Taslağı Silmek

Okuma ve yazmanın karşıtı silmedir. Kullanıcı taslağı attığında durum yeniden `null` olur ve `Draft` düğümü ağaçtan çıkmalıdır. Bunu da ölçelim.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderEditor } from "./editor.mjs";

const ops = reconcile(
  renderEditor({ taslak: "Merhaba" }),
  renderEditor({ taslak: null }),
);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
sokulen: /Editor/Draft
korunan: 2
guncellenen: 0
kurulan: 0
sokulen: 1
komut: 1
```

Açılışın tersi gerçekleşti: kurulan düğüm sökülen oldu. Başlık ve ekran korunandır. Silme bir komut doğurdu; ama bu komut ağacın komutudur, depoyu silmek ayrı bir iştir. Taslağı ağaçtan çıkarmak depodaki değeri silmez. Uygulama yeniden açıldığında depodaki eski taslak yeniden okunur ve `Draft` yeniden kurulur. Kullanıcının attığı taslağın geri gelmemesi için hem durumun hem deponun temizlenmesi gerekir; ağaç yalnız ilkini görür.

#### Hangi Depo Ne İçin

İki depo türü ağaçta aynı akıbeti verir ama farklı işler için seçilir. Anahtar–değer deposu adı bilinen tek bir değeri taşır ve okuma ile yazma adımları basittir. Sorgu gerekmeyen küçük veri için yeterlidir. Gömülü veritabanı ise veriye bir yapı verir: satırlar, kimlikler, süzgeçler. Verinin kendisi bir koleksiyonsa ve kullanıcı onu süzüp sıralıyorsa veritabanı doğru seçimdir.

Seçim hatası iki yönde olur. Bir koleksiyonu tek bir anahtara serileştirip her değişiklikte baştan yazmak, her küçük değişikliğin bütün veriyi okuyup yazmasına yol açar. Tek bir ayar değerini ise veritabanında tutmak gereksiz bir yapı kurar. Ağaç bu farkı görmez, ama ekranın tepki biçimi bu seçimden etkilenir: veritabanı kimlikli satırlar verdiği için liste ağacı anahtarlanabilir, tek bir serileştirilmiş değerden anahtar çıkarmak ise ayrıca iş ister.

#### Yaygın Yanılgılar

- "Depoya yazınca ekran değişir." Yazma ağaçtaki hiçbir düğümü değiştirmez. Ekranı değiştiren, durumun değişmesidir; kayıt ise durumun bir kopyasını ağacın dışına bırakır.
- "Depodan okumak ekranı hemen doldurur." Okuma eşzamansızdır. Ekran önce değersiz kurulur ve okuma bitince ikinci bir karşılaştırmayla güncellenir; bu ara bir an için de olsa görülebilir.
- "Boş depo bir hatadır." Boş depo okumayı `null` ile bitirir ve durum değişmez. Ağaç açılıştaki ağaçla aynı kalır, komut 0'dır.
- "Ağaçtan çıkan veri depodan da çıkmıştır." Sökülen düğüm depodaki değeri silmez. Kullanıcının attığı veri ancak depodan da silinirse yeniden açılışta geri gelmez.
- "Veritabanı her zaman anahtar–değer deposundan iyidir." Yapı bir bedeldir. Tek bir ayar için tablo kurmak ve satır kimliği düşünmek gereksiz iş getirir; seçim verinin biçimine bakar, deponun gücüne değil.
- "Sökülen düğümün yerel görünümü depoda kalır." Kaydırma konumu gibi yerel görünüm ağaçla birlikte gider; depoda kalan yalnız uygulamanın açıkça yazdığı veridir. Kullanıcının dönünce görmesi gereken şey yazılmadıysa kaybolur.
- "Depoyu okumak her açılışta gerekir." Okuma yalnız ekranın ihtiyaç duyduğu veri için gerekir. Ekranın hiç göstermediği bir değeri açılışta okumak boşa iş yapar; ağaç bu işi görmez ama uygulamanın açılış süreci o işi taşır.
- "Kayıt zaman alıyorsa ekran beklemelidir." Yazma eşzamansızdır ve ağaç yazmanın bitmesini beklemez. Kullanıcı yazdığı metni anında görür; kaydın bittiği ayrıca izlenir.
- "Depodaki veri ile ekrandaki veri her zaman aynıdır." İkisi ayrı yerlerde durur ve yazma bitene kadar farklı olabilir. Kullanıcı yazarken ekran ileridedir, depo geridedir; ağaç yalnız ekranın hâlini gösterir.

#### Sınırlayıcı Ölçüm

Depoya yazmak ağacı değiştirmez. Kullanıcı taslağı düzenleyince durum değişir, ağaçta `Draft` güncellenir. Taslağın depoya yazılması ise ayrı bir iştir ve yazma bittiğinde ağaçta yeni bir şey görünmez. Aşağıdaki ölçüm iki adımı ayırır: önce kullanıcı yazar, sonra yazı depoya kaydedilir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderEditor } from "./editor.mjs";
import { setItem } from "./store.mjs";

const opened = renderEditor({ taslak: "Merhaba" });
const typed = renderEditor({ taslak: "Merhaba dunya" });
console.log("yazma:");
printOps(reconcile(opened, typed).filter((op) => op.path === "/Editor/Draft"));
setItem("taslak", "Merhaba dunya").then(() => {
  console.log("kayit:");
  countFates(reconcile(typed, renderEditor({ taslak: "Merhaba dunya" })));
});
```

```text
yazma:
guncellenen: /Editor/Draft
kayit:
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Yazma `Draft` düğümünü güncelledi, çünkü metin özelliği değişti. Depoya kaydetme ise hiçbir düğümü değiştirmedi: komut 0'dır ve `Draft` düğümü korunandır. Depo ağacın dışındaki bir yazmadır; kural onu görmez ve görmesi gerekmez. Kayıt başarısız olsa da ağaç aynı kalırdı. Bu yüzden kaydın doğru yapıldığı ağaca bakarak anlaşılmaz; depodan geri okuyarak anlaşılır.

#### Özet

- Bu dersin akıbeti: kurulan. Açılışta depodan okunan taslak ağaca girince `/Editor/Draft` düğümü kurulan çıktı.
- Depo boşken okuma bir komut doğurmaz: ağaç, okuma durumu değiştirmedikçe değişmez.
- Gömülü veritabanında sorgu sonucu bir listedir; süzgeç değişince çıkan satır sökülen, kalan satırlar kimlikle eşlendikleri için korunandır.
- Depoya yazmak görünüm ağacında komut doğurmaz; düzenleme `Draft` düğümünü günceller, kayıt ise hiçbir düğümü değiştirmez.
- Kural deponun türünü ve yazmanın başarısını görmez; yalnız ağaçları karşılaştırır.

#### Sonraki Adım

Bu derste depoya yazılan veri taslak gibi sıradan bir metindi. Bazı değerler sıradan değildir: oturum belirteci, parola, anahtar. Bunlar düz bir depoda durmamalıdır. Sıradaki ders, `Güvenli Depolama`, gizli değerlerin nerede saklandığını ve oturum kapanınca ağaçta ne olduğunu ölçer.

### Güvenli Depolama

Source: https://academia.sh/tr/kurslar/react-native/guvenli-depolama

Oturum belirteci, parola ve anahtar gibi gizli değerler düz depoda durmaz; işletim sisteminin korumalı deposuna yazılır. Ders oturum kapanınca hesap alt ağacının sökülen olduğunu, belirtecin depodan silinmesinin ise ağaçta görünmediğini ölçer.

Önceki ders uygulama kapansa da kalan veriyi ele aldı. O verinin taslak gibi sıradan bir metin olması işi kolaylaştırıyordu. Oturum belirteci, parola ya da şifreleme anahtarı sıradan değildir: bunları ele geçiren, kullanıcının yerine geçer. Bu ders gizli değerlerin nerede durduğunu ve oturum kapanınca görünüm ağacında ne olduğunu ele alır.

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural yalnız ağaçları karşılaştırır, gizli değerin nerede durduğunu görmez.

#### Gizli Değer Nerede Durur

Düz anahtar–değer deposu uygulamanın veri dizininde, korumasız bir dosyada durur. Yedeğe girebilir, kök yetkisi alınmış bir telefondan okunabilir. Gizli değer için işletim sistemi ayrı bir yer sunar: güvenli anahtar deposu. Değer orada şifreli tutulur, erişim uygulamanın kimliğine ve çoğu zaman telefonun kilit durumuna bağlanır. Her platformun deposu kendi adıyla ve kendi kuralıyla gelir; çerçeve bunların üstüne tek bir yazma ve okuma yüzü koyar.

```swift
// Platform kodu: SecretStore
import Security

func saveToken(_ token: Data) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "session-token",
        kSecValueData as String: token,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)
    return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}
```

Bu parça ölçülmez; model platformun deposunu çalıştırmaz. Dikkat edilecek nokta, yazmanın uygulamanın görünümünden tamamen ayrı bir çağrı olmasıdır: depoya yazan kod ekrana hiçbir şey çizmez.

**UK1.** Model güvenli depoyu bellekteki bir `Map` olarak kurar ve okuma, yazma, silmeyi `Promise` ile döndürür. Şifrelemeyi ve erişim koşullarını modellemez; ölçülen şey depoya yazmanın ya da depodan silmenin ağaçta ne bıraktığıdır.

```javascript
// vault.mjs — guvenli anahtar deposu: bellek ici Map, Promise ile yaz oku sil
const secrets = new Map();

export function saveSecret(key, value) {
  return Promise.resolve().then(() => {
    secrets.set(key, value);
  });
}

export function readSecret(key) {
  return Promise.resolve().then(() => (secrets.has(key) ? secrets.get(key) : null));
}

export function deleteSecret(key) {
  return Promise.resolve().then(() => {
    secrets.delete(key);
  });
}
```

**UK2.** Model uygulamanın ağacını oturum durumundan kurar: belirteç yoksa `Login`, varsa `Account` ve altında `Profile` ile `Orders`. Başlık her iki durumda da vardır. Belirtecin değerinin kendisi ağaca girmez; ağaç yalnız belirtecin var ya da yok olmasına bakar.

```javascript
// app.mjs — uygulamanin gorunum agacini oturum durumundan kurar
import { el } from "./fate.mjs";

export function renderApp(session) {
  const body = session.token === null
    ? el("Login", {})
    : el("Account", {}, [el("Profile", { ad: session.ad }), el("Orders", {})]);
  return el("App", {}, [el("Header", {}, [el("Title", { metin: "Hesabim" })]), body]);
}
```

Çerçevede bu seçim, belirteç durumuna bakan küçük bir ekran kapısıdır.

```jsx
// Platform kodu: AccountGate
function AccountGate({ token }) {
  return token ? <Account /> : <Login />;
}
```

#### Oturum Kapanınca

Kullanıcı oturumu kapatır. Durumdaki belirteç silinir ve kapı `Login` ekranına döner.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const signedIn = renderApp({ token: "t-1", ad: "Ayse" });
const signedOut = renderApp({ token: null, ad: "" });
const ops = reconcile(signedIn, signedOut);
printOps(ops);
countFates(ops);
```

```text
korunan: /App
korunan: /App/Header
korunan: /App/Header/Title
sokulen: /App/Account
sokulen: /App/Account/Profile
sokulen: /App/Account/Orders
kurulan: /App/Login
korunan: 3
guncellenen: 0
kurulan: 1
sokulen: 3
komut: 4
```

`/App/Account` sökülendir ve onunla birlikte altındaki `Profile` ile `Orders` de sökülür: alt ağaç tek parça gider. Yerine `Login` kurulur. Başlık ve uygulamanın kendisi korunandır. Kullanıcının hesabına ait ekran durumu, kaydırma konumu, yarım yazdığı alan, bu düğümlerle birlikte yok olur. Bu sonuç M10/K02'deki ayrımla örtüşür: sökülenin durumu yok olur; akıbet ömrün yerini değil, güncellemenin düğüme yaptığını sınıflar.

#### Açılışta Belirteci Okumak

Uygulama yeniden açıldığında bellekteki durum boştur. Oturumun sürüp sürmediğini yalnız güvenli depo bilir. Açılışta belirteç depodan okunur, durum buna göre kurulur.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { saveSecret, readSecret } from "./vault.mjs";

const opening = renderApp({ token: null, ad: "" });
saveSecret("oturum", "t-1")
  .then(() => readSecret("oturum"))
  .then((token) => {
    const ops = reconcile(opening, renderApp({ token, ad: "Ayse" }));
    printOps(ops);
    countFates(ops);
  });
```

```text
korunan: /App
korunan: /App/Header
korunan: /App/Header/Title
sokulen: /App/Login
kurulan: /App/Account
kurulan: /App/Account/Profile
kurulan: /App/Account/Orders
korunan: 3
guncellenen: 0
kurulan: 3
sokulen: 1
komut: 4
```

Açılış ekranı `Login` ile başlar. Belirteç bulununca `Login` sökülür, `Account` ve altındaki iki düğüm kurulur. Ağaçta görülen, depodan okumanın kendisi değil, okumanın durumu değiştirmesidir. Belirteç bulunamasaydı durum değişmez ve ağaç açılıştaki ağaçla aynı kalırdı.

#### Silmek Ağaçta Görünmez

Oturum kapatmak iki ayrı iştir: durumu temizlemek ve belirteci depodan silmek. İlki ağacı değiştirir. İkincisi ağacın dışında olur. Aşağıdaki ölçüm yalnız ikincisini yapar: durum yerinde dururken belirteç depodan silinir.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { saveSecret, readSecret, deleteSecret } from "./vault.mjs";

const session = { token: "t-1", ad: "Ayse" };
saveSecret("oturum", "t-1")
  .then(() => deleteSecret("oturum"))
  .then(() => readSecret("oturum"))
  .then((left) => {
    console.log("depoda kalan: " + left);
    countFates(reconcile(renderApp(session), renderApp(session)));
  });
```

```text
depoda kalan: null
korunan: 6
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Depo boşaldı ama ağaçta hiçbir şey olmadı: komut 0'dır, `Account` hâlâ yerinde durur. Ekran, durumdaki belirtece bakar; depoda ne kaldığına bakmaz. Bu yüzden silme tek başına yetmez ve tek başına görünmez.

#### Platform Parçasını Satır Satır Okumak

`saveToken` işlevi dört kararı bir arada taşır. Birincisi, sorgunun sınıfıdır: değer genel bir parola kaydı olarak tanımlanır. İkincisi, kaydın adıdır: oturum belirteci tek bir adla bulunur, bu yüzden okuyan ve silen kod da aynı adı kullanır. Üçüncüsü, değerin kendisidir. Dördüncüsü, erişim koşuludur: değer yalnızca telefon açıkken okunabilir ve başka bir telefona taşınamaz.

Yazmadan önce aynı sorgu ile bir silme çağrısı yapılır. Bunun nedeni, bu depoların aynı adla ikinci bir kayıt eklemeyi reddedebilmesidir; önce silmek, belirteç yenilendiğinde yazmanın başarısız olmasını önler. İşlev son olarak eklemenin başarılı olup olmadığını döner.

Bu parçanın hiçbir satırı ekrana dokunmaz. Yazma başarılı da olsa başarısız da olsa görünüm ağacı aynıdır. Ekrana ulaşan tek şey, uygulamanın bu sonucu durum olarak yazmasıdır: belirteç alındı, oturum açık. Depo ile ağaç arasındaki bu mesafe, bu dersin ölçümlerinin ortak konusudur.

#### Belirteç Yenilenince

Belirteçlerin ömrü sınırlıdır; uygulama zaman zaman yeni bir belirteç alır ve depodaki değeri değiştirir. Kullanıcı bunu fark etmemelidir. Ağaç açısından da fark edilmemesi gerekir: oturum sürüyor, ekran aynı kalıyor. Bunu iki yenilemeyle ölçelim. İlkinde yalnızca belirteç değişir. İkincisinde belirteçle birlikte kullanıcının adı da değişir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";

const before = renderApp({ token: "t-1", ad: "Ayse" });
console.log("yalniz belirtec:");
countFates(reconcile(before, renderApp({ token: "t-2", ad: "Ayse" })));
console.log("belirtec ve ad:");
printOps(
  reconcile(before, renderApp({ token: "t-2", ad: "Zeynep" }))
    .filter((op) => op.fate !== "korunan"),
);
```

```text
yalniz belirtec:
korunan: 6
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
belirtec ve ad:
guncellenen: /App/Account/Profile
```

İlk yenilemede ağaç değişmedi: belirteç ağaca yazılmadığı için komut 0'dır. Kullanıcı ağaç açısından hiçbir şey görmez, hesap ekranı olduğu yerde durur. İkinci yenilemede yalnız `Profile` güncellenendir; çünkü değişen özellik, adı gösteren düğümün özelliğidir. `Account` ve altındaki öteki düğümler korunandır.

Bu iki sonuç aynı ilkenin iki yüzüdür. Gizli değer ağaca girmez; bu yüzden değişmesi ağaçta iz bırakmaz. Ağaca giren bilgi, ekranda görünen bilgidir; ve yalnız o değişince bir düğüm güncellenir. Gizli değerin ağaca girmemesi yalnızca bir sızıntı önlemi değildir, aynı zamanda gereksiz komutları da önler.

#### Karşı Örnek: Belirteci Ağaca Yazmak

Belirteci kolaylık olsun diye ekran özelliği yapmak akla gelebilir: `Account` düğümünün bir özelliği olarak belirteç taşınır. Bu çalışır, ama iki bedeli vardır. Birincisi, her yenilemede `Account` güncellenen olur; ekranda hiçbir şey değişmediği hâlde bir komut doğar. İkincisi ve daha ağırı, gizli değer artık görünüm ağacının bir parçasıdır. Ağaç hata raporlarında, hata ayıklama çıktılarında ve ekran kayıtlarında dolaşır; gizli değer de onunla dolaşır. Depo değeri korumak için vardır ve ağaca kopyalamak bu korumayı bozar.

#### Sınırlayıcı Ölçüm

Akıbet gizli değerin güvenliğini ölçmez. Aynı oturum kapatma iki biçimde yapılır: belirteç depoda bırakılarak ve depodan silinerek. Ağaç iki durumda da aynı değişir.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { saveSecret, readSecret, deleteSecret } from "./vault.mjs";

function signOut(withDelete) {
  const signedIn = renderApp({ token: "t-1", ad: "Ayse" });
  const signedOut = renderApp({ token: null, ad: "" });
  const clear = withDelete ? deleteSecret("oturum") : Promise.resolve();
  return clear.then(() => readSecret("oturum")).then((left) => {
    console.log(withDelete ? "silerek:" : "silmeden:");
    console.log("depo: " + (left === null ? "bos" : "dolu"));
    countFates(reconcile(signedIn, signedOut));
  });
}

saveSecret("oturum", "t-1")
  .then(() => signOut(false))
  .then(() => signOut(true));
```

```text
silmeden:
depo: dolu
korunan: 3
guncellenen: 0
kurulan: 1
sokulen: 3
komut: 4
silerek:
depo: bos
korunan: 3
guncellenen: 0
kurulan: 1
sokulen: 3
komut: 4
```

İki koşumda da komut sayısı aynıdır: hesap alt ağacı sökülür, `Login` kurulur. Fark yalnız depoda: ilkinde belirteç dolu kalır, ikincisinde boştur. Ağaca bakan biri oturumun gerçekten kapandığını söyleyemez; belirteç depoda duruyorsa uygulama bir sonraki açılışta oturumu yeniden kurar. Akıbet, güncellemenin düğüme yaptığını sınıflar; belirtecin akıbetini sınıflamaz.

#### Yaygın Yanılgılar

- "Oturum kapandı, ekran değişti, demek ki belirteç silindi." Ekran durumdan çizilir, depodan değil. Silme ayrı bir çağrıdır ve ayrıca doğrulanır.
- "Gizli değer ekranda göründüğü sürece belleğe girmiştir, depo anlamsızdır." Bellekteki kopya geçicidir; depo değerin uygulama kapanınca nerede kaldığını belirler.
- "Belirteç düz depoda da durabilir, sonuçta küçük bir metin." Boyut güvenliği belirlemez; erişimi koruyan şey deponun kendisidir.
- "Silme başarılıysa kullanıcı oturumdan çıkmıştır." Depodan silmek ile ağaçtan oturum ekranını çıkarmak ayrı işlerdir. Silme ağaçta komut doğurmaz; ekranı değiştiren, belirtecin durumdan kalkmasıdır. İki adım birlikte yapılmazsa depo boşken ekran hesabı göstermeye devam eder, ya da ekran girişe dönmüşken eski belirteç depoda kalır.
- "Belirteç yenilenince ekran da yenilenir." Yenileme ağaçta komut doğurmaz; belirteç ekranda görünmediği için hiçbir düğüm değişmez.
- "Güvenli depo yalnız belirteç içindir." Parola, anahtar ve kullanıcıya ait başka gizli değerler de aynı yere yazılır. Seçimi belirleyen değerin biçimi değil, ifşa edilirse doğuracağı zarardır.
- "Oturum kapatınca yalnız belirteç gider." Ağaçtan hesaba ait bütün ekran gider, ona bağlı yerel görünüm de onunla birlikte. Bir sonraki kullanıcı önceki kullanıcının ekran durumunu görmez; bu istenen bir sonuçtur ve ölçümde alt ağacın tek parça sökülmesiyle görünür.

#### Özet

- Bu dersin akıbeti: sökülen. Oturum kapanınca `/App/Account` ile altındaki `Profile` ve `Orders` düğümleri tek parça sökülür, yerine `Login` kurulur.
- Açılışta güvenli depodan okunan belirteç durumu değiştirdiğinde `Login` sökülür ve hesap alt ağacı kurulur.
- Belirtecin depodan silinmesi ağacı değiştirmez: durum yerinde durdukça `Account` düğümü korunandır.
- Aynı oturum kapatma depoyu dolu ya da boş bırakabilir; ağaç iki durumda da aynı değişir, akıbet gizli değerin akıbetini sınıflamaz.
- Gizli değer düz depoda değil, platformun güvenli anahtar deposunda durur.

#### Sonraki Adım

Bu derse kadar ağaçtaki değişiklikler durumdan geldi: bir ekranın açılması, bir verinin yüklenmesi, bir oturumun kapanması. Bir de dışarıdan gelen değişiklik vardır: bildirime dokunan kullanıcı uygulamayı belirli bir ekranda açar. Sıradaki ders, `Bildirimler ve Derin Bağlantı`, bir bağlantının ağaca nasıl girdiğini ölçer.

### Bildirimler ve Derin Bağlantı

Source: https://academia.sh/tr/kurslar/react-native/bildirimler-ve-derin-baglanti

Bildirime dokunmak ya da bir bağlantıyı açmak uygulamayı belirli bir ekranda başlatır. Ders açık uygulamada bağlantının yığına tek bir ekran kurduğunu, kapalı uygulamada ise ağacın tamamının kurulduğunu ölçer.

Önceki derslerde ağaçtaki değişiklik uygulamanın içinden geldi: bir dokunuş, bir yanıt, bir oturum. Bir değişiklik de dışarıdan gelir. Kullanıcı bildirime dokunur ya da bir bağlantıya tıklar; uygulama, sipariş 42'nin ekranında açılmak zorundadır. Bu ders dışarıdan gelen adresin rota listesine nasıl çevrildiğini ve uygulamanın açık ya da kapalı olmasının ağaçta ne fark ettiğini ele alır.

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural bağlantının nereden geldiğini görmez, yalnız önceki ve sonraki ağacı karşılaştırır.

#### Bağlantı Rotaya Dönüşür

Derin bağlantı, uygulamanın belirli bir ekranını gösteren bir adrestir. Bildirim bu adresi taşır, bir e-postadaki bağlantı ya da bir başka uygulama onu açar. Bağlantı ekran değildir: adres bir metindir ve uygulamanın rota tablosu onu bir rotaya çevirir. Platform bağlantıyı iki yoldan iletir. Uygulama açıksa bağlantı çalışan uygulamaya bir olay olarak gelir. Uygulama kapalıysa bağlantı, uygulamayı başlatan ilk adres olarak alınır.

```swift
// Platform kodu: NotificationTap
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {
    let link = response.notification.request.content.userInfo["link"] as? String
    pendingLink = link
    completionHandler()
}
```

Platform tarafı bağlantıyı yakalar ve JavaScript tarafına iletir. Çerçevede iki giriş vardır: açılışta ilk adresi sormak ve açıkken gelen olayı dinlemek.

```jsx
// Platform kodu: LinkListener
function useLink(onLink) {
  useEffect(() => {
    Linking.getInitialURL().then((url) => url && onLink(url));
    const sub = Linking.addEventListener("url", (e) => onLink(e.url));
    return () => sub.remove();
  }, []);
}
```

Bu iki parça ölçülmez; model platformu ve çerçeveyi çalıştırmaz.

**UK1.** Model bağlantıyı `siparis/<kimlik>` biçiminde tek bir adres olarak kurar. `openLink(routes, link)` mevcut rota listesini ve adresi alır, yeni rota listesini döndürür. Adres tanınmıyorsa ya da hedef rota zaten yığının tepesindeyse liste olduğu gibi döner.

```javascript
// link.mjs — derin baglantiyi rota listesine cevirir
export function openLink(routes, link) {
  const parts = link.split("/");
  if (parts[0] !== "siparis" || parts.length !== 2) return routes;
  const target = "siparis-" + parts[1];
  if (routes[routes.length - 1] === target) return routes;
  return [...routes, target];
}
```

**UK2.** Yığının ağacını birinci dersteki gibi rotalardan kurarız: `Stack` altında her rota için anahtarlı bir `Screen` ve içinde bir `Text`.

```javascript
// stack.mjs — gezinme yiginin rotalarindan gorunum agaci kurar
import { el } from "./fate.mjs";

export function renderStack(routes) {
  return el("Stack", {}, routes.map((name) =>
    el("Screen", { baslik: name }, [el("Text", { metin: name })], name)));
}
```

#### Uygulama Açıkken

Kullanıcı sipariş listesinden bir siparişin ayrıntısına inmiş, sonra bildirim gelmiş. Yığında `liste` ve `detay` var. Bildirime dokunulunca uygulama zaten çalışıyor ve bağlantı olay olarak geliyor.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";
import { openLink } from "./link.mjs";

const routes = ["liste", "detay"];
const before = renderStack(routes);
const after = renderStack(openLink(routes, "siparis/42"));
const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
korunan: /Stack
korunan: /Stack/Screen[liste]
korunan: /Stack/Screen[liste]/Text
korunan: /Stack/Screen[detay]
korunan: /Stack/Screen[detay]/Text
kurulan: /Stack/Screen[siparis-42]
kurulan: /Stack/Screen[siparis-42]/Text
korunan: 5
guncellenen: 0
kurulan: 2
sokulen: 0
komut: 2
```

`/Stack/Screen[siparis-42]` kurulandır ve onunla birlikte altındaki `Text` kurulur. Yığındaki öteki ekranlar ve içlerindeki metinler korunandır. Kullanıcı geri dönerse kaldığı yeri bulur; bağlantı yığını sıfırlamadı, üstüne bir ekran koydu.

#### Uygulama Kapalıyken

Uygulama kapalıyken bildirime dokunulursa bellekte ağaç yoktur. Platform uygulamayı başlatır ve ilk adres bağlantıdır. Başlangıç rotası `liste`, bağlantı hedefi `siparis-42`'dir; yığın ikisiyle birlikte kurulur.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";
import { openLink } from "./link.mjs";

const start = openLink(["liste"], "siparis/42");
const ops = reconcile(null, renderStack(start));
printOps(ops);
countFates(ops);
```

```text
kurulan: /Stack
kurulan: /Stack/Screen[liste]
kurulan: /Stack/Screen[liste]/Text
kurulan: /Stack/Screen[siparis-42]
kurulan: /Stack/Screen[siparis-42]/Text
korunan: 0
guncellenen: 0
kurulan: 5
sokulen: 0
komut: 5
```

Önceki ağaç olmadığı için ağacın tamamı kurulandır: yığın, iki ekran ve iki metin. Korunan düğüm yoktur, çünkü korunacak bir şey yoktur. Aynı bağlantı, uygulamanın açık ya da kapalı olmasına göre ağaçta iki ayrı iş yapar: açıkken tek bir ekran kurar, kapalıyken her şeyi. Bu iki ölçüm ayrı tutulmazsa bağlantının ağaca maliyeti yanlış anlaşılır.

#### Platform Parçalarını Satır Satır Okumak

`NotificationTap` işlevi, kullanıcının bildirime dokunduğu anı yakalar. İşlev bildirimin içindeki bir alandan adresi okur ve bir değişkene yazar; ekrana hiçbir şey çizmez ve yığına hiçbir rota eklemez. Son satır, platforma işin bittiğini bildirir. Bu işlevin görevi yalnız adresi bırakmaktır: adresi rotaya çevirmek ve yığına yazmak uygulamanın sonraki adımıdır.

Bu bölünme önemlidir. Platform kodu bağlantının geldiğini bilir ama hangi ekranın açılacağını bilmez; o bilgi rota tablosundadır. Rota tablosu ise bağlantının nereden geldiğini bilmez. Bu yüzden iki parça birbirinden bağımsız değişebilir: bildirimin biçimi değişince platform kodu, ekran adları değişince rota tablosu güncellenir.

`LinkListener` parçası, adresin JavaScript tarafına iki yoldan ulaştığını gösterir. İlk çağrı, uygulama bağlantıyla başlatıldıysa o ilk adresi sorar; adres yoksa hiçbir şey olmaz. İkinci çağrı, uygulama çalışırken gelen adresleri dinler. Temizleme işlevi (`return () => sub.remove();`) dinleyiciyi kaldırır; böylece ekran ömrü bittiğinde dinleme de biter. İki girişin ikisi de aynı işleve, `onLink`'e bağlanır. Model bu ortaklığı `openLink` ile yansıtır: açık ve kapalı yollar aynı dönüşümü kullanır, yalnız elde önceden bir yığın olup olmaması farklıdır.

#### Geri Dönmek

Bağlantıyla açılan ekranı kullanıcı kapatır. Yığından son rota çıkar ve bağlantının eklediği ekran ağaçtan gider. Bunu da ölçelim; çünkü bağlantı yığına bir ekran ekler ama bu ekranın ömrü bağlantıyla bitmez.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";

const ops = reconcile(
  renderStack(["liste", "detay", "siparis-42"]),
  renderStack(["liste", "detay"]),
);
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
sokulen: /Stack/Screen[siparis-42]
sokulen: /Stack/Screen[siparis-42]/Text
korunan: 5
guncellenen: 0
kurulan: 0
sokulen: 2
komut: 2
```

Bağlantının kurduğu ekran ve içindeki metin sökülendir; geri kalan beş düğüm korunandır. Kuruluşun tersi olarak sökülme, kuruluşla aynı sayıda komut doğurur. Bağlantının ağaca maliyeti, ekranın yaşadığı süre boyunca bir kez ödenir ve ekran kapanınca bir kez daha: bir kurulum, bir söküm.

#### İkinci Bağlantı

Kullanıcı bir siparişe gitmiş, geri dönmeden ikinci bir bildirime dokunmuş olabilir. Yığında `liste` ve `siparis-42` vardır, yeni bağlantı `siparis/43` adresini taşır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";
import { openLink } from "./link.mjs";

const routes = ["liste", "siparis-42"];
const ops = reconcile(renderStack(routes), renderStack(openLink(routes, "siparis/43")));
printOps(ops.filter((op) => op.fate !== "korunan"));
countFates(ops);
```

```text
kurulan: /Stack/Screen[siparis-43]
kurulan: /Stack/Screen[siparis-43]/Text
korunan: 5
guncellenen: 0
kurulan: 2
sokulen: 0
komut: 2
```

İkinci bağlantı birincisini yerinden çıkarmadı; üstüne yeni bir ekran koydu. `siparis-42` korunandır, `siparis-43` kurulandır. Model bu seçimi `UK1` ile yaptı: adres tanınıyorsa ve hedef tepede değilse rota eklenir. Gerçek bir uygulama bu durumda eski siparişi değiştirmeyi seçebilirdi; o hâlde ölçüm farklı bir akıbet verirdi ve bu ders o seçimi ölçmez. Ölçülen, modelin kurduğu davranıştır.

#### Rota Tablosu Ne Yapar

Rota tablosu, adresleri rota adlarına bağlayan küçük bir kural kümesidir. Modelimizde bu kural tek satırdır: `siparis/<kimlik>` biçimindeki adres `siparis-<kimlik>` rotasına dönüşür. Gerçek bir uygulamada tablo daha uzundur ve her satır bir ekranı, bazen de o ekranın parametrelerini tanımlar. Tablonun iki sınırı vardır. Tanımadığı adres için bir şey üretmez, ki bu ders bunu aşağıdaki `taninmayan` ölçümüyle gösterecek. Tanıdığı adres için ise yalnız rota adını üretir; ekranın içeriği, yani siparişin kendisi, ekran açıldıktan sonra ağ katmanından gelir.

Bu ikinci sınır ağaç açısından önemlidir. Bağlantı ekranı kurar, ama siparişin ayrıntıları henüz yoktur. Ekran önce yükleniyor hâlinde açılır, sonra veri gelince güncellenir. Bağlantının ağaca maliyeti bu yüzden iki aşamalıdır: ekranın kurulması ve içeriğin gelmesi. Bu derste yalnız ilk aşama ölçüldü; ikinci aşama Ağ ve Veri dersinin konusudur.

#### Başlangıç Sırası

Uygulama bağlantıyla başlatıldığında iki yol vardır. Birincisinde uygulama ilk adresi sorar ve ağacı doğrudan bağlantının gösterdiği yığınla kurar; ölçümümüz bu yolu gösterdi ve beş düğümün beşi kurulan oldu. İkincisinde uygulama önce başlangıç ekranını kurar, adresi sonra öğrenir ve bağlantıyı açık uygulamaya gelen bir olay gibi işler. Bu yolda başlangıç yığınının üç düğümü (`Stack`, `liste` ekranı ve metni) önce kurulur, bağlantının iki düğümü sonra eklenir; toplam yine beş kurulan düğümdür, ama kullanıcı kısa bir an için yalnız başlangıç ekranını görür.

İki yol komut sayısı bakımından eşittir. Aralarındaki fark ağacın değil, kullanıcının gördüğü ara durumun farkıdır. Kural ara durumu görmez; yalnız karşılaştırılan iki ağacı görür. Bu yüzden bu iki yolun karşılaştırılması ağaç akıbetinin dışında, bir tasarım değerlendirmesidir.

#### Sınırlayıcı Ölçüm

Bağlantının uygulamaya ulaşması ağacın değişmesi demek değildir. Rota tablosu adresi tanımayabilir, ya da bildirim iki kez gelebilir. İki durumda da bağlantı işlenmiştir ama ağaç değişmez.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderStack } from "./stack.mjs";
import { openLink } from "./link.mjs";

function measure(label, routes, link) {
  console.log(label);
  countFates(reconcile(renderStack(routes), renderStack(openLink(routes, link))));
}

measure("taninmayan:", ["liste", "detay"], "kampanya/7");
measure("yinelenen:", ["liste", "siparis-42"], "siparis/42");
```

```text
taninmayan:
korunan: 5
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
yinelenen:
korunan: 5
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

İki ölçümde de komut 0'dır: tüm düğümler korunandır. Akıbet bağlantının işlenip işlenmediğini ölçmez; ağacın ne olduğunu ölçer. Bildirime dokunulmuş, uygulama bağlantıyı almış ve rota tablosu karar vermiş olabilir; ağaçta hiçbir iz kalmayabilir.

#### Yaygın Yanılgılar

- "Bağlantı geldi, demek ki yeni ekran açıldı." Ekran, rota listesine bir rota eklenirse açılır. Tanınmayan ya da yinelenen bağlantı listeyi değiştirmez.
- "Bildirim uygulamayı hep sıfırdan başlatır." Uygulama açıksa bağlantı olay olarak gelir ve yığın korunur; sıfırdan kurulum yalnız uygulama kapalıyken olur.
- "Bildirimin kendisi ağaçta görünür." Bildirim işletim sisteminin bildirim alanında durur; ağaca yalnız ona dokunulunca gelen bağlantı girer.
- "Aynı bildirim iki kez gelirse iki ekran açılır." Hedef rota zaten tepedeyse model listeyi değiştirmez. Yinelenen bağlantı ölçümü komut 0 verdi.
- "Tanınmayan bağlantı hata verir." Model tanınmayan adreste listeyi olduğu gibi döndürür ve ağaç değişmez. Kullanıcıya bir şey söylemek isteniyorsa bu ayrı bir düğüm olarak ağaca yazılmalıdır; o zaman ölçüm komut 0 yerine bir kurulan düğüm gösterirdi.
- "Bağlantı yığını her zaman büyütür." Bağlantı yığına bir rota ekleyebilir, hiçbir şey eklemeyebilir ya da bir uygulama yığını baştan kurmayı seçebilir. Bu ders yalnız ekleme davranışını ölçtü.
- "Bildirim izni bağlantıyı ağaçta gösterir." İzin bildirimin gösterilip gösterilmeyeceğini belirler. Gösterilen bildirime dokunulmadıkça uygulamaya bağlantı ulaşmaz ve ağaç değişmez.
- "Aynı bağlantı her zaman aynı ağacı verir." Sonuç, bağlantının geldiği andaki yığına bağlıdır. Aynı `siparis/42` adresi uygulama kapalıyken, önceki ağaç yokken beş, `liste` ve `detay` ekranlı yığında iki kurulan düğüm üretti. Bağlantının maliyetini yalnız adrese bakarak söylemek mümkün değildir.

#### Özet

- Bu dersin akıbeti: kurulan. Uygulama açıkken bağlantı `/Stack/Screen[siparis-42]` ekranını kurar, alttaki ekranlar korunandır.
- Uygulama kapalıyken önceki ağaç yoktur; bağlantı ağacın tamamını kurulan yapar.
- Aynı bağlantı açıkken tek bir ekran, kapalıyken tüm ağaç kurar; iki durum ayrı ölçülür.
- Tanınmayan ve yinelenen bağlantı ağaçta komut doğurmaz, bağlantının işlenmiş olması ağacın değiştiği anlamına gelmez.
- Bağlantıyı adres olarak taşıyan platform koduyla onu rotaya çeviren rota tablosu ayrı iki iştir; akıbeti ikincisinin sonucu belirler.

#### Sonraki Adım

Bu derse kadar ağaç kesikli adımlarla değişti: bir dokunuş, bir yanıt, bir bağlantı. Parmak ekranda sürüklenirken değişiklik her karede olur. Son ders, `Hareketler ve Animasyonlar`, aynı hareketin ağaca iki yoldan yazılması hâlinde komutun nasıl ayrıştığını ölçer.

### Hareketler ve Animasyonlar

Source: https://academia.sh/tr/kurslar/react-native/hareketler-ve-animasyonlar

Bir hareketin konumu ağaca özellik olarak yazılırsa her kare bir güncelleme komutu doğurur. Hareket yerel tarafta sürülürse aradaki karelerde ağaç korunur. Ders iki yolu aynı hareket üzerinde ölçer.

Önceki ders dışarıdan gelen değişikliği ele aldı. Bir değişiklik de parmaktan gelir: bir kart sürüklenir, bir panel kayarak açılır. Hareket tek bir olay değil, kare kare ilerleyen bir değişimdir. Kartın konumu her karede değişir ve soru şudur: bu değişimin her adımı görünüm ağacından geçer mi, yoksa ağaç yalnızca başlangıcı ve bitişi mi görür?

Kursun sorusu değişmez: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Akıbet korunan, güncellenen, kurulan ya da sökülendir; güncellenen, kurulan ve sökülen düğümlerin toplamı komuttur. Akıbeti elle atamayız, kural verir:

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Kural kareleri değil ağaç çiftlerini karşılaştırır; bir hareketin kaç kare sürdüğünü, ağaca kaç kez uğradığından anlarız.

#### Konum Ağaca Yazılırsa

Hareketi sürmenin ilk yolu konumu uygulamanın durumunda tutmaktır. Parmak hareket ettikçe durum değişir, ağaç yeniden kurulur ve kartın konum özelliği yeni değeri taşır. Her kare için iki ağaç karşılaştırılır ve fark yerel tarafa komut olarak gider.

**UK1.** Model kartı `Deck` içindeki tek bir `Card` olarak kurar; `Card`, konumu bir özellik olarak taşır ve altında bir `Text` vardır. Konum 0, 40, 80 gibi sayılarla verilir; sayılar karenin kimliğidir, ölçü değildir.

```javascript
// deck.mjs — kart destesinin gorunum agacini iki surus yolu icin kurar
import { el } from "./fate.mjs";

export function renderDeck(position) {
  return el("Deck", {}, [
    el("Card", { konum: position }, [el("Text", { metin: "Siparis" })]),
  ]);
}

export function renderDriven() {
  return el("Deck", {}, [
    el("Card", { surucu: "yerel" }, [el("Text", { metin: "Siparis" })]),
  ]);
}
```

`renderDeck` konumu özellik olarak yazar. `renderDriven` ise konumu ağaca yazmaz; kart yalnız hareketin yerel tarafta sürüldüğünü bildirir. İkinci yol çerçevede, hareketin yerel sürücüye devredilmesiyle kurulur.

```jsx
// Platform kodu: SlideCard
function SlideCard({ x }) {
  useEffect(() => {
    Animated.timing(x, { toValue: 160, useNativeDriver: true }).start();
  }, []);
  return <Animated.View style={{ transform: [{ translateX: x }] }} />;
}
```

Bu parça ölçülmez; model yerel sürücüyü çalıştırmaz. Parçadaki kanca konumu durumdan geçirmez: hareketin başlangıç ve bitiş değerleri yerel tarafa bir kez verilir, aradaki kareleri yerel taraf üretir.

İlk ölçüm konumun ağaca yazıldığı tek bir kare geçişidir: kart 0 konumundan 40 konumuna gidiyor.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderDeck } from "./deck.mjs";

const ops = reconcile(renderDeck(0), renderDeck(40));
printOps(ops);
countFates(ops);
```

```text
korunan: /Deck
guncellenen: /Deck/Card
korunan: /Deck/Card/Text
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

`/Deck/Card` güncellenendir, çünkü konum özelliği değişti. `Deck` ve `Text` korunandır; kartın altındaki metin hareketten etkilenmez. Komut sayısı 1'dir: tek bir kare tek bir güncelleme taşıdı.

#### Her Kare Bir Komut

Bir hareket tek karede bitmez. Aynı geçiş her karede tekrarlanır ve her tekrar bir komut ekler. Aşağıdaki yardımcı, art arda gelen kareler için karşılaştırma yapar ve her geçişin komut sayısını döndürür.

```javascript
// frames.mjs — art arda gelen karelerin her gecisindeki komut sayisini verir
import { reconcile } from "./fate.mjs";

export function stepCosts(frames, render) {
  const costs = [];
  for (let i = 1; i < frames.length; i++) {
    const ops = reconcile(render(frames[i - 1]), render(frames[i]));
    costs.push(ops.filter((op) => op.fate !== "korunan").length);
  }
  return costs;
}
```

İki konum dizisi, biri üç kareli biri dört kareli, konumu ağaca yazan yoldan geçirilir. Karşılaştırma için aynı diziler yerel sürülen yoldan da geçirilir.

```javascript
// main.mjs
import { stepCosts } from "./frames.mjs";
import { renderDeck, renderDriven } from "./deck.mjs";

console.log("konum agacta, 3 kare: " + stepCosts([0, 40, 80], renderDeck).join(" "));
console.log("konum agacta, 4 kare: " + stepCosts([0, 40, 80, 120], renderDeck).join(" "));
console.log("yerel surulen, 3 kare: " + stepCosts([0, 40, 80], renderDriven).join(" "));
console.log("yerel surulen, 4 kare: " + stepCosts([0, 40, 80, 120], renderDriven).join(" "));
```

```text
konum agacta, 3 kare: 1 1
konum agacta, 4 kare: 1 1 1
yerel surulen, 3 kare: 0 0
yerel surulen, 4 kare: 0 0 0
```

Konum ağaçta olduğu sürece her geçiş bir komuttur ve hareket uzadıkça komut sayısı kare sayısıyla birlikte artar. Yerel sürülen yolda her geçişin maliyeti 0'dır; hareketin uzunluğu ağaçta görünmez.

#### Yerel Sürülen Hareket

Yerel sürücü hareketi baştan sona kendisi ilerletir. Ağaç yalnızca kartın var olduğunu ve hareketin yerel tarafta sürüldüğünü bilir. Aradaki karelerde ağaç aynı kalır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderDriven } from "./deck.mjs";

const ops = reconcile(renderDriven(), renderDriven());
printOps(ops);
countFates(ops);
```

```text
korunan: /Deck
korunan: /Deck/Card
korunan: /Deck/Card/Text
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

`/Deck/Card` korunandır. Kart ekranda kayıyor olsa da ağaç için hiçbir şey olmuyor: üç düğümün üçü de korunan, komut 0. Hareketin akıcılığı yerel tarafın işidir; konum özelliği ağaca hiç girmez, bu yüzden her kare için yeniden karşılaştırma gerekmez.

#### Kaynak Parçayı Satır Satır Okumak

`SlideCard` parçasında hareketin tamamı bir çağrıya sığar. Bileşen bir animasyon değeri `x` alır: başlangıç konumu bu değerin içindedir ve durumda tutulmaz. `useEffect` bloğu açılışta bir kez çalışan yan etkidir; hareketi o başlatır. Çağrıdaki `toValue` bitiş konumunu verir, `useNativeDriver: true` ise ara karelerin yerel tarafta üretileceğini söyler. `return` satırı çizimdir ve konumu bir dönüşüm olarak karta bağlar.

Parçada eksik olan şey de öğreticidir: konumu güncelleyen bir durum çağrısı yoktur. Hareket sürerken uygulamanın durumu değişmez, dolayısıyla ağaç yeniden kurulmaz. Modeldeki `renderDriven`, bu eksikliğin ağaçtaki karşılığıdır: kart konum taşımaz, yalnız hareketin yerel tarafta sürüldüğünü bildirir. Konumu durumda tutan başka bir yazım ise `renderDeck` karşılığında olurdu ve her kare için durumu değiştiren bir çağrı gerektirirdi.

Bu iki yazım arasındaki seçimin tek koşulu vardır: hareketin ara değerlerine uygulama mantığının ihtiyacı olup olmadığı. Mantık ara konumu okuyacaksa, örneğin kart belli bir noktayı geçince başka bir şey olacaksa, değer durumda tutulur. Yalnız görsel bir kayma gerekiyorsa yerel sürücü yeterlidir.

#### Birden Fazla Kart

Gerçek bir ekranda aynı anda tek kart kaymaz. Bir deste açıldığında birkaç kart birlikte hareket eder. Konum ağaca yazılıyorsa her kartın konumu ayrı bir özelliktir ve her geçişte hepsi güncellenir. Bunu kart sayısını değiştirerek ölçelim.

```javascript
// main.mjs
import { el } from "./fate.mjs";
import { stepCosts } from "./frames.mjs";

function renderMany(count, driven) {
  return (position) => el("Deck", {}, Array.from({ length: count }, (_, i) =>
    el("Card", driven ? { surucu: "yerel" } : { konum: position },
      [el("Text", { metin: "Siparis" })], "k" + i)));
}

const frames = [0, 40, 80];
console.log("1 kart, konum agacta: " + stepCosts(frames, renderMany(1, false)).join(" "));
console.log("2 kart, konum agacta: " + stepCosts(frames, renderMany(2, false)).join(" "));
console.log("3 kart, konum agacta: " + stepCosts(frames, renderMany(3, false)).join(" "));
console.log("3 kart, yerel surulen: " + stepCosts(frames, renderMany(3, true)).join(" "));
```

```text
1 kart, konum agacta: 1 1
2 kart, konum agacta: 2 2
3 kart, konum agacta: 3 3
3 kart, yerel surulen: 0 0
```

Her geçişin maliyeti kart sayısına eşittir: bir kartta 1, iki kartta 2, üç kartta 3. Dolayısıyla toplam maliyet iki çarpanın ürünüdür: kare sayısı ve kart sayısı. Yerel sürülen yolda üç kart da kaysa maliyet sıfırdır. Hareket ne kadar çok düğümü ilgilendirirse, konumu ağaca yazmanın bedeli o kadar artar.

Bu çarpım hareketin neden pahalı olabileceğini açıklar, ama bir eşik vermez. Kaç kartın "çok" olduğu uygulamaya ve platforma bağlıdır; bu ders yalnız maliyetin hangi çarpanlara bağlı olduğunu ölçer.

#### Karşı Örnek: Konumun Ağaçta Gerektiği Yer

Yerel sürücü her zaman doğru çözüm değildir. Kartın konumu başka bir düğümün durumunu belirliyorsa, örneğin sürüklenen kart bir bırakma alanının üstüne gelince alan vurgulanacaksa, uygulamanın ara konumu bilmesi gerekir. Yerel sürücü bu bilgiyi uygulamaya her kare iletmez. Bu durumda ya konum durumda tutulur ve bedeli ödenir ya da yalnız eşik anında uygulamaya bir olay gönderilir. İkinci seçenek ağaca yalnız o anı yazar: hareket boyunca komut 0, eşik anında bir güncelleme.

Bu seçimde ağacın akıbeti kararı vermez, kararı hareketin işlevi verir. Akıbet yalnız iki yolun bedelini yan yana koyar.

#### Sınırlayıcı Ölçüm

Yerel sürülen hareketin ağaca hiç uğramadığını söylemek yanlış olur. Hareket bitince uygulamanın kartın son konumunu bilmesi gerekir: kart yerine yerleşmiş mi, silinmiş mi, kapanmış mı. Bu bilgi bir kez durumdan ağaca yazılır.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderDeck, renderDriven } from "./deck.mjs";

const ops = reconcile(renderDriven(), renderDeck(160));
printOps(ops);
countFates(ops);
```

```text
korunan: /Deck
guncellenen: /Deck/Card
korunan: /Deck/Card/Text
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 0
komut: 1
```

Kart bu kez güncellenendir: sürücü özelliği gitti, konum özelliği geldi. Komut sayısı 1'dir. Hareket boyunca 0 komut, bitişte 1 komut. Akıbet hareketin akıcı olup olmadığını ölçmez; ağaca ne yazıldığını ölçer. Yerel sürülen hareketin ağaçta komutu yoktur, ama bitişte yazılan konum yine bir güncellemedir.

#### Yaygın Yanılgılar

- "Her animasyon yerel sürülür, dolayısıyla ağaç hiç değişmez." Sürücü bir seçimdir. Konum durumda tutuluyorsa her kare ağaca girer.
- "Komut 0 ise hareket akıcıdır." Komut sayısı ağaçla yerel taraf arasındaki yazımı ölçer; akıcılığı ölçmez.
- "Hareket bitince ağaç aynı kalır." Son konum uygulama için gerekliyse bir kez yazılır ve bu tek bir güncellemedir.
- "Kart sayısı ağaç maliyetini değiştirmez." Konum ağaca yazılıyorsa her kart ayrı bir güncellemedir. Birden fazla kartlı ölçümde geçiş başına komut, kart sayısına eşit çıktı.
- "Yerel sürücü kullanmak kodu karmaşıklaştırır, ağaç zaten yeterince hızlıdır." Bu bir başarım iddiasıdır ve ders bunu ölçmez. Ölçülen şey yalnızca ağaca yazılan komut sayısıdır; ağaç hızı ya da kare süresi ölçülmedi.
- "Hareketi durumdan sürmek yanlıştır." Ara konuma mantık bağlıysa durumdan sürmek doğru seçimdir. Yanlış olan, ihtiyaç yokken her kareyi ağaca yazmaktır.
- "Hareketin durumu ile ağaç aynı şeydir." Animasyon değeri hareketin içindeki sayıdır; ağaç ise ekranın o anki hâlidir. Yerel sürücü sayıyı ilerletir ama ağaca yazmaz. İki ayrı şeyin bu ayrımı, ölçümlerdeki sıfır komutu açıklar.
- "Birden çok hareket aynı komutu paylaşır." Ağaca yazılan her kartın konumu ayrı bir özelliktir; komutlar kart sayısı kadar çoğalır. Çoklu kart ölçümünde paylaşım görülmedi.
- "Animasyonun süresi komut sayısını belirler." Bu derste süre ölçülmedi. Ölçülen, geçiş sayısıdır: konum ağaçta olduğunda her kare bir geçiş ve bir komut getirir; sürenin kare sayısına nasıl dönüştüğü platforma bağlıdır ve bu derste yer almaz.
- "Yerel sürücü yalnız kaydırma içindir." Sürücü, konum dışında dönüşüm gibi başka görsel özellikler için de kullanılabilir. Ortak koşul, ara değerlerin uygulama mantığına gerekmemesidir.
- "Hareket ile geçiş aynı şeydir." Hareket, birçok karenin sürekli akışıdır; geçiş ise iki ağaç arasındaki tek bir karşılaştırmadır. Ders hareketi geçişlere bölerek ölçer ve bu bölme, ağaç kuralının hareketi nasıl gördüğünü açıklar.
- "Kartı ağaçtan kaldırmak hareketi durdurur." Kart sökülürse hareket de yok olur; ama bu başka bir akıbettir. Hareketin ortasında kartı kaldırmak ağaçta sökülen bir düğüm üretir ve bu ölçümün konusu olmadı.

#### Özet

- Bu dersin akıbeti: korunan. Yerel sürülen harekette aradaki karelerde `/Deck/Card` korunandır ve komut 0'dır.
- Konum ağaca özellik olarak yazılırsa her kare `/Deck/Card` düğümünü günceller ve komut sayısı kare sayısıyla artar.
- Yerel sürülen harekette kare sayısı arttıkça komut sayısı değişmez.
- Hareket bitince konumu ağaca bir kez yazmak tek bir güncelleme komutudur.
- Akıbet hareketin akıcılığını değil, ağaca yazılanı sınıflar.

#### Sonraki Adım

Bu dersle uygulamanın katmanları tamamlandı: gezinme, durum, ağ, depolama, bildirim ve hareket. Her ders ağaçtaki bir değişikliğin akıbetini ölçtü. Sıradaki derslerde aynı ölçü başarım sorularına döner. Bir sonraki bölümün ilk dersi, `Liste Başarımı`, bir listenin başına öge eklenince satır düğümlerine ne olduğunu ölçer.

## Başarım ve Üretim

Sanallaştırılmış liste ayarları, kare hızı ve profilleme, paket boyutu ve başlangıç, hata ayıklama araçları, test, yerel modül yazımı, derleme ve yayın.

### Liste Başarımı

Source: https://academia.sh/tr/kurslar/react-native/liste-basarimi

Bir listenin başına öge eklenince satır düğümlerine ne olduğunu ölçer. Sıraya göre eşlemede satırların içeriği kayar; kararlı anahtarla aynı ekleme tek bir kurulan düğüm verir.

Önceki derste animasyon yerel tarafta sürdü. İlerleyen değer ağaca özellik olarak girmediği için düğüm korunan kaldı. Liste bunun tersi bir yerdir: değişen şey doğrudan ağaçtaki ögelerin kendisidir ve tek bir ekleme, bütün satırların akıbetini etkileyebilir. Bu ders listenin ne kadar hızlı kaydığını sormaz. Sorduğu şey, bir öge eklenince hangi satır düğümüne ne olduğudur.

Kursun sorusu aynıdır: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtın dört biçimi var. Düğüm yerinde kalır ve özellikleri aynıysa **korunan**, özellikleri değiştiyse **güncellenen** olur. Önceki ağaçta karşılığı olmayan düğüm **kurulan**, yeni ağaçta karşılığı kalmayan düğüm **sökülen** olur. Bir güncellemenin yerel tarafa gönderdiği iş, korunan dışındaki üç akıbetin toplamıdır ve bu toplam **komut** diye anılır. Akıbeti etiket değil aşağıdaki kural verir. Ağacın durumdan yeniden üretilmesine yeniden çizim (render) denir. Kural, iki ağacı çocuklarını anahtarla ya da anahtar yoksa sırayla eşleyerek karşılaştırır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Kuralın bu derste önem taşıyan kısmı `identity` işlevindedir. Anahtarı olan çocuk anahtarıyla, anahtarı olmayan çocuk sırasıyla tanınır. İki ağaçta aynı kimliği taşıyan çocuklar çifttir; çiftin türü ve anahtarı aynıysa özellikleri karşılaştırılır.

Listeyi ağaca çeviren işlev ayrı bir dosyada durur. `renderInbox` ögeleri satır düğümlerine çevirir. İkinci parametresi `true` ise satıra öge numarasını anahtar olarak verir, `false` ise satır anahtarsız kalır.

```javascript
// inbox.mjs — bir gelen kutusunu gorunum agacina cevirir
import { el } from "./fate.mjs";

export const ITEMS = [
  { id: "a", title: "Toplanti" },
  { id: "b", title: "Fatura" },
  { id: "c", title: "Davet" },
];

export const FRESH = { id: "d", title: "Duyuru" };

export function renderInbox(items, useKeys) {
  const rows = items.map((item) =>
    el("Row", { title: item.title }, [], useKeys ? item.id : null));
  return el("Inbox", {}, rows);
}
```

Gelen kutusunda üç öge var ve listenin başına dördüncüsü, `Duyuru`, geliyor. Yeni ögeyi baştan görmek kullanıcının beklediği davranıştır: en yeni ileti en üstte durur.

#### Sıraya Göre Eşleme

İlk ölçümde satırlara anahtar verilmez. Eski ağaçta üç satır, yeni ağaçta dört satır vardır. Kural satırları sırayla eşler: eskinin birinci satırı yeninin birinci satırıyla, ikincisi ikinciyle, üçüncüsü üçüncüsüyle çift olur. Yeninin dördüncü satırının eskide karşılığı yoktur.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderInbox, ITEMS, FRESH } from "./inbox.mjs";

const before = renderInbox(ITEMS, false);
const after = renderInbox([FRESH, ...ITEMS], false);
printOps(reconcile(before, after));
```

```text
korunan: /Inbox
guncellenen: /Inbox/Row
guncellenen: /Inbox/Row
guncellenen: /Inbox/Row
kurulan: /Inbox/Row
```

Üç satır güncellenen, sondaki satır kurulan çıktı. Eklenen öge baştaydı, ama ağaçta yeni olan satır sonda doğdu. Birinci satır önce `Toplanti` gösteriyordu, şimdi `Duyuru` gösteriyor; ikinci satır `Fatura` yerine `Toplanti`, üçüncü satır `Davet` yerine `Fatura` gösteriyor. Hiçbir satır yeni bir ögeyi temsil etmek için kurulmadı: var olan satırlar ögeleri birer basamak kaydırdı ve fazlalık sona eklendi. Üç satırın özelliği değiştiği için üç güncelleme gitti. Kök düğüm `/Inbox` özelliksizdir ve korunan kaldı.

Anahtarsız satırların yolu aynıdır, `/Inbox/Row`, çünkü yol yalnız tür ve anahtardan kurulur. Hangi satırın hangisi olduğunu çıktıdan sıra bildirir. Kuralın içinde ise her biri `#0`, `#1`, `#2` kimliğiyle tanınır.

Bu yalnız fazladan komut demek değildir. Gerçek bir listede satırın kendi durumu da olur: seçili olması, açık olan bir ayrıntı, yazılmış yarım bir yanıt. Sıraya göre eşlemede satır düğümü korunur ama başka bir ögeye geçer. Durum satırda kalır, öge kayar; birinci satırdaki yarım yanıt bir anda `Duyuru` ögesinin yanıtı olur. Kimlik korunumu denen şey burada bozulur: korunan ya da güncellenen düğüm kimliğini sürdürür, ve kimlik yanlış ögeye bağlanmıştır.

#### Kararlı Anahtar

İkinci ölçümde aynı ekleme yapılır ama satırlara öge numarası anahtar olarak verilir. Anahtar bir ögeyi sırasından bağımsız tanır: `a` ögesi listenin hangi basamağında olursa olsun `a` kalır. Böyle bir anahtara kararlı anahtar denir.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderInbox, ITEMS, FRESH } from "./inbox.mjs";

const before = renderInbox(ITEMS, true);
const after = renderInbox([FRESH, ...ITEMS], true);
printOps(reconcile(before, after));
```

```text
korunan: /Inbox
kurulan: /Inbox/Row[d]
korunan: /Inbox/Row[a]
korunan: /Inbox/Row[b]
korunan: /Inbox/Row[c]
```

Şimdi tek bir satır kuruldu: yeni ögenin kendi satırı, `/Inbox/Row[d]`. Eski üç satır anahtarlarıyla eşleşti, özellikleri aynı kaldığı için hepsi korunan çıktı. Yeni satır yalnız ağaçtaki sırasıyla baştadır; kuralın çıktısı da onu başta basıyor. Ayrıca kimlik korunumu bu kez doğrudur: `a` ögesinin satırı, içindeki durumla birlikte, aynı satır olarak sürer.

Anahtarın kararlı olması şarttır. Anahtar olarak listedeki sıra numarası verilirse anahtar sıraya bağlanır ve ilk ölçümdeki durum geri gelir. Anahtar her güncellemede yeniden üretilirse, örneğin rastgele bir sayıdan, her satır her seferinde eski kimliğini yitirir; bütün eski satırlar sökülen, bütün yeni satırlar kurulan olur.

Gerçek çerçevede anahtar, satırı üreten işlevde bir öge numarasından çıkarılır. Sanallaştırılmış listede bu iş anahtar çıkaran işlevin görevidir. Aşağıdaki parça listenin kaynak biçimini gösterir; model bu parçayı çalıştırmaz.

```jsx
// Platform kodu: Inbox
function Inbox({ items }) {
  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Row title={item.title} />}
    />
  );
}
```

`keyExtractor` her ögeden bir anahtar çıkarır; model bunu `useKeys` parametresiyle temsil eder. Anahtar çıkaran işlev tanımlı değilse gerçek liste bu yedek davranışı kendi başına seçebilir; model yalnız iki ucu, anahtarsız ve anahtarlı hâli, ölçer.

#### Ortadan Silme

Ekleme baştan yapıldığı için kayma en uçtaki durumdu. Silme de aynı sınıftandır ve ortadan silinen bir öge, kuralın iki eşlemesini daha da ayırır. Listeden `b` ögesi, `Fatura`, çıkarılıyor. Kalan iki öge `a` ve `c` yerinde durur.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderInbox, ITEMS } from "./inbox.mjs";

const remaining = ITEMS.filter((item) => item.id !== "b");
for (const useKeys of [false, true]) {
  console.log(useKeys ? "anahtara gore" : "siraya gore");
  countFates(reconcile(renderInbox(ITEMS, useKeys), renderInbox(remaining, useKeys)));
}
```

```text
siraya gore
korunan: 2
guncellenen: 1
kurulan: 0
sokulen: 1
komut: 2
anahtara gore
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 1
komut: 1
```

Sıraya göre eşlemede silinen öge `b` olduğu hâlde sökülen satır sondaki satırdır: ikinci satır `Davet` ögesini gösterecek şekilde güncellendi, üçüncü satır eşsiz kaldı ve söküldü. Kararlı anahtarla sökülen satır silinen ögenin kendi satırıdır, `/Inbox/Row[b]`; öteki satırlar korunandır. Komut sayısı ikiye karşı bire düştü.

Bu iki ölçümün ortak dersi şudur: anahtarsız bir listede ağaç, hangi ögenin eklendiğini ya da çıktığını söyleyemez; yalnız sonda bir satırın kaç tane arttığını ya da azaldığını söyler. Anahtar, ağaca ögenin kimliğini ekler. Liste ögeleri sıralanabiliyor, süzülebiliyor ya da kullanıcı tarafından yeniden dizilebiliyorsa bu kimlik hiç isteğe bağlı değildir.

#### Pencere ve Sanallaştırma

Kural listenin bütün ögelerini ağaçta görür. Gerçek sanallaştırılmış liste böyle çalışmaz. Binlerce ögelik bir listede yalnız ekranda görünen satırların ve yakın çevresinin yerel görünümü kurulur; bu aralığa pencere denir. Kullanıcı kaydırdıkça pencerenin dışına çıkan satırlar yerel tarafta sökülür ve yeni giren satırlar kurulur. Bu yüzden listenin başarımı, ağaçtaki satır sayısından çok pencerenin genişliğine bağlıdır.

Pencerenin ayarları listenin kaynağında yazılır. Satırın boyu önceden bilinirse liste, satırı kurmadan konumunu hesaplayabilir; pencere genişliği ve ilk açılışta çizilecek öge sayısı da ayardır.

```jsx
// Platform kodu: InboxTuned
function InboxTuned({ items }) {
  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      getItemLayout={(_, index) => ({ length: 64, offset: 64 * index, index })}
      windowSize={5}
      initialNumToRender={8}
      renderItem={({ item }) => <Row title={item.title} />}
    />
  );
}
```

Burada yazılan sayılar bir listenin ayarıdır, ölçüm değildir. Hangi değerin işe yaradığını profil aracı gösterir; sonraki ders bu aracı anlatır. Bu derste önemli olan, bu ayarların hiçbirinin kuralın girdisi olmamasıdır. Pencere satırları yerel tarafta kurar ve sökerken ağaç değişmez.

#### Sınırlayıcı Ölçüm

İki eşlemeyi yan yana koyalım. Aynı ekleme iki koşumda yapılır, `countFates` her birinin akıbet sayılarını basar. Karşılaştırılan şey komut sayısıdır, yani yerel tarafa giden iş birimi.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderInbox, ITEMS, FRESH } from "./inbox.mjs";

for (const useKeys of [false, true]) {
  console.log(useKeys ? "anahtara gore" : "siraya gore");
  const before = renderInbox(ITEMS, useKeys);
  const after = renderInbox([FRESH, ...ITEMS], useKeys);
  countFates(reconcile(before, after));
}
```

```text
siraya gore
korunan: 1
guncellenen: 3
kurulan: 1
sokulen: 0
komut: 4
anahtara gore
korunan: 4
guncellenen: 0
kurulan: 1
sokulen: 0
komut: 1
```

Sıraya göre eşleme dört komut, anahtara göre eşleme bir komut verdi. Aynı ekleme için dört komuta karşı bir komut vardır. Üç ögelik bir listede bu fark küçük görünür; fark liste uzadıkça büyür, çünkü başa eklenen her öge, sıraya göre eşlemede kendinden sonraki her satırı bir basamak kaydırır. Kararlı anahtarla komut sayısı listenin uzunluğuna bağlı değildir.

Odak düğüm, sıraya göre eşlemedeki ilk satırdır, `/Inbox/Row`. Aynı yolu taşıyan bütün kayıtları süzelim.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderInbox, ITEMS, FRESH } from "./inbox.mjs";

const ops = reconcile(renderInbox(ITEMS, false), renderInbox([FRESH, ...ITEMS], false));
printOps(ops.filter((op) => op.path === "/Inbox/Row"));
```

```text
guncellenen: /Inbox/Row
guncellenen: /Inbox/Row
guncellenen: /Inbox/Row
kurulan: /Inbox/Row
```

Birinci kayıt `guncellenen`: ilk satır yerinde kaldı ama başka ögeyi gösteriyor. Dersin akıbeti bu kayıttır.

Kuralın görmediği iki şey var. Kural ağaçtaki bütün ögeleri görür, pencereyi görmez; gerçek listede yerel tarafta bu dört komutun hepsi gerekmeyebilir, çünkü pencerenin dışındaki satırların yerel görünümü yoktur. İkincisi, komutun yerel tarafta tuttuğu iş modelde yoktur: bir satırın metnini değiştirmek ile satırın içindeki görseli yeniden yüklemek aynı sayıda komuttur. İkisini ayırmak profil aracına düşer.

**BU1.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde liste üç ögelidir, pencere yoktur ve her öge ağaçta bir satır düğümüdür. Gerçek bir liste yalnız pencere içindeki ögeler için yerel görünüm kurar, anahtar çıkaran işlev tanımlı değilse kendi yedek davranışını seçer ve satırı belleyerek özelliği değişmeyen satırın yeniden çizimini atlayabilir; model bunların hiçbirini kurmaz. Model anahtarsız çocuğu sırasıyla, anahtarlı çocuğu anahtarıyla eşler ve ölçtüğü iki ucun arasındaki davranışı yorumlamaz.

#### Özet

- Bu dersin akıbeti: güncellenen. Sıraya göre eşlemede `/Inbox/Row` düğümü, başa bir öge eklenince yerinde kaldı ama başka ögeyi gösterecek şekilde güncellendi.
- Sıraya göre eşlemede başa eklenen bir öge üç satırı güncelleyip sonda bir satır kurdu; komut sayısı dört oldu.
- Kararlı anahtarla aynı ekleme tek bir `/Inbox/Row[d]` düğümü kurdu, üç eski satır korunan kaldı; komut sayısı bir oldu.
- Sıraya göre eşlemede satırın durumu yanlış ögeye bağlanır; kararlı anahtar kimliği ögeyle birlikte tutar.
- Kural ağaçtaki bütün ögeleri görür, pencereyi ve yerel tarafın işini görmez.

#### Sonraki Adım

Bu derste ağaç değişti ve komutlar sayıldı. Bir yeniden çizimin her zaman komut üretmediği de görülmüştür: kararlı anahtarla üç satır, ağaç yeniden üretildiği hâlde komut vermedi. Sıradaki ders, `Kare Hızı ve Profilleme`, ağacı üç kez yeniden üreten bir güncellemenin sıfır komut verebildiğini ölçer.

### Kare Hızı ve Profilleme

Source: https://academia.sh/tr/kurslar/react-native/kare-hizi-ve-profilleme

Ağacı üç kez yeniden üreten bir güncellemenin sıfır komut verebildiğini ölçer. Yeniden çizim ile komut ayrılır; belleme sayacı düşürür, komutu değil.

Önceki ders başa eklenen bir ögenin komut sayısını dörtten bire indirebildiğini gösterdi. Orada ağaç değişiyordu ve soru değişikliğin satırlara nasıl dağıldığıydı. Bu derste ağaç değişmez: aynı durumdan üç kez üretilir. Kullanıcı yine de takılma yaşayabilir, çünkü ağacı üretmek de bir iştir. Bu ders o işin ağaçta görünmediğini, ama ölçülebildiğini anlatır.

Kursun sorusu değişmedi: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural, çekirdek dosyada aşağıda verilmiştir.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Ekranı ağaca çeviren işlev ayrı bir dosyada durur. Ekranda bir başlık, bir grafik ve bir alt not vardır. İşlev her çağrıldığında ders-yerel bir sayacı artırır; sayaç yeniden çizimleri sayar. Sayaç ağaçta değildir ve kuralın girdisi olmaz: kural yalnız iki ağacı karşılaştırır.

```javascript
// screen.mjs — bir ekrani gorunum agacina cevirir ve yeniden cizimleri sayar
import { el } from "./fate.mjs";

export const counter = { renders: 0 };

export function renderScreen(state) {
  counter.renders += 1;
  return el("Screen", {}, [
    el("Header", { title: state.title }),
    el("Chart", { points: state.points }),
    el("Footer", { note: state.note }),
  ]);
}

export function memoRender(render) {
  let lastKey = null;
  let lastTree = null;
  return (state) => {
    const key = JSON.stringify(state);
    if (key !== lastKey) {
      lastKey = key;
      lastTree = render(state);
    }
    return lastTree;
  };
}
```

`renderScreen` durumu ağaca çevirir. `memoRender` bir çizim işlevini sarar: durum öncekiyle aynıysa ağacı yeniden üretmez, öncekini döndürür. Bu bir bellemedir. Yeniden çizim ile komut arasındaki fark bu iki işlevle ölçülür.

#### Üç Yeniden Çizim

İlk ölçümde aynı durumdan ağaç üç kez üretilir. Üç ağaç iki güncelleme verir: birinciden ikinciye, ikinciden üçüncüye. Kural iki güncellemeyi aynı `ops` dizisinde biriktirir ve sayar; yeniden çizim sayısı ayrıca sayaçtan okunur.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderScreen, counter } from "./screen.mjs";

const state = { title: "Satislar", points: [3, 5, 8], note: "gunluk" };
const first = renderScreen(state);
const second = renderScreen(state);
const third = renderScreen(state);

const ops = [];
reconcile(first, second, "", ops);
reconcile(second, third, "", ops);
console.log(`yeniden cizim: ${counter.renders}`);
countFates(ops);
```

```text
yeniden cizim: 3
korunan: 8
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Üç yeniden çizim, sıfır komut. Dört düğümün her biri iki güncellemede korunan çıktı, sekiz kayıt. Ağaç üç kez üretildi, ama üretilen ağaçlar birbirine eşitti ve yerel tarafa gidecek hiçbir iş çıkmadı.

M10/K03'te bir işlevin kaç kez koştuğu sayılıyordu. Buradaki sayaç aynı şeyi sayar, ama akıbet o sayıya bakmaz. **Yeniden çizim komut değildir; üç yeniden çizim sıfır komut verebilir.** Yeniden çizim ağacın üretilmesidir; komut ise iki ağaç karşılaştırıldıktan sonra yerel tarafa giden iştir. İkisi aynı yönde de, ayrı yönlerde de hareket eder: bir yeniden çizim yüz komut da verebilir, hiç komut da.

#### Belleme

Sayaç üçse, bu üçün gereksiz olduğu görülebilir: durum hiç değişmedi. Belleme bu gereksizliği keser. Aynı üç çağrı `memoRender` ile sarılmış çizim işleviyle yapılır; sayaç sıfırdan başlar.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderScreen, memoRender, counter } from "./screen.mjs";

const render = memoRender(renderScreen);
const state = { title: "Satislar", points: [3, 5, 8], note: "gunluk" };
const first = render(state);
const second = render(state);
const third = render(state);

const ops = [];
reconcile(first, second, "", ops);
reconcile(second, third, "", ops);
console.log(`yeniden cizim: ${counter.renders}`);
countFates(ops);
```

```text
yeniden cizim: 1
korunan: 8
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

Yeniden çizim üçten bire düştü; komut sıfırdı, sıfır kaldı. Belleme iş yaptı, ama işin ağaçtaki akıbete etkisi yok: komut zaten sıfırdı. Bellemenin değeri ağaçta değil, ağacı üretmenin kendi işinde. Üretilen ağaç büyük ve pahalıysa belleme o işi atlar; kural bunu görmez, çünkü iki ağacın eşit olduğunu iki durumda da aynı şekilde söyler.

Burada dikkat edilecek nokta bellemenin anahtarıdır. `memoRender` durumu içeriğiyle karşılaştırır. Gerçek çerçevelerde karşılaştırma çoğu zaman özelliklerin kimliğiyle, yani aynı nesne olup olmadığıyla yapılır. Her çağrıda yeni bir dizi ya da yeni bir işlev üreten bir üst ekran, içeriği aynı olsa bile belleyeni atlatır. Bu fark varsayım bloğunda söylenir; model ölçmez.

Gerçek bir ekranda bellenen şey genellikle ağır bir alt ağaçtır, örneğin grafik. Aşağıdaki parça grafiği bellenmiş bir bileşen işlevi olarak yazar ve ekran, grafiğe verdiği diziyi yalnız satırlar değişince yeniden hesaplar. Model bu parçayı çalıştırmaz.

```jsx
// Platform kodu: SalesScreen
const Chart = React.memo(function Chart({ points }) {
  return <ChartView data={points} />;
});

function SalesScreen({ state }) {
  const points = useMemo(() => state.rows.map((r) => r.total), [state.rows]);
  return <Chart points={points} />;
}
```

`React.memo` grafiği sarar: özellikleri öncekiyle aynıysa grafik yeniden çizilmez. `useMemo` ise `points` dizisini yalnız `state.rows` değişince yeniden kurar; dizi aynı kalırsa sarılmış grafik yeni bir özellik görmez.

#### Değişen Veri

Bellemenin yaptığı şeyi anlamak için tersini de ölçmek gerekir: veri gerçekten değişince ne oluyor? Grafiğin noktaları değişir, başlık ve alt not aynı kalır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./screen.mjs";

const before = renderScreen({ title: "Satislar", points: [3, 5, 8], note: "gunluk" });
const after = renderScreen({ title: "Satislar", points: [3, 5, 9], note: "gunluk" });
printOps(reconcile(before, after));
```

```text
korunan: /Screen
korunan: /Screen/Header
guncellenen: /Screen/Chart
korunan: /Screen/Footer
```

Tek komut gitti: `/Screen/Chart` güncellenen. Başlık ve alt not korunan kaldı; ekran kökü de özelliksiz olduğu için korunan. Veri değişince yeniden çizim zorunludur ve komut da zorunludur. Bellemenin işi, veri değişmediğinde yeniden çizimi atlamaktır; veri değişince atlayacağı bir şey yoktur.

#### Profilleme

Takılma, yeniden çizim sayısından değil, yeniden çizimin sürdüğü zamandan doğar. Ekran her kare için belirli bir sürede yeni görüntüyü hazırlamak zorundadır; JS tarafındaki iş bu süreye sığmazsa kare kaçırılır. Kaçırılan kare kullanıcının gördüğü takılmadır. Bu ders süre yazmaz, çünkü süre modelde yoktur; ölçüm gerçek aygıtta, profil aracıyla yapılır.

Profil aracı iki şeyi ayırır. Birincisi JS tarafındaki iştir: durumun işlenmesi, ağacın üretilmesi, iki ağacın karşılaştırılması. İkincisi yerel taraftaki iştir: komutların yerel görünümlere uygulanması, düzenin hesaplanması, çizim. Bir ekran sıfır komut verse de JS tarafındaki iş yüzünden takılabilir; çok komut verse de yerel taraf yetişebilir. Sıcak yol denen yer, bu iki işin kareden kareye en çok tekrarlandığı yerdir: kaydırma, yazı yazma, sürükleme.

Profil aracıyla ölçülen yer bir sarmalayanla işaretlenebilir. Aşağıdaki parça grafiği bir profil sarmalayanının içine alır ve her çizimin süresini bir toplayıcıya geçirir. Model bu parçayı çalıştırmaz.

```jsx
// Platform kodu: ProfiledChart
function ProfiledChart({ points }) {
  return (
    <Profiler id="chart" onRender={(id, phase, duration) => collect(id, duration)}>
      <Chart points={points} />
    </Profiler>
  );
}
```

Bu parçanın verdiği süre, bu grafiğin JS tarafındaki çizim işidir. Komut sayısıyla aynı şey değildir; ikisi birlikte okunur. Süre yüksek, komut sıfırsa belleme aranır. Süre düşük, komut çoksa ağacın kendisi değişiyordur ve anahtarlar gözden geçirilir.

#### Profil Sarmalayanını Satır Satır Okumak

Yukarıdaki parçanın her satırı bir şey yapar.

- `<Profiler id="chart" ...>` sarmalayanı açar ve ölçümün adını verir. Aynı ekranda birden çok sarmalayan olabilir; `id` hangisinin konuştuğunu söyler.
- `onRender={(id, phase, duration) => collect(id, duration)}` her çizim bittiğinde çağrılan işlevdir. `phase` çizimin ilk çizim mi, güncelleme mi, yoksa bir güncellemenin içinde başlayan güncelleme mi olduğunu, `duration` ise bu çizimin JS tarafındaki işini verir. `collect` toplayıcıdır; uygulamanın kendi işlevidir.
- `<Chart points={points} />` ölçülen alt ağaçtır. Sarmalayan yalnız içindekini ölçer; başlık ya da alt not bu sayıya girmez.

Sarmalayanın ağaçta düğüm olmadığına dikkat etmek gerekir. Ölçüm düzeneği görünüm ağacını değiştirmez; model de bu yüzden `/Screen/Chart` yolunu aynı bırakır. Bir ölçüm aracı ağaca düğüm ekliyorsa araç ölçtüğü şeyi bozuyordur.

#### Yaygın Yanılgılar

İlk yanılgı, çok yeniden çizimin takılma demek olduğudur. Üç yeniden çizim sıfır komut verebildiği için sayı tek başına bir şey söylemez. Yeniden çizim yalnız JS tarafında işlenen bir iştir; yerel tarafa bir şey gitmediği sürece ekranda hiçbir görünüm değişmez. Takılma, ancak yeniden çizimin işi kare süresine sığmadığında olur ve bunu sayı değil profil gösterir.

İkinci yanılgı, her şeyi bellemenin güvenli olduğudur. Belleme bir karşılaştırma ister: `memoRender` her çağrıda durumu bir anahtara çevirir ve öncekiyle karşılaştırır. Durum nadiren aynı kalıyorsa bu karşılaştırma boşa harcanan iştir ve ağacı yine de üretirsiniz. Belleme, durumun sık sık aynı kaldığı yerde kazandırır; her yere serpiştirilmesi bir kazanç değil, bir maliyettir.

Üçüncü yanılgı, sıfır komutun her zaman iyi olduğudur. Veri gerçekten değiştiğinde sıfır komut, ekranın eski veriyi göstermeye devam ettiği anlamına gelir. Değişen Veri ölçümünde grafiğin güncellenen çıkması bir kusur değil, doğru davranıştır. İyi olan, gereksiz komutun olmamasıdır; komutun hiç olmaması değil.

#### Sınırlayıcı Ölçüm

Son ölçümde başlık değişir, grafik değişmez. Üç yeniden çizim yapılır, iki güncelleme sayılır. Başlık her güncellemede yeni bir değer alır; grafik, alt not ve kök aynı kalır. Kural grafiğin yolunu süzerek gösterir.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderScreen, counter } from "./screen.mjs";

const points = [3, 5, 8];
const first = renderScreen({ title: "Satislar", points, note: "gunluk" });
const second = renderScreen({ title: "Satislar (hafta)", points, note: "gunluk" });
const third = renderScreen({ title: "Satislar (ay)", points, note: "gunluk" });

const ops = [];
reconcile(first, second, "", ops);
reconcile(second, third, "", ops);
console.log(`yeniden cizim: ${counter.renders}`);
printOps(ops.filter((op) => op.path === "/Screen/Chart"));
countFates(ops);
```

```text
yeniden cizim: 3
korunan: /Screen/Chart
korunan: /Screen/Chart
korunan: 6
guncellenen: 2
kurulan: 0
sokulen: 0
komut: 2
```

Odak düğüm `/Screen/Chart`: iki güncellemede de korunan. Grafik üç yeniden çizimin her birinde ağaca girdi ve üç kez bir önceki grafikle karşılaştırıldı, ama özellikleri aynı olduğu için yerel tarafa hiçbir şey gitmedi. Başlık iki güncellemede güncellenen oldu; komutların ikisi de başlığa gitti.

Bu ölçümün sınırı, dersin asıl uyarısıdır. Grafik üç kez yeniden çizildi ve yeniden çizim bir iştir: grafiğin noktaları üç kez işlenmiş olabilir, ağır bir hesap üç kez koşmuş olabilir. Kural bunu görmez. Kural yalnız ağaçta görüneni görür; bir düğümün korunan çıkması, o düğümün üretilmesinin ucuz olduğunu söylemez. Takılma, korunan bir düğümün arkasında yaşayabilir. Bunu bulmak için profil aracı gerekir; ağaç farkı bulamaz.

**BU2.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde yeniden çizim, bir işlevin ağacı yeniden üretmesidir ve sayaç bu çağrıları sayar; gerçek çerçevede bir ekranın hangi koşulda yeniden çizileceğini durum, bağlam ve üst ekran birlikte belirler ve model bunları kurmaz. Modeldeki `memoRender` durumu içeriğiyle karşılaştırır; gerçek bellemeler çoğu zaman nesne kimliğiyle karşılaştırır ve bu fark ölçülmez. Kare süresi, JS tarafı ile yerel taraf arasındaki işin dağılımı ve kaçırılan kare modelde yoktur.

#### Özet

- Bu dersin akıbeti: korunan. `/Screen/Chart` düğümü, ağaç üç kez yeniden üretildiği ve başlık değiştiği hâlde iki güncellemede de korunan kaldı.
- Aynı durumdan üç kez üretilen ağaç sıfır komut verdi; yeniden çizim komut değildir.
- Belleme yeniden çizim sayısını üçten bire indirdi, komut sıfır kaldı; bellemenin değeri ağacı üretme işindedir.
- Grafiğin noktaları değişince `/Screen/Chart` güncellenen oldu ve tek komut gitti; belleme veri değişince bir şey atlamaz.
- Takılmanın bedeli JS tarafındaki işte durur; kural yalnız ağaçta görüneni görür ve bu işi profil aracı ölçer.

#### Sonraki Adım

Bu derste ağaç hep aynı ekranın ağacıydı ve ekran en baştan oradaydı. Uygulamanın bütün ekranlarını açılışta yüklemek bu varsayımı büyütür: yüklenen kod da bir bedeldir. Sıradaki ders, `Paket Boyutu ve Başlangıç`, bir ekranın kodunu ertelemenin düğümünü ne zaman kurduğunu ölçer.

### Paket Boyutu ve Başlangıç

Source: https://academia.sh/tr/kurslar/react-native/paket-boyutu-ve-baslangic

Ertelenmiş bir ekran yüklenince yer tutucu söküldü, ekranın düğümü kuruldu. Ders tembel yüklemenin kurulmanın zamanını değiştirdiğini, akıbeti değiştirmediğini ölçer.

Önceki ders ağacı üç kez yeniden üretti ve hiçbir komut çıkmadı. Ekran baştan oradaydı. Gerçek bir uygulamada her ekran baştan orada değildir ve ekranın kodu da baştan yüklü olmak zorunda değildir. Uygulamanın JS kodu bir pakette durur; açılışta o paketin okunması ve çalıştırılması gerekir. Paket büyüdükçe açılış, kullanıcının hiç açmayacağı ekranların kodunu da taşır.

Kursun sorusu: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Bu derste ekranın kodunu yüklemek ağaç dışında bir iştir. Kod yüklenince ağaca bir düğüm girer ve akıbeti o düğüme bakar. İki dosya bu ayrımı kurar. Birincisi ekran kodunu ilk istekte yükleyen ve yüklenen modülleri sayan yükleyicidir. Yükleme bir `Promise` ile ertelenir; modül ilk istekte yüklenir, sonraki isteklerde aynı modül döner.

```javascript
// loader.mjs — ekran kodunu ilk kullanimda yukler ve yuklenen modulleri sayar
export const loaded = { count: 0 };

const cache = new Map();

const MODULES = {
  Reports: () => Promise.resolve({ title: "Raporlar", rows: 3 }),
  Settings: () => Promise.resolve({ title: "Ayarlar", rows: 2 }),
};

export function loadScreen(name) {
  if (!cache.has(name)) {
    cache.set(
      name,
      MODULES[name]().then((mod) => {
        loaded.count += 1;
        return mod;
      })
    );
  }
  return cache.get(name);
}
```

`loadScreen` her modülü ilk istekte yükler ve sayacı artırır. İkinci istekte önbellekteki söz döner; sayaç artmaz. `loaded.count` ağaçta değildir ve kuralın girdisi olmaz; ders onu yalnız yüklenen modül sayısını göstermek için tutar.

İkinci dosya durumdan ağaç üreten işlevdir. Uygulamanın bir başlığı ve bir içerik alanı vardır. Rapor ekranı yüklenmemişse içerik alanında bir yer tutucu durur, yüklenmişse rapor ekranı durur.

```javascript
// app.mjs — uygulamayi gorunum agacina cevirir
import { el } from "./fate.mjs";

export function renderApp(state) {
  const content = state.reports === null
    ? el("Placeholder", { text: "Yukleniyor" })
    : el("Reports", { rows: state.reports.rows });
  return el("App", {}, [el("Header", { title: "Panel" }), content]);
}
```

#### Açılışta Ertelenmiş Ekran

Uygulama açılırken rapor ekranı henüz istenmedi. Açılış ağacı yer tutucuyu taşır; ağaç sıfırdan kurulduğu için `reconcile(null, ...)` her düğümü kurulan verir. Yüklenen modül sayısı sıfırdır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { loaded } from "./loader.mjs";

const first = renderApp({ reports: null });
console.log(`yuklenen modul: ${loaded.count}`);
printOps(reconcile(null, first));
```

```text
yuklenen modul: 0
kurulan: /App
kurulan: /App/Header
kurulan: /App/Placeholder
```

Açılışta üç düğüm kuruldu ve hiçbir ekran modülü yüklenmedi. Rapor ekranının kodu pakette durabilir, ama ağaca girmediği ve istenmediği için açılışın yükü olmadı.

#### Yüklenince

Kullanıcı rapor ekranını açtı. Önce modül yüklenir, sonra durum güncellenir ve ağaç yeniden üretilir. Yeni ağaçta içerik alanında yer tutucu yoktur, rapor ekranı vardır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { loadScreen, loaded } from "./loader.mjs";

const first = renderApp({ reports: null });
const mod = await loadScreen("Reports");
const second = renderApp({ reports: mod });
console.log(`yuklenen modul: ${loaded.count}`);
printOps(reconcile(first, second));
```

```text
yuklenen modul: 1
korunan: /App
korunan: /App/Header
sokulen: /App/Placeholder
kurulan: /App/Reports
```

Başlık ve kök korunan kaldı. İçerik alanında iki ayrı tür vardı: yer tutucu ile rapor ekranı. Tür değiştiği için kural özelliklere bakmadan eskiyi söktü ve yeniyi kurdu. `/App/Placeholder` sökülen, `/App/Reports` kurulan çıktı. Rapor ekranı, önceki ağaçta karşılığı olmayan bir düğümdür; yer tutucunun bir sonraki hâli değildir.

Tembel yükleme burada kurulmanın zamanını değiştirdi. Rapor ekranının düğümü açılışta kurulmadı; kullanıcı istediği anda kuruldu. Aynı düğüm erken yüklemeyle açılışta da kurulabilirdi. Bunu ayrıca ölçelim.

#### Erken Yükleme

Erken yüklemede iki ekranın kodu da açılıştan önce yüklenir. İlk ağaçta rapor ekranı zaten vardır; ağaç sıfırdan kurulduğu için `reconcile(null, ...)` yine yalnız kurulan verir.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { loadScreen, loaded } from "./loader.mjs";

await Promise.all([loadScreen("Reports"), loadScreen("Settings")]);
console.log(`erken: yuklenen modul ${loaded.count}`);
const tree = renderApp({ reports: await loadScreen("Reports") });
countFates(reconcile(null, tree));
```

```text
erken: yuklenen modul 2
korunan: 0
guncellenen: 0
kurulan: 3
sokulen: 0
komut: 3
```

Erken yüklemede açılış iki modül yükledi ve üç düğüm kurdu; tembel yüklemede açılış sıfır modül yükledi ve üç düğüm kurdu. Üç kurulan düğüm iki ölçümde de aynıdır, ama içlerinden biri farklıdır: erkende üçüncü düğüm rapor ekranıdır, tembelde yer tutucudur. Tembel yükleme ağaçtaki komut sayısını azaltmadı; yükü açılıştan alıp ilk istek anına taşıdı. Bunun bedeli, ilk istekte kullanıcının yer tutucuyu görmesidir.

M12/K04'te bir parçanın bu istekte koşup koşmadığı soruluyordu. Buradaki ekran için de aynı soru sorulabilir, ama akıbet o soruya bakmaz. **Ertelenmiş ekran yüklenince düğümü kurulandır; "ertelendi" akıbet değildir.** Ertelenmiş bir ekranın ağaçta yeri yoktur; yüklenene kadar yer tutucu vardır. Yer tutucunun kendi akıbeti sökülendir ve ekranın akıbeti kurulandır.

#### Ertelemenin Kaynakta Görünüşü

Gerçek bir uygulamada ekranı ertelemenin iki yolu vardır. Birincisi ekranı ertelenmiş bir içe almayla tanımlamak ve ekran istenene kadar yer tutucu göstermektir. Model bu parçayı çalıştırmaz.

```jsx
// Platform kodu: App
const Reports = React.lazy(() => import("./Reports"));

function App() {
  return (
    <Suspense fallback={<Placeholder />}>
      <Reports />
    </Suspense>
  );
}
```

`Suspense` yer tutucuyu tutar: `Reports` hazır olmadığı sürece ağaçta `Placeholder` durur, hazır olunca yerini `Reports` alır. Bu, ölçümde gördüğümüz sökme ve kurma çiftidir.

İkinci yol, modülü bir düğmeye basılana kadar okumamaktır. Modül işlevin içinde istendiği için çalıştırılması da oraya kadar ertelenir.

```jsx
// Platform kodu: OpenReports
function OpenReports({ onOpen }) {
  const open = () => {
    const mod = require("./Reports");
    onOpen(mod.default);
  };
  return <Button title="Raporlar" onPress={open} />;
}
```

İki yol da kodun yüklenmesini ya da çalıştırılmasını ilk kullanıma bırakır. İkisi de açılışta daha az iş demektir, ve ikisi de ilk kullanımda bir bekleme getirir. Beklemenin ne kadar sürdüğü modelde yoktur; bu süre gerçek aygıtta ve gerçek paketle ölçülür.

#### Kaynak Parçalarını Satır Satır Okumak

İlk parçada üç satır işe yarar. `React.lazy(() => import("./Reports"))` ekranı hemen okumaz; yalnız "gerektiğinde şu modülü getir" diyen bir tanım kurar. `<Suspense fallback={<Placeholder />}>` ekran hazır olana kadar ağaçta duracak düğümü söyler. `<Reports />` ise ağaçta olması istenen asıl düğümdür; hazır olmadığı sürece ağaca girmez, yerinde yer tutucu durur. Modeldeki `loadScreen` ilk satırın, `renderApp` ikinci ve üçüncü satırın karşılığıdır.

İkinci parçada `require("./Reports")` işlevin içindedir ve bu yüzden yalnız `open` çağrıldığında çalışır. `onOpen(mod.default)` yüklenen ekranı uygulamaya verir; `onPress={open}` ise çağrıyı kullanıcının dokunuşuna bağlar. Burada ağaç düzeyinde bir yer tutucu yoktur: düğmeye basılana kadar ağaçta ekran da yer tutucu da yoktur, basılınca ekran kurulur.

İki parça arasındaki fark, yer tutucunun akıbetini değiştirir. Birinci yolda ağaçta yer tutucu vardır ve yüklenince sökülen olur. İkinci yolda yer tutucu hiç kurulmaz; ekran doğrudan kurulan olur. Dersin akıbeti her iki yolda da ekranın kurulan olmasıdır.

#### "Ertelendi" Neden Bir Akıbet Değildir

Yukarıdaki ayrımı bir örnekle işlemek yararlıdır. Açılışta ekran henüz yüklenmediği için `/App/Reports` yolunda ağaçta hiçbir şey yoktur. Kural yolu olmayan bir düğüm için kayıt tutmaz; "ertelendi" diye bir kayıt da yoktur. Yüklenme gerçekleştiği anda ağaçta yeni bir düğüm belirir ve kural onu ilk kez görür: kurulan.

Yani erteleme düğümün akıbetini değiştirmez; yalnız düğümün ağaca ne zaman gireceğini değiştirir. Ertelenmiş ekranın düğümü, kurulacağı güncellemeye kadar akıbet almaz.

#### Yaygın Yanılgılar

İlk yanılgı, ertelemenin paketi küçülttüğü sanısıdır. Erteleme kodun ne zaman yükleneceğini değiştirir; pakette olup olmadığını değiştirmez. Ekranın kodu yine pakete girer ya da ayrı bir parça olarak yanında gelir. Boyutu küçültmek ayrı bir iştir.

İkinci yanılgı, her ekranı ertelemenin kazanç olduğudur. Erteleme ilk kullanımda bir bekleme getirir. Kullanıcının hemen gideceği bir ekranı ertelemek açılışı hafifletmez, yalnız beklemeyi başka bir ana kaydırır. Erken Yükleme ölçümü bunu gösterir: kod önceden yüklenince kullanıcı bekleme görmez ama açılışta daha çok iş yapılır.

Üçüncü yanılgı, yer tutucunun ağaçta ücretsiz olduğudur. Yer tutucu bir düğümdür; kurulur ve yüklenince sökülür. İki komut bu yüzden yüklenme anında gider: biri yer tutucuyu söker, öbürü ekranı kurar.

#### Paket Boyutu

Paketin boyutu iki şeyden oluşur: uygulamanın kendi kodu ve içe aldığı kod. Boyutu küçültmek için önce neyin pakette olduğuna bakılır. Hiç kullanılmayan bir içe alma pakette yer tutar; büyük bir kitaplığın küçük bir parçası için bütün kitaplığın içe alınması da öyle. Bu ders boyut yazmaz, çünkü boyut modelde yoktur; gerçek bir paketin içeriğini döküp inceleyen araçlar bu işi yapar.

Başlangıç ise boyuttan farklı bir ölçüdür. İki ayrı soru sorulur: açılışta kaç modül yükleniyor, ve ilk ağaç ne zaman kuruluyor. Küçük bir paket çok modül içerebilir ve hepsi açılışta çalıştırılıyorsa başlangıç yine uzar. Büyük bir paketin çoğu modülü ertelenmişse başlangıç kısa kalabilir. Modeldeki `loaded.count` bu ikinci soruya karşılık gelir: yüklenen modül sayısı.

#### Sınırlayıcı Ölçüm

Son ölçümde ekran yüklenir, gösterilir ve sonra gizlenir. İki güncelleme sayılır: yer tutucudan rapor ekranına, rapor ekranından yeniden yer tutucuya. Yüklenen modül sayısı güncellemelerden sonra da okunur. Kural rapor ekranının yoluna süzülür.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderApp } from "./app.mjs";
import { loadScreen, loaded } from "./loader.mjs";

const empty = renderApp({ reports: null });
const mod = await loadScreen("Reports");
const shown = renderApp({ reports: mod });
const ops = reconcile(empty, shown);
reconcile(shown, empty, "", ops);
printOps(ops.filter((op) => op.path === "/App/Reports"));
console.log(`yuklenen modul: ${loaded.count}`);
```

```text
kurulan: /App/Reports
sokulen: /App/Reports
yuklenen modul: 1
```

Odak düğüm `/App/Reports`. İlk güncellemede kurulan çıktı: dersin akıbeti budur. İkinci güncellemede ekran gizlendiğinde aynı düğüm sökülen oldu. Modül sayısı ise bir kaldı: düğüm sökülünce modül yüklemesi geri alınmadı.

Bu, ertelemenin sınırını gösterir. Düğümün kurulması ve sökülmesi ağacın işidir; modülün yüklü kalması yükleyicinin işidir. İkisi birbirine bağlı değildir. Modelde ekranı gizlemek modül yüklemesini geri almaz; modül yüklü kalır ve ikinci açılışta yeniden yüklenmez. Kural ikinci açılışta düğümü yine kurulan sayar, çünkü ağaçta düğüm yoktu. Kuralın göremediği şey, ikinci kurulumun yeniden modül yüklemesi gerektirmediğidir.

**BU3.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde modül yüklemesi bir `Promise` ile ertelenir ve bir sayaç yüklenen modülleri sayar. Gerçek uygulamada ertelenen şey ya paketin içinde durup çalıştırılması ertelenen bir modüldür ya da paketten ayrı yüklenen bir parçadır; hangisinin olduğu araç zincirine göre değişir ve model ikisini ayırmaz. Paket boyutu, açılış süresi ve yükleme bekleme süresi modelde yoktur.

#### Özet

- Bu dersin akıbeti: kurulan. `/App/Reports` düğümü, ertelenmiş ekran yüklenince ağaca girdi ve kurulan çıktı; yer tutucu aynı güncellemede sökülen oldu.
- Tembel yüklemede açılış sıfır modül yükledi; erken yüklemede iki modül yükledi. Her iki açılışta da üç düğüm kuruldu.
- Tembel yükleme kurulmanın zamanını değiştirir, akıbeti değil: "ertelendi" bir akıbet değildir.
- Düğüm sökülünce modül yüklü kaldı; düğümün kurulması ve sökülmesi ile modülün yüklenmesi iki ayrı iştir.
- Kural paket boyutunu ve bekleme süresini görmez; bunları gerçek aygıtta ve gerçek paketle ölçmek gerekir.

#### Sonraki Adım

Bu derste ekranın kodu yüklenebiliyordu ve ağaç ona göre kurulabiliyordu. Kod yüklenirken ya da çalışırken hata çıkarsa ne olur? Sıradaki ders, `Hata Ayıklama Araçları`, bir hata sınırının bozuk alt ağacı söküp yedek görünümü kurmasını ölçer.

### Hata Ayıklama Araçları

Source: https://academia.sh/tr/kurslar/react-native/hata-ayiklama-araclari

Bir hata sınırı bozuk alt ağacı söküp yedek görünümü kurar. Ders aynı panelin özellik değişince güncellendiğini, hata verince söküldüğünü ve sökülenin kimliğini yitirdiğini ölçer.

Önceki ders ekranın kodunu ertelemenin düğümü ne zaman kurduğunu ölçtü. Kod yüklendi ve ağaç sorunsuz kuruldu. Gerçek uygulamada her yükleme ve her çizim sorunsuz bitmez: bir veri beklenen biçimde gelmez, bir işlev beklenmedik bir değer alır ve ekranın bir parçası çizilirken hata verir. Bu ders o hatanın görünüm ağacında neyi söktüğünü ve neyi kurduğunu ölçer.

Kursun sorusu: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Hatayı yakalayan düzenek bir hata sınırıdır. Hata sınırı, bir alt ağacı çizen işlevi sarar: işlev hata verirse sınır hatayı yakalar, alt ağacın yerine bir yedek görünüm üretir ve ekranın geri kalanı çizilmeye devam eder. Sınır olmasaydı hata, ekranın hepsini düşürürdü. Aşağıdaki dosya modeldeki bir paneli, onu saran sınırı ve ekranı kurar. Panel `state.broken` doğruysa hata verir. Sınır hatayı iş diliyle tek satırda yazar ve yedek görünümü döndürür.

```javascript
// screen.mjs — bir ekrani gorunum agacina cevirir ve bozuk paneli hata sinirina sarar
import { el } from "./fate.mjs";

export function renderPanel(state) {
  if (state.broken) throw new Error("veri okunamadi");
  return el("Panel", { total: state.total }, [el("Value", { total: state.total })]);
}

export function withBoundary(render) {
  return (state) => {
    try {
      return render(state);
    } catch (err) {
      console.log(`hata sinirinda yakalandi: ${err.message}`);
      return el("Fallback", { text: "Bir sorun olustu" });
    }
  };
}

export function renderScreen(state) {
  const safe = withBoundary(renderPanel);
  return el("Screen", {}, [el("Header", { title: "Ozet" }), safe(state)]);
}
```

`renderPanel` durumdan bir panel ağacı üretir; panelin bir de değer düğümü vardır. `withBoundary` bir çizim işlevini sarar: işlev hata verirse hatayı yakalar, mesajı yazar ve `Fallback` düğümünü döndürür. `renderScreen` paneli sınırla sarıp başlığın yanına koyar.

#### Sağlam Güncelleme

İlk ölçümde panel hata vermez; yalnız toplam değişir. Başlık aynı kalır, panel ve değer düğümü yeni toplamı taşır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./screen.mjs";

const before = renderScreen({ broken: false, total: 10 });
const after = renderScreen({ broken: false, total: 12 });
printOps(reconcile(before, after));
```

```text
korunan: /Screen
korunan: /Screen/Header
guncellenen: /Screen/Panel
guncellenen: /Screen/Panel/Value
```

Panel ve değer güncellenen çıktı, iki komut gitti. Başlık ve kök korunan kaldı. Güncellenen bir düğüm aynı düğümdür: türü ve anahtarı değişmemiştir, yalnız özelliği değişmiştir. Yerel tarafta varsa durumu, odağı ve kaydırma konumu sürer.

#### Hatalı Güncelleme

İkinci ölçümde aynı güncelleme panelin hata vermesiyle yapılır. Panel hata verir, sınır hatayı yakalar ve `Fallback` döner. Yeni ağaçta panelin yerinde yedek görünüm vardır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./screen.mjs";

const before = renderScreen({ broken: false, total: 10 });
const after = renderScreen({ broken: true, total: 12 });
printOps(reconcile(before, after));
```

```text
hata sinirinda yakalandi: veri okunamadi
korunan: /Screen
korunan: /Screen/Header
sokulen: /Screen/Panel
sokulen: /Screen/Panel/Value
kurulan: /Screen/Fallback
```

İlk satır hata sınırının yazdığı tek satırdır. Başlık ve kök korunan çıktı: hata ekranın geri kalanına geçmedi. Panel ve değeri sökülen oldu, `Fallback` kurulan oldu. Üç komut gitti: iki söküm ve bir kurulum. Panelin özelliği değişmediği için bu söküm özellik karşılaştırmasından gelmez; içerik alanındaki tür değişti, `Panel` yerine `Fallback` geldi ve kural eski alt ağacın hepsini söküp yeninin hepsini kurdu.

M11/K05'te "bu işlem kaynağa ne bırakıyor" sorusu bir işlemden sonra kaynak nesnenin hâlini sorar: aynen, değişti, boş ya da belirsiz. Buradaki akıbet bir nesnenin hâlini değil, bir düğümün kimliğinin güncellemeyi aşıp aşmadığını sınıflar. **Güncellenen kimliğini korur, sökülen yitirir.** Sağlam güncellemede panel yerinde kaldı ve içindeki durum sürdü. Hatalı güncellemede panel yok oldu; panelin yerel durumu, odağı ve kaydırma konumu onunla birlikte gitti.

#### Sağlığa Dönüş

Veri düzeldiğinde ağaç yeniden panele döner. Dönüş, sökülen düğümlerin geri gelmesi değildir; kurulan düğümlerin yeniden doğmasıdır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderScreen } from "./screen.mjs";

const broken = renderScreen({ broken: true, total: 12 });
const healed = renderScreen({ broken: false, total: 12 });
countFates(reconcile(broken, healed));
```

```text
hata sinirinda yakalandi: veri okunamadi
korunan: 2
guncellenen: 0
kurulan: 2
sokulen: 1
komut: 3
```

Başlık ve kök korundu. `Fallback` söküldü ve panel ile değeri kuruldu. Üç komut gitti; yeni panel eskinin aynısı değil, yeni bir düğümdür. Panelin hatadan önceki durumu geri gelmez: sökülünce yok olmuştu. Hata sınırı ekranı ayakta tutar ama bozuk alt ağacın yaşamını sürdürmez.

#### Hata Sınırı Kaynakta

Gerçek bir hata sınırı, hata durumunu tutan bir bileşen sınıfıdır. Çocuk çizilirken hata çıkarsa sınıf hata durumuna geçer ve çocuk yerine yedek görünümü çizer. Model bunu bir `try` ve `catch` ile temsil eder. Aşağıdaki parça sınıfın kaynak biçimini gösterir; model onu çalıştırmaz.

```jsx
// Platform kodu: Boundary
class Boundary extends React.Component {
  state = { failed: false };

  static getDerivedStateFromError() {
    return { failed: true };
  }

  render() {
    return this.state.failed ? <Fallback /> : this.props.children;
  }
}
```

Sınıf, çocuklarını sarar. `getDerivedStateFromError` hata gelince durumu değiştirir ve sınıfın çizim işlevi yedek görünümü döndürür. Ekranda kullanımı bir sarmalamadır; yalnız sarılan alt ağaç bu sınırın korumasındadır.

```jsx
// Platform kodu: Summary
function Summary({ total }) {
  return (
    <View>
      <Text>Ozet</Text>
      <Boundary>
        <Panel total={total} />
      </Boundary>
    </View>
  );
}
```

Sınırın yeri, hatanın ne kadarını yutacağını belirler. Sınır yalnız paneli sararsa hata yalnız panelle sınırlı kalır; ekranın hepsini sararsa bir paneldeki hata bütün ekranı yedek görünümle değiştirir. Model bunu `renderScreen` içinde `withBoundary` çağrısının yeriyle gösterir: başlık sınırın dışındadır ve bu yüzden korunan kaldı.

#### Hata Sınırı Parçasını Satır Satır Okumak

Birinci parçada dört satır iş görür. `state = { failed: false }` sınırın başlangıçta hatasız olduğunu söyler. `getDerivedStateFromError()` alt ağaçta bir hata çıktığında çağrılır ve yeni durumu döndürür; burada durumu `failed: true` yapar. `this.state.failed ? <Fallback /> : this.props.children` ise iki yoldan birini seçer: hata varsa yedek görünüm, yoksa sarılan çocuklar.

Modeldeki `withBoundary` aynı kararı bir `try` ve `catch` ile verir. Fark şudur: modelde karar her çağrıda yeniden alınır, gerçek sınırda ise durum saklanır. Hata bir kez çıktıktan sonra gerçek sınır, sıfırlanana kadar yedek görünümü göstermeye devam eder. Bu yüzden bir yeniden dene düğmesi sınırın durumunu sıfırlayacak bir yol taşır.

İkinci parçada `<Boundary>` yalnız `<Panel />` ögesini sarar. `<Text>Ozet</Text>` sınırın dışındadır. Modeldeki başlığın korunan kalmasının nedeni budur: hata sınırın içinde çıktı ve sınırın dışına geçmedi.

#### Ayrım Cümlesini Bir Örnekle Okumak

M11/K05'in sorusu ile bu dersin sorusu benzer görünür ve yanlış okunabilir, bu yüzden ayrımı bir örnekle yeniden kurmak yararlıdır. Panelin iki ayrı sonu olabilir.

Birinci sonda panelin yalnız toplamı değişir. Panel aynı düğümdür, özelliği yeni değer alır ve akıbet güncellenendir. Panelin içindeki durum, örneğin açık bir ayrıntı satırı, yerinde kalır. Kimlik güncellemeyi aşmıştır.

İkinci sonda panelin çizimi hata verir. Panel artık ağaçta yoktur; yerine başka türden bir düğüm gelmiştir. Akıbet sökülendir ve açık ayrıntı satırı gibi içerideki her durum onunla birlikte gider. Aynı kullanıcı ekrana yeniden bakınca panelin eski hâlini bulamaz; sağlığa dönüşte yeni bir panel görür.

Her iki sonda da "panele bir şey oldu" denir, ama biri kimliği korur, öteki yitirir. Güncellenen kimliğini korur, sökülen yitirir; akıbet, bir nesnenin hâlini değil bu kimliğin akıbetini sınıflar.

#### Yaygın Yanılgılar

İlk yanılgı, hata sınırının hatayı çözdüğüdür. Sınır hatayı yakalar ve ekranı ayakta tutar, ama hatanın nedeni yerinde durur. Veri bozuksa bir sonraki güncellemede panel yine hata verir; sınır bunu yine yakalar. Sınır bir sigortadır, onarım değildir.

İkinci yanılgı, yedek görünümün sökülen düğümün yedeği olduğudur. Yedek görünüm yeni bir düğümdür; panelin durumunu taşımaz. Panelin neleri tuttuğu hatayla birlikte kaybolur. Durumu korumak isteyen bir uygulama onu sınırın dışında, daha üstte tutmalıdır.

Üçüncü yanılgı, sınırın her hatayı yakaladığıdır. Çizim sırasındaki hatalar yakalanır; dokunma işleyicisindeki ya da bir zamanlayıcıdaki hata yakalanmaz. Bu yüzden sınırın yokluğuna bakmak yerine hatanın nerede çıktığına bakılır.

#### Araçlar Ağacı Gösterir

Hata ayıklamanın günlük araçları üç gruba ayrılır. Geliştirici menüsü uygulamayı çalışırken yeniden yüklemek, günlükleri görmek ve inceleyiciyi açmak için bir kapıdır. Hata ayıklama inceleyicisi çalışan uygulamanın görünüm ağacını gösterir: her düğümün türünü, özelliklerini ve çocuklarını. Hata ayıklayıcı ise kodu satır satır durdurur ve değişkenleri gösterir.

Üçü de ağacı gösterir, ağacı değiştirmez. İnceleyici, bu derste `printOps`'un yaptığını yapar, ama gerçek ağaç üzerinde: hata verdiğinde panelin yerinde `Fallback` olduğunu gösterir, hata mesajı ise günlükte durur. Hatayı bulmak için ikisine birlikte bakılır. Ağaçta `Fallback` görülürse bir hata sınırı devreye girmiştir ve günlükte hatanın kaynağı yazılıdır. Ağaçta `Fallback` yoksa ve ekran düşmüşse sınır yoktur ya da hata sınırın yakalayamadığı bir yerde çıkmıştır.

Sınırın yakalamadığı yerler de vardır. Çizim sırasındaki hataları sınır yakalar. Bir dokunma işleyicisinde ya da bir zamanlayıcı geri çağrısında çıkan hata, çizim sırasında çıkmadığı için sınıra ulaşmaz; onu ayrıca yakalamak gerekir. Model yalnız çizim sırasındaki hatayı kurar, öteki durumlar düzyazıda kalır.

#### Sınırlayıcı Ölçüm

Son ölçüm iki güncellemeyi aynı panel üzerinde karşılaştırır. Birinde panelin özelliği değişir, ötekinde panel hata verir. Kural panelin yoluna süzülür. Yedek görünümün ve değer düğümünün akıbeti bu sayfada gösterilmez, çünkü soru panelin ne olduğudur.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./screen.mjs";

const before = renderScreen({ broken: false, total: 10 });
for (const broken of [false, true]) {
  console.log(broken ? "hatali guncelleme" : "saglam guncelleme");
  const after = renderScreen({ broken, total: 12 });
  const ops = reconcile(before, after);
  printOps(ops.filter((op) => op.path === "/Screen/Panel"));
}
```

```text
saglam guncelleme
guncellenen: /Screen/Panel
hatali guncelleme
hata sinirinda yakalandi: veri okunamadi
sokulen: /Screen/Panel
```

Odak düğüm `/Screen/Panel`. Sağlam güncellemede güncellenen çıktı: panel kimliğini korudu. Hatalı güncellemede sökülen çıktı: panel kimliğini yitirdi. Dersin akıbeti ikincisidir.

Bu ölçümün sınırı, sökülenin yerine gelenin yedek görünüm olmasıdır. Kural `Fallback` düğümünü kurulan sayar, ama o düğümün kullanıcıya ne gösterdiğini bilmez. Yedek görünüm "bir sorun oluştu" yazabilir, bir yeniden dene düğmesi taşıyabilir ya da boş kalabilir; üçü de kural için aynı kayıttır. Hata ayıklamada işe yarayan şey de budur: ağaç, hatanın nerede durduğunu söyler; hatanın ne olduğunu günlük ve hata ayıklayıcı söyler.

**BU4.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde hata sınırı bir `try` ve `catch` ile sarılmış çizim işlevidir; hata, işlevin içinden atılır ve sınır onu iş diliyle tek satır yazar. Gerçek bir hata sınırı, hata durumunu tutan ve çocuklarını çizen bir sınıftır; hatanın yakalandığı yer, hata durumunun sıfırlanma biçimi ve hatanın günlüğe nasıl gittiği model dışındadır. Modelde hata her güncellemede yeniden kurulur; gerçek sınır hata durumunu bir sonraki güncellemeye taşıyabilir ve yeniden dene düğmesiyle sıfırlanabilir.

#### Özet

- Bu dersin akıbeti: sökülen. `/Screen/Panel` düğümü, alt ağacı hata verince söküldü; yerine `/Screen/Fallback` kuruldu.
- Aynı panel özellik değişince güncellenen çıktı ve kimliğini korudu; hata verince sökülen çıktı ve kimliğini yitirdi.
- Hata ekranın geri kalanına geçmedi: başlık ve kök korunan kaldı.
- Sağlığa dönüşte panel yeni bir düğüm olarak kuruldu; sökülenin durumu geri gelmedi.
- Hata ayıklama araçları ağacı ve hatayı gösterir, ağacı değiştirmez; kural yedek görünümün ne gösterdiğini görmez.

#### Sonraki Adım

Bu derste hata bir sınırla yakalandı ve ağaçtaki etkisi ölçüldü. Bir hatayı yakalamak da bir işi doğrulamak da aynı gereksinimden gelir: ağacın beklenen biçimi bilinmelidir. Sıradaki ders, `Test`, beklenen kayıt listesini `reconcile` çıktısıyla karşılaştırır ve bir güncellemenin doğrulandığını ölçer.

### Test

Source: https://academia.sh/tr/kurslar/react-native/test

Bir test, güncellemenin beklenen kayıt listesini gerçek kayıt listesiyle karşılaştırır. Ders düğmeye basılınca metnin güncellendiğini ve testin neyi göremediğini ölçer.

Önceki ders hatanın ağaçta bıraktığı izi ölçtü. Hata ayıklama bir şeyin yanlış gittiğini sonradan bulmaktır; test ise doğru gitmesi gereken şeyi önceden yazıp her değişiklikte denetlemektir. Bu ders bir güncellemenin beklenen sonucunu kayıt listesi olarak yazar ve gerçek listeyle karşılaştırır.

Kursun sorusu: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Test edilecek model bir sayaç ekranıdır: bir metin ve bir düğme. Düğmeye basmak sayacı bir artırır ve metin yeni sayıyı gösterir. Ekranı `renderCounter(count)` kurar. Test yardımcısı `check` bir adı, beklenen kayıtları ve eski ile yeni ağacı alır. Beklenen kayıtlar `korunan` dışındaki akıbetlerin `akıbet: yol` biçiminde yazılmış hâlidir; `check` gerçek kayıtları aynı biçime çevirir, karşılaştırır ve `GECTI` ya da `KALDI` yazar.

```javascript
// counter.mjs — sayac ekranini gorunum agacina cevirir
import { el } from "./fate.mjs";

export function renderCounter(count) {
  return el("Counter", {}, [
    el("Text", { value: count }),
    el("Button", { label: "Artir" }),
  ]);
}
```

```javascript
// check.mjs — beklenen kayit listesini gercek kayit listesiyle karsilastirir
import { reconcile } from "./fate.mjs";

export function check(name, expected, before, after) {
  const actual = reconcile(before, after)
    .filter((op) => op.fate !== "korunan")
    .map((op) => `${op.fate}: ${op.path}`);
  const passed = JSON.stringify(actual) === JSON.stringify(expected);
  console.log(`${passed ? "GECTI" : "KALDI"}: ${name}`);
  if (!passed) {
    console.log(`beklenen: ${expected.join(" | ") || "bos"}`);
    console.log(`gercek: ${actual.join(" | ") || "bos"}`);
  }
}
```

`renderCounter` durumdan bir ağaç üretir. `check` korunan kayıtları atar, çünkü bir güncellemenin sorusu ne değiştiğidir; değişmeyenleri saymak testi kırılgan yapar. Beklenen liste de bu yüzden yalnız değişenleri yazar.

#### Düğmeye Basmak

İlk testte kullanıcı düğmeye basar. Sayaç sıfırdan bire geçer. Beklenen: yalnız metin düğümü güncellenir, düğme ve kök korunur.

```javascript
// main.mjs
import { renderCounter } from "./counter.mjs";
import { check } from "./check.mjs";

check("dugmeye basmak metni gunceller", ["guncellenen: /Counter/Text"], renderCounter(0), renderCounter(1));
```

```text
GECTI: dugmeye basmak metni gunceller
```

Test geçti. Gerçek güncelleme tek kayıt verdi: `/Counter/Text` güncellenen. Düğmenin etiketi değişmediği için düğme korunan kaldı ve beklenen listeye girmedi.

#### Değişmeyen Güncelleme

İkinci testte hiçbir şey değişmez: aynı sayaç değeriyle ağaç yeniden kurulur. Beklenen liste boştur. Yeniden çizim komut değildir; bu test bunu bir kural olarak sabitler.

```javascript
// main.mjs
import { renderCounter } from "./counter.mjs";
import { check } from "./check.mjs";

check("ayni deger komut vermez", [], renderCounter(3), renderCounter(3));
```

```text
GECTI: ayni deger komut vermez
```

Boş liste de bir beklentidir. Test, gereksiz bir güncellemenin ağaca komut göndermediğini sabitler; ileride biri sayaca her çizimde değişen bir özellik eklerse bu test kalır.

#### Kalan Test

Üçüncü testte beklenti yanlıştır: yazar düğmenin de güncellendiğini sanmıştır. Gerçek güncelleme yalnız metni değiştirir. Test kalır ve iki listeyi alt alta yazar.

```javascript
// main.mjs
import { renderCounter } from "./counter.mjs";
import { check } from "./check.mjs";

const expected = ["guncellenen: /Counter/Text", "guncellenen: /Counter/Button"];
check("yanlis beklenti", expected, renderCounter(0), renderCounter(1));
```

```text
KALDI: yanlis beklenti
beklenen: guncellenen: /Counter/Text | guncellenen: /Counter/Button
gercek: guncellenen: /Counter/Text
```

Kalan testin çıktısı hatanın yerini söyler: beklenen listede fazladan bir kayıt var. Bu, iki yönde de bir bulgudur. Ya beklenti yanlıştır, ya da uygulama düğmeyi güncellemesi gerekirken korumuştur. Test hangisi olduğuna karar vermez; yalnız farkı gösterir.

#### Düğüm Değişince Beklenti

Şimdiye kadar yalnız özellik değişti. Gerçek bir ekran, sınıra ulaşıldığında düğmenin yerine bir uyarı koyabilir. Bu durumda testin beklentisi başka bir akıbet türüne geçer: düğme sökülür, uyarı kurulur. Aşağıdaki ölçümde iki ağaç doğrudan `el` ile kurulur ve beklenen liste iki kayıt taşır.

```javascript
// main.mjs
import { el } from "./fate.mjs";
import { check } from "./check.mjs";

const before = el("Counter", {}, [el("Text", { value: 9 }), el("Button", { label: "Artir" })]);
const after = el("Counter", {}, [el("Text", { value: 10 }), el("Limit", { text: "Sinira ulasildi" })]);
const expected = ["guncellenen: /Counter/Text", "sokulen: /Counter/Button", "kurulan: /Counter/Limit"];
check("sinira ulasinca dugme yerini uyariya birakir", expected, before, after);
```

```text
GECTI: sinira ulasinca dugme yerini uyariya birakir
```

Test geçti ve üç kaydın sırası beklenenle örtüştü. Burada iki şey görülür. Birincisi, beklenen liste yalnız değişenleri yazsa da bir güncellemenin birden çok akıbeti olabilir: metin güncellendi, düğme söküldü, uyarı kuruldu. İkincisi, `check` listeleri sıralı karşılaştırır. Aynı üç kayıt başka bir sırayla yazılsaydı test, uygulama doğru olduğu hâlde kalırdı. Kayıtların sırası kuralın bir sonucudur; testi yazan kişi sırayı kuraldan öğrenir, tahmin etmez.

Bu ölçüm bir karşı örnektir: önceki testler tek kayıtlıydı ve "test bir kaydı denetler" izlenimi verebilirdi. Test bir kayıt listesini denetler; liste bir kayıt kadar da uzun olabilir, üç kayıt kadar da.

#### Yaygın Yanılgı

İlk yanılgı, geçen bir testin uygulamanın doğru olduğunu kanıtladığı sanısıdır. Geçen test yalnız yazılan beklentinin tuttuğunu söyler. Beklenti eksikse ya da yanlışsa uygulama yanlış kalır ve test yine geçer. Yukarıdaki üçüncü testte durum tersiydi: beklenti yanlıştı ve test uygulamayı değil beklentiyi suçladı.

İkinci yanılgı, her şeyi yazmanın testi sağlamlaştırdığı sanısıdır. `check` bu yüzden `korunan` kayıtlarını atar. Ağaçtaki her düğümün akıbetini listeye yazan bir test, ekrana yeni bir düğüm eklendiği anda kalır; oysa eklenen düğüm denetlenen davranışla ilgisiz olabilir. Sağlam test, denetlediği davranışın bıraktığı kayıtları yazar ve ötesini sessiz bırakır.

Üçüncü yanılgı, ağaç testinin yerel tarafı da doğruladığı sanısıdır. Ağaç testi, JavaScript tarafının ağaca ne bıraktığını denetler; ekranda ne göründüğünü denetlemez. Bu ayrım dersin sınırlayıcı ölçümünde somutlaşır.

#### Bileşen Testi ve Uçtan Uca Test

Yukarıdaki testler ağaca bakar: bir bileşen testi bir ekranı kurar, bir olayı tetikler ve ağacın nasıl değiştiğini denetler. Hızlı çalışır ve gerçek bir telefon istemez, çünkü yerel görünüm yoktur; yalnız ağaç vardır. Aşağıdaki parça böyle bir testin kaynak biçimini gösterir; model onu çalıştırmaz.

```jsx
// Platform kodu: Counter testi
test("dugmeye basmak metni gunceller", () => {
  render(<Counter />);
  fireEvent.press(screen.getByText("Artir"));
  expect(screen.getByText("1")).toBeTruthy();
});
```

Uçtan uca test ise gerçek uygulamayı gerçek bir ekranda açar, düğmeye gerçekten dokunur ve ekrana bakar. Ağaçta görünmeyen şeyleri de görür: yerel tarafın çizdiği piksel, klavyenin açılması, izin penceresi. Yavaştır ve kırılgandır, bu yüzden azdır. Bir ekran düzyazıda anlatılır; model ağaca bakar ve ekran ağaç değildir.

#### Kaynak Parçasını Satır Satır Okumak

Bileşen testinin kaynak parçası beş satırdır ve her satırın modelde bir karşılığı vardır.

- `render(<Counter />)` ekranı bir test ağacına kurar. Modelde bu, `renderCounter(0)` ile ilk ağacı üretmeye denk gelir. Gerçek yerel görünüm kurulmaz.
- `fireEvent.press(screen.getByText("Artir"))` önce ağaçta etiketi "Artir" olan düğümü bulur, sonra ona bir basma olayı gönderir. Modelde olay yoktur; basmanın sonucu `renderCounter(1)` çağrısıyla temsil edilir.
- `expect(screen.getByText("1")).toBeTruthy()` olaydan sonra ağaçta "1" metnini taşıyan bir düğümün bulunduğunu söyler. Modeldeki karşılığı, `/Counter/Text` düğümünün güncellenen çıkmasıdır.

Aralarındaki fark önemlidir. Kaynak parçası sonucu içerik üzerinden denetler: "1" metni ağaçta var mı? Model ise akıbet üzerinden denetler: metin düğümü güncellendi mi? Sorgu, düğümün yerinde güncellendiğini mi yoksa söküp yeniden mi kurulduğunu mu ayırt etmez; ikisi de ağaçta "1" bırakır. Akıbeti denetlemek isteyen test, sorguya ek olarak düğümün kimliğini de saklamalıdır. Bu dersin kural tarafı o ayrımı görünür kılar.

#### Testin Adı

Testin adı kısa bir cümledir ve neyin sınandığını söyler: "düğmeye basmak metni günceller" bir davranışı adlandırır, "test 1" adlandırmaz. İyi ad, test kaldığında okuyanın kodu açmadan neyin bozulduğunu anlamasını sağlar. Bu derste kalan test bu yüzden "yanlış beklenti" adını taşır: ad, hatanın uygulamada değil beklentide olduğunu baştan söyler. Ad ayrıca testin neyi sınamadığını da dürüstçe sınırlar: "metni günceller" diyen bir test düğmenin etiketi hakkında bir şey söylemez, ve bunu başka bir test üstlenir.

#### Beklenti Nasıl Yazılır

Beklenen listeyi yazmanın bir sırası vardır ve sıra, listeyi uygulamadan kopyalamaktan korur. Önce güncellemenin kullanıcıya ne vaat ettiği düzyazıyla yazılır: "düğmeye basılınca sayı bir artar." Sonra bu vaadin ağaçta hangi düğümlere dokunması gerektiği düşünülür: sayıyı gösteren düğüm değişir, düğme ve kök değişmez. Ancak bundan sonra liste yazılır ve `check` çalıştırılır.

Sırayı ters çevirip önce `check`'i koşturmak ve çıkan gerçek listeyi beklenen olarak yapıştırmak testi hemen geçirir, ama hiçbir şey sınamaz. Böyle bir test uygulamanın bugünkü davranışını dondurur; bugünkü davranış yanlışsa yanlışı da dondurur. Kalan test bu yüzden zararsız değil, değerlidir: beklenti ile gerçek arasındaki fark, bir insanın iki şeyi ayrı ayrı düşündüğünün kanıtıdır.

#### Hangi Testi Nerede Yazmalı

Üç ölçüm de ağaç düzeyindeydi. Pratikte hangi sorunun hangi testle sorulacağı şöyle ayrılır.

- Bir olayın hangi düğümü değiştirdiği sorusu ağaç düzeyindedir. Bileşen testi bunu hızlı ve kararlı yanıtlar; her değişiklikte koşabilir.
- Bir düğümün kimliğini koruyup korumadığı da ağaç sorusudur ama sorgu ile görünmez. Güncellenen ile sökülen arasındaki fark, ancak akıbet ölçen bir araçla ya da düğümün kimliğini saklayan bir testle yakalanır.
- Ekranda ne göründüğü, klavyenin açılıp açılmadığı, bir izin penceresinin çıkıp çıkmadığı ağaç dışıdır. Bunlar uçtan uca testin işidir.

Uçtan uca test yavaştır; her dokunuşta gerçek bir uygulama açılır. Bu yüzden az sayıda ve en önemli yolları kapsayan testlerle sınırlı tutulur. Ağaç düzeyindeki testler çoğunluğu oluşturur. Bu dağılım, hataların çoğunun ağaçta yakalanabilmesine ve ağaçtaki hatanın ucuza bulunmasına dayanır.

#### Sınırlayıcı Ölçüm

Son ölçüm testin neyi göremediğini gösterir. Modelde yazı rengi yoktur; ağaçta olmayan bir ayrıntı kural için de yoktur. Aynı sayaç değeriyle kurulan iki ekran, renkleri ne olursa olsun, aynı ağacı üretir. Aşağıdaki ölçüm bu iki ekranı karşılaştırır ve süzgeci metin düğümüne çevirir.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderCounter } from "./counter.mjs";

const before = renderCounter(1);
const after = renderCounter(1);
const ops = reconcile(before, after);
printOps(ops.filter((op) => op.path === "/Counter/Text"));
```

```text
korunan: /Counter/Text
```

Odak düğüm `/Counter/Text`. İki ekran ağaç olarak aynıdır ve metin düğümü korunan çıktı. Bir test yazı rengi gibi ağaca girmeyen bir farkı bilemez: kural özelliğe bakar, pikselde ne olduğuna bakmaz. Rengin doğru olduğunu denetlemek isteyen bir test, rengi ağaca özellik olarak taşımalı ya da uçtan uca testle ekrana bakmalıdır.

Sayaç bire geçtiğinde aynı süzgeçle `/Counter/Text` güncellenen çıkar (ilk ölçüm). Dersin akıbeti budur: bir güncellemeyi sınayan test, doğrulanan şeyi güncellenen düğümün yolunda arar.

**BU5.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde test bir kayıt listesi karşılaştırmasıdır ve `korunan` kayıtları atar. Gerçek bir bileşen testi bir metni, bir erişilebilirlik etiketini ya da bir rolü sorgular; sorgu ağacın bir düğümünü bulur, ama bulduğu düğümün güncellendiğini ya da yeniden kurulduğunu söylemez. Modelde ölçülen beklentiler gerçek bir testin sorgularına bire bir karşılık gelmez.

#### Özet

- Bu dersin akıbeti: güncellenen. Düğmeye basılınca `/Counter/Text` düğümü güncellendi; düğme ve kök korunan kaldı.
- Beklenen kayıt listesi `korunan` dışındaki akıbetleri yazar; `check` bunu gerçek listeyle karşılaştırıp `GECTI` ya da `KALDI` verir.
- Değişmeyen bir güncellemenin beklentisi boş listedir; yeniden çizim komut değildir.
- Beklenti önce düzyazıyla, sonra kayıt listesi olarak yazılır; gerçek listeyi beklenen diye yapıştırmak hiçbir şey sınamaz.
- Bileşen testi ağaca bakar; ağaca girmeyen bir ayrıntıyı, örneğin yazı rengini, ancak uçtan uca test görür.

#### Sonraki Adım

Testler ağaca bakar ama ağacın altındaki yerel katmana inmez. Sıradaki ders, `Yerel Modül Yazımı`, ağacın bir düğümünün arkasında platforma özgü kod yazıldığında güncellemenin ağaçta ne yaptığını ölçer ve kursun son dersi `Derleme ve Yayın`a köprü kurar.

### Yerel Modül Yazımı

Source: https://academia.sh/tr/kurslar/react-native/yerel-modul-yazimi

Yerel modül, JavaScript tarafının platforma özgü kodu çağırdığı köprüdür. Ders yerel bir harita görünümünün ağaçta tek düğüm olduğunu ve merkez değişince bu düğümün güncellendiğini ölçer.

Önceki ders ağaca bakan testleri ölçtü. Ağacın altında bir katman daha vardır: çerçevenin hazır sunmadığı bir özellik gerektiğinde, örneğin bir harita ya da bir sensör, platforma özgü kod yazılır ve JavaScript tarafına açılır. Bu ders, böyle bir kodun ağaçta kaç düğüm olduğunu ve bir özellik değişince ona ne olduğunu ölçer.

Kursun sorusu: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Yerel modül iki parçadır. Birincisi platform tarafındaki koddur: iOS için Swift ya da Objective-C, Android için Kotlin. İkincisi JavaScript tarafındaki köprüdür: ad ile bir yerel işlevi çağırır ve sonucu geri alır. Aşağıdaki dosya köprüyü ve tek bir yerel modülü modeller. Çağrı her seferinde bir satır yazar, böylece ne zaman yerel tarafa inildiği görünür. Ekran, yerel harita görünümünü `NativeMap` adlı tek bir düğümle ağaca koyar.

```javascript
// bridge.mjs — yerel modulu ve onu cagiran koprunun modeli
import { el } from "./fate.mjs";

const MODULES = {
  Geo: { label: (lat, lng) => `${lat},${lng}` },
};

export function callNative(moduleName, fnName, ...args) {
  console.log(`yerel cagri: ${moduleName}.${fnName}`);
  return MODULES[moduleName][fnName](...args);
}

export function renderScreen(center) {
  const label = callNative("Geo", "label", center.lat, center.lng);
  return el("Screen", {}, [
    el("Header", { title: "Harita" }),
    el("NativeMap", { label }),
  ]);
}
```

`MODULES` yerel tarafın yerine geçer: `Geo` modülünün tek bir işlevi vardır ve konumdan bir etiket üretir. `callNative` modül ve işlev adıyla bu işlevi bulur, çağrıyı yazar ve sonucu döndürür. `renderScreen` merkezden etiketi alır ve haritayı tek bir düğüm olarak ağaca koyar.

#### İlk Açılış

Ekran ilk kez kurulduğunda ağaçta ne olduğuna bakılır. Önceki ağaç yoktur, bu yüzden her düğüm kurulandır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./bridge.mjs";

printOps(reconcile(null, renderScreen({ lat: 41, lng: 29 })));
```

```text
yerel cagri: Geo.label
kurulan: /Screen
kurulan: /Screen/Header
kurulan: /Screen/NativeMap
```

Yerel çağrı bir kez yapıldı. Ağaçta üç düğüm kuruldu; harita bunlardan biri. Haritanın içinde pek çok şey olabilir: katmanlar, işaretçiler, kaydırma ve yakınlaştırma durumu. Ağaç bunların hiçbirini görmez; kural için harita tek bir düğümdür.

#### Merkez Değişince

Kullanıcı haritayı başka bir yere taşır. JavaScript tarafı yeni merkezi verir ve ekran yeniden kurulur. Haritanın türü ve anahtarı aynı kalır; yalnız etiketi değişir.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./bridge.mjs";

const before = renderScreen({ lat: 41, lng: 29 });
const after = renderScreen({ lat: 39, lng: 32 });
printOps(reconcile(before, after));
```

```text
yerel cagri: Geo.label
yerel cagri: Geo.label
korunan: /Screen
korunan: /Screen/Header
guncellenen: /Screen/NativeMap
```

İki ekran kuruldu ve iki yerel çağrı yapıldı. Harita güncellenen çıktı; başlık ve kök korunan kaldı. Güncellenen düğüm kimliğini korur: yerel harita görünümü yeniden yaratılmadı, aynı görünüm yeni özelliği aldı. Haritanın kaydırma ve yakınlaştırma durumu böylece sürer.

#### Aynı Merkez

Merkez değişmezse ağaç komut vermez. Bu, yerel çağrının ağaçtaki etkisini yerel çağrının sayısından ayırır.

```javascript
// main.mjs
import { reconcile, countFates } from "./fate.mjs";
import { renderScreen } from "./bridge.mjs";

const before = renderScreen({ lat: 41, lng: 29 });
const after = renderScreen({ lat: 41, lng: 29 });
countFates(reconcile(before, after));
```

```text
yerel cagri: Geo.label
yerel cagri: Geo.label
korunan: 3
guncellenen: 0
kurulan: 0
sokulen: 0
komut: 0
```

İki yerel çağrı yapıldı ama sıfır komut gitti. Yerel çağrı ile ağaç komutu aynı şey değildir: çağrı yerel tarafa inmek, komut ise ağaçta bir düğümü değiştirmektir. Bir çağrı komut doğurabilir de doğurmayabilir de.

#### Harita Ekrandan Çıkınca

Şimdiye kadar harita hep ağaçtaydı. Kullanıcı ekranı değiştirirse harita ağaçtan çıkar. Bu, yerel görünümün ömrünün ağaçta nerede bittiğini gösteren karşı örnektir.

```javascript
// main.mjs
import { el, reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./bridge.mjs";

const before = renderScreen({ lat: 41, lng: 29 });
const after = el("Screen", {}, [el("Header", { title: "Harita" })]);
printOps(reconcile(before, after));
```

```text
yerel cagri: Geo.label
korunan: /Screen
korunan: /Screen/Header
sokulen: /Screen/NativeMap
```

Başlık ve kök korunan kaldı; harita sökülen çıktı. Güncellenen harita kimliğini korurdu, sökülen harita yitirir: yerel görünümün kaydırma ve yakınlaştırma durumu onunla birlikte gider. Kullanıcı haritaya geri dönerse yeni bir düğüm kurulur ve harita ilk açıldığı hâlinden başlar. Yerel çağrı yalnız bir kez yapıldı, çünkü yeni ağacı kurmak için modüle inmek gerekmedi.

#### Yerel Katmanın Kaynakta Görünüşü

Yerel modülün platform tarafı, ağacın hiç görmediği bir koddur. Aşağıdaki iki parça aynı `Geo` modülünün iOS ve Android biçimini kısaca gösterir; model onları çalıştırmaz.

```swift
// Platform kodu: GeoModule (iOS)
@objc(GeoModule)
class GeoModule: NSObject {
  @objc func label(_ lat: Double, lng: Double,
                   resolve: RCTPromiseResolveBlock,
                   reject: RCTPromiseRejectBlock) {
    resolve("\(lat),\(lng)")
  }
}
```

```kotlin
// Platform kodu: GeoModule (Android)
class GeoModule(context: ReactApplicationContext) :
  ReactContextBaseJavaModule(context) {
  override fun getName() = "Geo"

  @ReactMethod
  fun label(lat: Double, lng: Double, promise: Promise) {
    promise.resolve("$lat,$lng")
  }
}
```

İki parça aynı sözleşmeyi taşır: modülün bir adı vardır, işlev iki sayı alır ve sonucu bir söz nesnesiyle JavaScript tarafına döndürür. Platformlar arasındaki bu ikilik, M32/K01'deki dağılım sorusunun bir örneğidir: kodun bir kısmı iki kez yazılır. Ağaç bu ikiliği görmez; iki platformda da ekran `NativeMap` düğümüyle kurulur.

#### Kaynak Parçalarını Satır Satır Okumak

İki parça aynı sözleşmeyi iki dilde yazar; satırlar birebir eşleşir.

- `@objc(GeoModule)` ile `getName() = "Geo"` modülün JavaScript tarafında hangi adla görüneceğini belirler. Model bunu `MODULES` nesnesinin `Geo` anahtarıyla gösterir. Ad iki platformda aynı olmazsa JavaScript tarafı modülü bulamaz.
- `label(lat, lng, ...)` işlevin kendisidir. İki parçada da iki sayı alır; Swift tarafında ek olarak çözme ve reddetme blokları, Kotlin tarafında bir söz nesnesi vardır. Modeldeki `label: (lat, lng) => ...` bunların yalnız ortak çekirdeğidir.
- `resolve("\(lat),\(lng)")` ve `promise.resolve("$lat,$lng")` sonucu JavaScript tarafına geri verir. Model `return` ile aynı işi yapar; fark, gerçekte sonucun bir söz nesnesiyle ve eşzamansız dönmesidir.

Satırların gösterdiği şey şudur: yerel modülün yazımı, sözleşmeyi her iki tarafta da aynı tutmaktan ibarettir. Ad, parametre sırası ve dönen değer iki platformda ayrışırsa hata ağaçta değil, köprüde çıkar.

#### Yaygın Yanılgılar

İlk yanılgı, yerel modülün bir görünüm olduğudur. Yukarıdaki `Geo` modülü ekranda hiçbir şey çizmez; yalnız bir işlev sunar. Görünüm ise ayrı bir iştir: haritayı çizen yerel görünüm, ağaçta `NativeMap` düğümüyle temsil edilir. Bir modül görünümsüz de olabilir, görünüm de modülsüz bir özellik kümesi olabilir.

İkinci yanılgı, her yerel çağrının komut doğurduğudur. Aynı Merkez ölçümü iki çağrının sıfır komut verdiğini gösterdi. Çağrı yerel tarafa inmektir; komut ise ağaçtaki bir düğümün değişmesidir. Çağrı sayısını azaltmak komut sayısını doğrudan azaltmaz.

Üçüncü yanılgı, yerel görünümün içinin ağaçta göründüğüdür. Harita yüzlerce katman çizse bile ağaçta tek düğümdür. Katmanların her biri için akıbet aramak boşunadır: kural yalnız ağaçtaki düğümleri sınıflar.

#### Modül mü, Görünüm mü

Yerel kod yazmaya karar vermeden önce iki soru sorulur. Birincisi: gereken şey bir işlev mi, bir görünüm mü? Konumu okumak, bir dosyayı şifrelemek ya da bir sensörün değerini almak işlevdir; bunlar ekranda yer kaplamaz ve ağaçta düğümleri yoktur. Bir harita, bir kamera önizlemesi ya da bir video oynatıcı görünümdür; ekranda yer kaplar ve ağaçta bir düğümle temsil edilir.

İkincisi: bu özellik gerçekten yerel koda mı ihtiyaç duyuyor? Platformun sunduğu ve çerçevenin zaten sardığı bir özellik için yeni bir modül yazmak, iki platformda iki kod tabanı bakımı demektir. M32/K01'deki dağılım sorusu burada geri gelir: yerel kod yazılan her özellik, kodun paylaşılmayan kısmını büyütür. Bu yüzden modül yazmak, hazır bir çözüm yokken ve özellik gerçekten gerekli olduğunda tercih edilir.

İki sorunun cevabı ağaçtaki izi de belirler. Görünümsüz bir modül ağaca hiç dokunmaz; yalnız çağrılırken yerel tarafa iner. Görünümlü bir modül ağaçta bir düğüm bırakır ve o düğümün ömrü, bu dersin ölçümlerinin gösterdiği gibi, kurulan, güncellenen ve sökülen akıbetleriyle yazılır.

#### Köprünün Sınırı

Köprü iki dünya arasında bir geçittir ve her geçişin bir sözleşmesi vardır. JavaScript tarafı yalnız ad ve basit değerler verebilir; yerel taraf yalnız basit değerlerle ya da bir söz nesnesiyle cevap verebilir. İki tarafın nesneleri birbirine doğrudan geçmez. Bu yüzden modeldeki `label` işlevi bir metin döndürür, bir nesne değil.

Sözleşme bozulduğunda hata çoğu kez çalışma sırasında, çağrı anında ortaya çıkar. Modelde `MODULES[moduleName][fnName]` var olmayan bir adla çağrılırsa JavaScript hata verir; gerçek köprüde de modül bulunamazsa çağrı başarısız olur. Bu hatayı yakalayan, bir önceki derste kurulan hata sınırıdır; ama sınır yalnız çizim sırasındaki hataları yakalar. Çağrı bir dokunma işleyicisinden yapılırsa hata ayrıca yakalanmalıdır.

Bu sınır ağaçtaki izi de açıklar. Köprüden geçen değer ağaca ancak bir özellik olarak girer; modelde `label` önce yerel çağrıdan döner, sonra `NativeMap` düğümünün özelliği olur. Ağaç çağrının kendisini değil, çağrının ağaca bıraktığı değeri görür. Aynı değer gelirse düğüm korunan kalır; farklı bir değer gelirse güncellenen olur. Bu yüzden köprüden dönen değeri sade tutmak, ağaçtaki gereksiz güncellemeleri de önler.

#### Sınırlayıcı Ölçüm

Son ölçüm yerel katmanın iç ayrıntılarının kuralda akıbet almadığını gösterir. Merkez değişir; kural yalnız `/Screen/NativeMap` yoluna süzülür. Haritanın içindeki katmanlar ve işaretçiler için ağaçta düğüm yoktur, bu yüzden onlar için bir kayıt da yoktur.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { renderScreen } from "./bridge.mjs";

const before = renderScreen({ lat: 41, lng: 29 });
const after = renderScreen({ lat: 39, lng: 32 });
const ops = reconcile(before, after);
printOps(ops.filter((op) => op.path === "/Screen/NativeMap"));
console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
```

```text
yerel cagri: Geo.label
yerel cagri: Geo.label
guncellenen: /Screen/NativeMap
komut: 1
```

Odak düğüm `/Screen/NativeMap`. Güncellenen çıktı ve tek komut gitti. Haritanın içinde kaç katmanın çizildiği ya da kaç işaretçinin taşındığı bu sayıya girmez: o işi yerel taraf yapar, ağaç yalnız harita düğümünün özelliğinin değiştiğini bilir.

Bu sınırın bir sonucu da şudur: yerel katmandaki bir hatayı ağaç göstermez. Harita yanlış bir yeri gösterse de, özelliği doğru geldiği sürece ağaçta hiçbir şey ters görünmez. Yerel katmanın hatası yerel araçlarla aranır.

Bunun sonucu şudur: yerel görünüm pahalıysa maliyet ağaçtaki komut sayısında görünmez. Kural harita düğümünün özelliğinin değiştiğini söyler; yerel tarafın o değişimle ne yaptığını söylemez. Ağaç sayısı ile yerel işin maliyeti ayrı ölçülür.

**BU6.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde yerel modül bir nesne içindeki işlevdir ve çağrı eşzamanlı bir işlev çağrısıdır. Gerçek bir yerel modül ayrı bir iş parçacığında koşabilir, sözle döner ve platform kodunun derlenmesini gerektirir; çağrının ne kadar sürdüğü ve hangi iş parçacığında koştuğu model dışındadır. Modelde harita tek bir etiketle temsil edilir; gerçek bir yerel görünümün özellik kümesi çok daha geniştir ve her özelliğin değişimi kendi kuralını taşır.

#### Özet

- Bu dersin akıbeti: güncellenen. Merkez değişince `/Screen/NativeMap` düğümü güncellendi ve tek komut gitti.
- Yerel görünüm ağaçta tek düğümdür; içindeki katmanlar, işaretçiler ve durum kural için akıbet almaz.
- Yerel çağrı komut değildir: iki çağrı sıfır komut verebilir.
- Aynı modül iOS ve Android için ayrı yazılır; ağaçta ikisi de aynı düğümle görünür.

#### Sonraki Adım

Yerel kod yazılınca uygulama artık yalnız JavaScript değildir: platform tarafı derlenir, imzalanır ve mağazaya gider. Sıradaki ders, `Derleme ve Yayın`, kursun kapanışıdır ve bu yolu baştan sona ele alır.

### Derleme ve Yayın

Source: https://academia.sh/tr/kurslar/react-native/derleme-ve-yayin

Geliştirmede hızlı yenileme düğümü yerinde güncellerken tam yeniden yükleme kökü söker. Ders bu farkı ölçer ve yirmi dersin akıbetlerini tek tabloda toplar.

Önceki ders, yerel modülün ağaçta tek düğüm olduğunu ölçtü. Yerel kod yazılınca uygulama yalnız JavaScript olmaktan çıkar: platform tarafı derlenir, imzalanır ve mağazaya gider. Bu ders kursun kapanışıdır. Önce geliştirme sırasında bir bileşen dosyası değişince ağaçta ne olduğuna bakar, sonra yayın hattını kısaca ayırır, en sonda yirmi dersin akıbetini tek tabloda toplar.

Kursun sorusu: **bu güncelleme görünüm ağacındaki düğüme ne yapıyor?** Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.

```javascript
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir
export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "kurulan", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "sokulen", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "sokulen", ops);
    whole(next, path, "kurulan", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
}
```

Geliştirme oturumunun modeli ayrı bir dosyadadır. Ekranın kaynağı `source` nesnesidir: kök düğümün tipi ve başlık metni. `buildTree` kaynaktan ağacı kurar. Sayaç oturumun durumudur ve ağaca `Counter` düğümünün özelliği olarak girer. İki yükleme yolu vardır. `fastRefresh` kaynağı değiştirir ama kök anahtarını korur. `fullReload` kök anahtarını bir artırır ve sayacı sıfırlar.

```javascript
// dev.mjs — gelistirme oturumunun modeli
import { el, reconcile } from "./fate.mjs";

export function buildTree(source, count, rootKey) {
  return el(source.root, {}, [
    el("Header", { title: source.title }),
    el("Counter", { count }),
  ], rootKey);
}

export function openSession(source) {
  return { source, count: 0, rootKey: null, tree: buildTree(source, 0, null) };
}

export function press(session) {
  const count = session.count + 1;
  return { ...session, count, tree: buildTree(session.source, count, session.rootKey) };
}

export function fastRefresh(session, source) {
  const draft = buildTree(source, session.count, session.rootKey);
  const ops = reconcile(session.tree, draft);
  const lost = ops.some((op) => op.fate === "sokulen" && op.path.endsWith("/Counter"));
  const count = lost ? 0 : session.count;
  const tree = lost ? buildTree(source, 0, session.rootKey) : draft;
  return [{ ...session, source, count, tree }, ops];
}

export function fullReload(session, source) {
  const rootKey = (session.rootKey ?? 0) + 1;
  const tree = buildTree(source, 0, rootKey);
  return [{ source, count: 0, rootKey, tree }, reconcile(session.tree, tree)];
}
```

Modelin tek kuralı durumun kimliğe bağlı olmasıdır: sayaç, ancak `Counter` düğümü güncellemeyi aşıp kimliğini koruduysa yaşar. `fastRefresh` bunu `lost` ile sınar. Durumu ayrıca saklamak ya da kopyalamak yerine ağacın karşılaştırma sonucuna bakar.

#### İlk Açılış

Oturum ilk açıldığında önceki ağaç yoktur. Her düğüm kurulandır ve sayaç sıfırdır.

```javascript
// main.mjs
import { reconcile, printOps } from "./fate.mjs";
import { openSession } from "./dev.mjs";

const session = openSession({ root: "App", title: "Ana ekran" });
printOps(reconcile(null, session.tree));
console.log(`sayac: ${session.count}`);
```

```text
kurulan: /App
kurulan: /App/Header
kurulan: /App/Counter
sayac: 0
```

#### Hızlı Yenileme

Kullanıcı sayaca üç kez basar, sonra geliştirici başlığı değiştirir ve dosyayı kaydeder. Hızlı yenileme yalnız değişen bileşen dosyasını yeniden yükler. Kök tipi ve anahtarı aynı kalır.

```javascript
// main.mjs
import { printOps } from "./fate.mjs";
import { openSession, press, fastRefresh } from "./dev.mjs";

let session = openSession({ root: "App", title: "Ana ekran" });
session = press(press(press(session)));
const [after, ops] = fastRefresh(session, { root: "App", title: "Baslangic" });
printOps(ops);
console.log(`sayac: ${after.count}`);
```

```text
korunan: /App
guncellenen: /App/Header
korunan: /App/Counter
sayac: 3
```

Başlık güncellenen çıktı, kök ve sayaç korunan kaldı. Tek komut gitti ve sayaç üçte durdu: yenileme düğümlerin kimliğine dokunmadı, bu yüzden durum yaşadı. Başlığı hiç değiştirmeyen bir kayıt üç düğümü de korunan bırakırdı. Hızlı yenileme, geliştirmede en sık yaşanan akıbet çiftidir: değişen düğüm güncellenen, geri kalan korunan.

#### Tam Yeniden Yükleme

Her değişiklik hızlı yenilemeyle uyuşmaz. Çizim ağacının dışından içe alınan bir dosya, örneğin uygulamanın başlangıç kodu değişirse bütün paket baştan yüklenir; modelde bunun izi kök anahtarının artmasıdır. Yerel bir modül eklemek bunun da ötesine geçer: paketin yanında platform tarafının yeniden derlenmesini gerektirir.

```javascript
// main.mjs
import { countFates } from "./fate.mjs";
import { openSession, press, fullReload } from "./dev.mjs";

let session = openSession({ root: "App", title: "Ana ekran" });
session = press(press(press(session)));
const [after, ops] = fullReload(session, { root: "App", title: "Ana ekran" });
countFates(ops);
console.log(`sayac: ${after.count}`);
```

```text
korunan: 0
guncellenen: 0
kurulan: 3
sokulen: 3
komut: 6
sayac: 0
```

Kaynak değişmediği hâlde sonuç, hızlı yenilemenin tersidir: üç düğüm sökülen, üç düğüm kurulan, korunan yok. Komut sayısı altı, sayaç sıfır. Kök anahtarı değişince karşılaştırma kökün altına inmez: kök yeni bir düğümdür, eskisi yok olur ve altındaki her düğüm onunla birlikte gider.

#### Hızlı Yenileme de Kırılabilir

Hızlı yenileme bir ayrıcalık değil, bir koşuldur. Kök tipi değişirse karşılaştırma kökü eşleştiremez. Geliştirici kök bileşenin adını değiştirirse hızlı yenileme de bütün ağacı söker.

```javascript
// main.mjs
import { countFates } from "./fate.mjs";
import { openSession, press, fastRefresh } from "./dev.mjs";

let session = openSession({ root: "App", title: "Ana ekran" });
session = press(press(press(session)));
const [after, ops] = fastRefresh(session, { root: "Shell", title: "Ana ekran" });
countFates(ops);
console.log(`sayac: ${after.count}`);
```

```text
korunan: 0
guncellenen: 0
kurulan: 3
sokulen: 3
komut: 6
sayac: 0
```

Çıktı tam yeniden yüklemeyle aynı, ama yol farklı: burada kök anahtarı değil, kök tipi değişti. İki yol aynı akıbete varır. Durumun yaşayıp yaşamaması hangi komutun kullanıldığına değil, kök düğümün kimliğinin korunup korunmadığına bağlıdır.

#### Yayın Hattı

Geliştirmede paket, geliştirme sunucusundan gelir. Yayında paket uygulamanın içine gömülür ve iki platformda iki ayrı yoldan geçer. Her platformda yapı üç adımdır: derleme, imzalama, dağıtım. Derleme, JavaScript paketini ve yerel kodu bir araya getirir. İmzalama, paketin kimin tarafından üretildiğini kanıtlar; iki platformda da kendi anahtar yönetimi vardır. Dağıtım, imzalı paketi bir sürüm kanalına koyar: dahili deneme, sınırlı deneme ya da herkese açık yayın.

```kotlin
// Platform kodu: yayin yapilandirmasi (Android)
buildTypes {
  release {
    isMinifyEnabled = true
    signingConfig = signingConfigs.getByName("release")
  }
}
```

Parça iki ayrımı gösterir. `release` bloğu yayın derlemesinin geliştirme derlemesinden ayrı bir yapılandırma olduğunu, `signingConfig` satırı da imzanın derleme adımının parçası olduğunu anlatır. iOS tarafında aynı iş proje ayarlarında yapılır; model bunu ayırmaz, çünkü ağaç hangi anahtarla imzalandığını görmez.

Bir kanal daha vardır: uzaktan güncelleme. JavaScript paketi mağaza sürümünden bağımsız olarak değiştirilebilir. Sınır şudur: yalnız JavaScript tarafı uzaktan değişir. Yerel modülün yeni bir sürümü, yeni bir izin ya da yeni bir yerel görünüm yeni bir mağaza sürümü ister. Bu yüzden önceki dersteki `NativeMap` düğümü uzaktan güncellemeyle yeniden yazılabilir ama haritanın yerel kodu yazılamaz.

#### Yaygın Yanılgılar

İlk yanılgı, hızlı yenilemenin her zaman durumu koruduğudur. Yukarıdaki kök tipi ölçümü bunun tersini gösterdi: kök tipi değişince durum yitirildi. Hızlı yenileme bir güvence değil, ağacın karşılaştırma sonucuna bağlı bir sonuçtur.

İkinci yanılgı, tam yeniden yüklemenin uygulamayı yeniden başlatmak olduğudur. Model uygulamayı değil, kökü yeniden kurar. Telefon sürecinin ayakta kalması ya da kalmaması ağaç kuralının dışındadır; kural yalnız ağaçtaki düğümlere ne olduğunu söyler.

Üçüncü yanılgı, yayın yapılandırmasının ağaca yansıdığıdır. Derleme türü, imza ve sürüm kanalı düğümlerin tipini ya da özelliklerini değiştirmez. Aynı kaynak hem geliştirme hem yayın paketinde aynı ağacı üretir; farklı olan, ağaca ulaşan paketin nereden geldiğidir.

#### Sınırlayıcı Ölçüm

Bu dersin ayrım sorusu **bu değişiklik nereye ulaşıyor (M12/K02)** sorusudur. O soru bir kod değişikliğinin yayılımını sorar: yerinde, bağlantıda, kıran ya da örtük. Bu kurs kodun değil, çalışma anındaki tek bir güncellemenin tek bir düğüme etkisini sınıflar. Hızlı yenilemede kod değişikliği yerinde yayılır; ağaçta bıraktığı iz ise başlık düğümünde güncellenen, geri kalanda korunandır. İki sorunun cevabı birbirinden bağımsızdır.

Son ölçüm eski kökü gösterir. Tam yeniden yükleme kökte yapılır; kural yalnız kök düzeyindeki kayıtlara süzülür.

```javascript
// main.mjs
import { printOps } from "./fate.mjs";
import { openSession, press, fullReload } from "./dev.mjs";

let session = openSession({ root: "App", title: "Ana ekran" });
session = press(press(press(session)));
const [after, ops] = fullReload(session, { root: "App", title: "Ana ekran" });
printOps(ops.filter((op) => op.path.split("/").length === 2));
console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`);
```

```text
sokulen: /App
kurulan: /App[1]
komut: 6
```

Odak düğüm eski kök `/App`. Sökülen çıktı; yenisi `/App[1]` anahtarıyla kurulan. Eski kök gidince altındaki başlık ve sayaç da sökülen kaydı aldı: komut altı. Sökülen düğüm kimliğini yitirir, yani sayaç üçten sıfıra döndü. Güncellenen bir kök kimliğini korurdu.

**BU7.** Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde hızlı yenileme ağacı kaynaktan baştan kurar ve karşılaştırır; gerçek bir hızlı yenileme yalnız değişen dosyayı yeniden değerlendirir ve durumu tutmak için kendi kurallarını uygular. Modelde kök anahtarı elle artırılır; gerçek bir tam yeniden yükleme paketi baştan yükler ve bunun bedeli, geliştirme sunucusunun bağlantısı model dışındadır. Yayın hattı da modelde yoktur: derleme, imzalama ve mağaza süreçleri ağaç kuralının girdisi değildir.

#### Kapanış Tablosu

Kurs boyunca yirmi güncelleme ölçüldü. Her satır bir dersin akıbetini ve o dersin sınırlayıcı ölçümünde odaklanılan düğümü verir. Akıbet hücreleri kaynak dersin `Özet` bölümünden alınmıştır.

| Ders | Ölçülen güncelleme | Akıbet | Sınırlayıcı ölçüm |
|---|---|---|---|
| React Native Nedir | sayaç ağaca girmeyen değişkende artar | korunan | `/Screen/Text`, komut 0 |
| Ortam Kurulumu | kabuktan sonraki ilk güncelleme | kurulan | `/App/Home`, alt ağacıyla |
| Çekirdek Bileşenler | yer tutucu görselle değişir | sökülen | `/Card/Text` |
| Biçem Sistemi | içeriği aynı biçem nesnesi gelir | korunan | `/Row/Box`, komut 0 |
| Dokunma ve Etkileşim | basılı durum özellik olur | güncellenen | `/Screen/Button` |
| Platforma Özgü Kod | iki platformun ağacı karşılaştırılır | kurulan | `/Settings/Toggle` |
| Gezinme | yeni rota itilir | korunan | `/Stack/Screen[liste]` |
| Durum Yönetimi | sepet sayısı değişir | güncellenen | `/Screen/Header/Badge` |
| Ağ ve Veri | veri gelir | sökülen | `/Feed/Spinner` |
| Yerel Depolama | açılışta taslak okunur | kurulan | `/Editor/Draft` |
| Güvenli Depolama | oturum kapanır | sökülen | `/App/Account` |
| Bildirimler ve Derin Bağlantı | bağlantı açılır | kurulan | `/Stack/Screen[siparis-42]` |
| Hareketler ve Animasyonlar | hareket yerel tarafta sürer | korunan | `/Deck/Card`, komut 0 |
| Liste Başarımı | başa öge eklenir | güncellenen | `/Inbox/Row` |
| Kare Hızı ve Profilleme | ağaç üç kez yeniden üretilir | korunan | `/Screen/Chart` |
| Paket Boyutu ve Başlangıç | ertelenmiş ekran yüklenir | kurulan | `/App/Reports` |
| Hata Ayıklama Araçları | alt ağaç hata verir | sökülen | `/Screen/Panel` |
| Test | düğmeye basılır | güncellenen | `/Counter/Text` |
| Yerel Modül Yazımı | harita merkezi değişir | güncellenen | `/Screen/NativeMap`, komut 1 |
| Derleme ve Yayın | tam yeniden yükleme | sökülen | `/App`, komut 6 |

Derslerin dağılımı: korunan 5 (React Native Nedir, Biçem Sistemi, Gezinme, Hareketler ve Animasyonlar, Kare Hızı ve Profilleme), güncellenen 5 (Dokunma ve Etkileşim, Durum Yönetimi, Liste Başarımı, Test, Yerel Modül Yazımı), kurulan 5 (Ortam Kurulumu, Platforma Özgü Kod, Yerel Depolama, Bildirimler ve Derin Bağlantı, Paket Boyutu ve Başlangıç), sökülen 5 (Çekirdek Bileşenler, Ağ ve Veri, Güvenli Depolama, Hata Ayıklama Araçları, Derleme ve Yayın).

Bu dağılım, çekirdek dosyanın açılış sayımıyla aynı şey değildir. Çekirdeğin açılış sayımı tek bir güncellemenin sekiz düğümünü sayar: korunan 5, güncellenen 1, kurulan 1, sökülen 1 ve komut 3. Tablo ise yirmi dersin her birinin akıbetini sayar. Biri düğüm, öteki ders sayar.

#### Özet

- Bu dersin akıbeti: sökülen. Tam yeniden yüklemede eski kök `/App` kök anahtarı değiştiği için sökülen çıktı; yenisi kurulan oldu, altındaki üç düğümle komut altı.
- Hızlı yenileme değişen düğümü güncellenen, geri kalanı korunan bırakır; durum yaşar. Kök tipi ya da anahtarı değişince durum yitirilir.
- Derleme, imzalama ve sürüm kanalı ağaçta görünmez; uzaktan güncelleme yalnız JavaScript tarafını değiştirir.
- Yirmi dersin akıbeti beşer beşer dağılır: korunan, güncellenen, kurulan ve sökülen beşer ders.

#### Sonraki Adım

Bu kurs, ortak kodu platformun yerel görünümlerine tarif olarak ileten bir çerçeveyi ölçtü. Sıradaki kurs, ekranı platformun görünümlerine bırakmayıp kendi çizim motoruyla çizen bir çerçeveyi ele alır.

