Ders 02 / 20
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.
Yayın Güncelleme
Bu derste
- Araç zincirinin parçalarını ve yönetilen ile yerel projeli iş akışı arasındaki ayrımı açıklar
- Emülatör, gerçek aygıt ve geliştirici sunucusunun geliştirme döngüsündeki yerini sıralar
- İlk açılışta önceki ağaç olmadığı için bütün ağacın kurulan çıktığını ölçer
- Kabuk ağacı ile ana ekranın ayrı akıbet aldığı ikinci güncellemeyi okur
İçindekiler
Ö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.
// 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.
// 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.
// 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.
// 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);
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.
// 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);
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 aciktir.
// 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);
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.
// 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);
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/Homeana 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.