İçeriğe geç
academia.sh

Ders 01 / 20

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.

Yayın Güncelleme

Bu derste

  • Bildirimsel tarif, görünüm ağacı, yerel görünüm ve köprü geçişi kavramlarını birbirine bağlar
  • Bir güncellemenin ağaçtaki her düğüme ne yaptığını korunan, güncellenen, kurulan ve sökülen olarak sınıflar
  • Komut sayısının yalnız güncellenen, kurulan ve sökülen düğümlerden oluştuğunu ölçer
  • Ağaca girmeyen bir değerin değişmesinin neden komut doğurmadığını açıklar
İçindekiler

Ö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üncellenendir: yerinde kalır, yerel görünümü güncellenir. Düğümün önceki ağaçta eşi yoksa kurulandır ve yerel görünümü sıfırdan oluşturulur. Düğümün sonraki ağaçta eşi yoksa sökülendir ve yerel görünümüyle birlikte içindeki durum da yok olur. Dört yanıtın üst adı akıbettir. Çı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.

// 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.

// 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.

// 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);
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.

// 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.

// 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));
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.

// 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);
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.

// 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}`);
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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat