Ders 19 / 20
Yerel Modül Yazımı
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.
Yayın Güncelleme
Bu derste
- Yerel modülün JavaScript tarafından nasıl çağrıldığını bir köprü işleviyle gösterir
- Yerel görünümün ağaçta tek düğüm olduğunu ölçer
- Özellik değişince yerel görünüm düğümünün güncellendiğini ve tek komut verdiğini gösterir
- Yerel katmanın iç ayrıntılarının kuralda akıbet almadığını açıklar
İçindekiler
Ö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.
// 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.
// 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.
// main.mjs import { reconcile, printOps } from "./fate.mjs"; import { renderScreen } from "./bridge.mjs"; printOps(reconcile(null, renderScreen({ lat: 41, lng: 29 })));
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.
// 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));
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.
// 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));
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.
// 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));
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.
// Platform kodu: GeoModule (iOS)
@objc(GeoModule)
class GeoModule: NSObject {
@objc func label(_ lat: Double, lng: Double,
resolve: RCTPromiseResolveBlock,
reject: RCTPromiseRejectBlock) {
resolve("\(lat),\(lng)")
}
}
// 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)ilegetName() = "Geo"modülün JavaScript tarafında hangi adla görüneceğini belirler. Model bunuMODULESnesnesininGeoanahtarı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. Modeldekilabel: (lat, lng) => ...bunların yalnız ortak çekirdeğidir.resolve("\(lat),\(lng)")vepromise.resolve("$lat,$lng")sonucu JavaScript tarafına geri verir. Modelreturnile 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.
// 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}`);
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/NativeMapdüğü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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.