JavaScript Signals standart öneri taslağı
- JavaScript'teki sinyaller (signals) için ortak bir ilk yönü açıklayan bir belge olup, ES2015'te TC39 tarafından standartlaştırılan Promises öncesindeki Promises/A+ çabasına benzer.
- Bu çaba, JavaScript ekosistemini uyumlu hale getirmeye odaklanıyor; bu uyum başarılı olursa, bu deneyime dayanarak bir standart ortaya çıkabilir.
- Birden fazla framework yazarı, reaktivite çekirdeğini destekleyebilecek ortak bir model üzerinde iş birliği yapıyor.
- Mevcut taslak, Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz gibi projelerin yazarları/bakımcılarından gelen tasarım girdilerine dayanıyor.
Arka plan: Neden sinyaller?
- Karmaşık kullanıcı arayüzleri (UI) geliştirmek için JavaScript uygulama geliştiricilerinin durumu depolaması, hesaplaması, geçersiz kılması, senkronize etmesi ve bunu uygulamanın görünüm katmanına verimli bir şekilde aktarması gerekir.
- UI, yalnızca basit değer yönetimini değil, diğer değer veya durumlara bağlı hesaplanmış durumların render edilmesini de sıkça içerir.
- Sinyallerin amacı, bu uygulama durumunu yönetmek için altyapı sağlayarak geliştiricilerin tekrarlayan ayrıntılar yerine iş mantığına odaklanmasını sağlamaktır.
Örnek - VanillaJS sayaç
counter adında bir değişken var ve bu değişken her değiştiğinde DOM'da sayacın çift olup olmadığını güncellemek istiyoruz.
- Vanilla JS'de aşağıdaki gibi bir kod olabilir:
let counter = 0;
const setCounter = (value) => {
counter = value;
render();
};
const isEven = () => (counter & 1) == 0;
const parity = () => isEven() ? "even" : "odd";
const render = () => element.innerText = parity();
// Simulate external updates to counter...
setInterval(() => setCounter(counter + 1), 1000);
- Bu kodun bazı sorunları vardır:
counter ayarlama işlemi gürültülü ve bol miktarda boilerplate içerir.
counter durumu, render sistemiyle sıkı şekilde bağlıdır.
counter değişmiş olsa da parity değişmediğinde (örneğin 2'den 4'e), gereksiz hesaplama ve render yapılır.
- UI'nin başka bir bölümü yalnızca
counter güncellendiğinde render edilmek isteyebilir.
- Yalnızca
isEven veya parity'ye bağlı olan UI'nin diğer bölümleri, counter ile doğrudan etkileşime girmeden güncellenemez.
Sinyallere giriş
- Model ile görünüm arasındaki veri bağlama soyutlaması, JS veya web platformunda böyle bir mekanizma yerleşik olmasa da uzun süredir UI framework'lerinin merkezinde yer alıyor.
- JS framework ve kütüphanelerinde bu bağlamaları temsil etmenin farklı yolları üzerine çok sayıda deney yapıldı ve genellikle "Signals" olarak adlandırılan, durumdan veya diğer verilerden türetilen hesaplamaları birinci sınıf reaktif değerler olarak ele alma yaklaşımının gücü kanıtlandı.
- Yukarıdaki örneği sinyal API'si kullanarak yeniden düşünürsek şu şekilde olur:
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() => (counter.get() & 1) == 0);
const parity = new Signal.Computed(() => isEven.get() ? "even" : "odd");
// A library or framework defines effects based on other Signal primitives
declare function effect(cb: () => void): (() => void);
effect(() => element.innerText = parity.get());
// Simulate external updates to counter...
setInterval(() => counter.set(counter.get() + 1), 1000);
Sinyallerin standartlaştırılması için motivasyon
Birlikte çalışabilirlik
- Her sinyal uygulaması kendi otomatik izleme mekanizmasına sahip olduğundan, farklı framework'ler arasında model, bileşen ve kütüphane paylaşmak zordur.
- Bu önerinin amacı, reaktif modeli render edilen görünümden tamamen ayırarak geliştiricilerin yeni bir render teknolojisine geçerken UI dışı kodu yeniden yazmak zorunda kalmamasını veya başka bağlamlarda dağıtılabilecek ortak reaktif modelleri JS ile geliştirebilmesini sağlamaktır.
Performans/bellek kullanımı
- Yaygın kullanılan bir kütüphane yerleşik olduğunda daha az kod taşımak her zaman küçük de olsa potansiyel bir performans iyileşmesi sağlayabilir, ancak sinyal uygulamaları genellikle oldukça küçük olduğundan bu etkinin çok büyük olması beklenmiyor.
Geliştirici araçları
- Mevcut JS dilindeki sinyal kütüphaneleri kullanılırken hesaplanmış sinyal zincirleri boyunca çağrı yığınını, sinyaller arası referans grafiğini vb. izlemek zordur.
- Yerleşik sinyaller, JS çalışma zamanı ve geliştirici araçlarının sinyalleri incelemek için daha iyi destek sunmasını mümkün kılar.
Yan faydalar
Standart kütüphanenin faydaları
- Genel olarak JavaScript oldukça minimal bir standart kütüphaneye sahipti, ancak TC39'un eğilimi JS'yi yüksek kaliteli yerleşik özellikler kümesine sahip, "bataryaları içinde" gelen bir dil haline getirmektir.
HTML/DOM entegrasyonu (gelecekteki olasılık)
- W3C ve tarayıcı geliştiricileri şu anda HTML'e yerel şablonlar getirmek için çalışmalar yürütüyor.
- Bu hedeflere ulaşmak için nihayetinde HTML içinde reaktif ilkelere ihtiyaç duyulacaktır.
Sinyal tasarım hedefleri
- Mevcut sinyal kütüphaneleri özünde o kadar da farklı değil.
- Bu öneri, birçok kütüphanenin önemli özelliklerini uygulayarak onların başarısı üzerine inşa etmeyi amaçlıyor.
Temel işlevler
- Durumu temsil eden bir Signal tipi, yani yazılabilir Signal.
- Diğer sinyallere bağlı, tembel hesaplanan ve önbelleğe alınan hesaplanmış/memo/türetilmiş Signal tipi.
- JS framework'lerinin kendi zamanlamalarını yapabilmesine olanak tanınması.
API taslağı
- İlk sinyal API fikri aşağıdaki gibidir. Bu yalnızca başlangıç taslağıdır ve zamanla değişmesi beklenmektedir.
namespace Signal {
// A read-write Signal
class State<T> implements Signal<T> {
// Create a state Signal starting with the value t
constructor(t: T, options?: SignalOptions<T>);
// Get the value of the signal
get(): T;
// Set the state Signal value to t
set(t: T): void;
}
// A Signal which is a formula based on other Signals
class Computed<T> implements Signal<T> {
// Create a Signal which evaluates to the value returned by the callback.
// Callback is called with this signal as the this value.
constructor(cb: (this: Computed<T>) => T, options?: SignalOptions<T>);
// Get the value of the signal
get(): T;
}
// This namespace includes "advanced" features that are better to
// leave for framework authors rather than application developers.
// Analogous to `crypto.subtle`
namespace subtle {
// Run a callback with all tracking disabled (even for nested computed).
function untrack<T>(cb: () => T): T;
// Get the current computed signal which is tracking any signal reads, if any
function currentComputed(): Computed | null;
// Returns ordered list of all signals which this one referenced
// during the last time it was evaluated.
// For a Watcher, lists the set of signals which it is watching.
function introspectSources(s: Computed | Watcher): (State | Computed)[];
// Returns the Watchers that this signal is contained in, plus any
// Computed signals which read this signal last time they were evaluated,
// if that computed signal is (recursively) watched.
function introspectSinks(s: State | Computed): (Computed | Watcher)[];
// True if this signal is "live", in that it is watched by a Watcher,
// or it is read by a Computed signal which is (recursively) live.
function hasSinks(s: State | Computed): boolean;
// True if this element is "reactive", in that it depends
// on some other signal. A Computed where hasSources is false
// will always return the same constant.
function hasSources(s: Computed | Watcher): boolean;
class Watcher {
// When a (recursive) source of Watcher is written to, call this callback,
// if it hasn't already been called since the last `watch` call.
// No signals may be read or written during the notify.
constructor(notify: (this: Watcher) => void);
// Add these signals to the Watcher's set, and set the watcher to run its
// notify callback next time any signal in the set (or one of its dependencies) changes.
// Can be called with no arguments just to reset the "notified" state, so that
// the notify callback will be invoked again.
watch(...s: Signal[]): void;
// Remove these signals from the watched set (e.g., for an effect which is disposed)
unwatch(...s: Signal[]): void;
// Returns the set of sources in the Watcher's set which are still dirty, or is a computed signal
// with a source which is dirty or pending and hasn't yet been re-evaluated
getPending(): Signal[];
}
// Hooks to observe being watched or no longer watched
var watched: Symbol;
var unwatched: Symbol;
}
interface Options<T> {
// Custom comparison function between old and new value. Default: Object.is.
// The signal is passed in as the this value for context.
equals?: (this: Signal<T>, t: T, t2: T) => boolean;
// Callback called when isWatched becomes true, if it was previously false
[Signal.subtle.watched]?: (this: Signal<T>) => void;
// Callback called whenever isWatched becomes false, if it was previously true
[Signal.subtle.unwatched]?: (this: Signal<T>) => void;
}
}
Sinyal algoritması
- JavaScript'e açık edilen her API için uygulanacak algoritmaları açıklar.
- Bu, ilk bir spesifikasyon olarak düşünülebilir ve değişime oldukça açık olsa da mümkün olduğunca bir anlam kümesini netleştirmeyi amaçlar.
GN⁺ görüşü
- JavaScript Signals standart önerisi, framework'ler arası birlikte çalışabilirliği artırmayı ve geliştiricilerin reaktif programlamayı daha kolay uygulayabilmesini hedefliyor.
- Bu öneri, mevcut çeşitli sinyal kütüphanelerinin temel işlevlerini standartlaştırma girişimi olarak geliştiricilere tutarlı bir programlama modeli sunabilir.
- Sinyal kavramı yalnızca UI geliştirmede değil, UI dışı bağlamlarda da faydalı olabilir; özellikle build sistemlerinde gereksiz yeniden derlemeleri önlemeye yardımcı olabilir.
- Önerilen API, framework geliştiricilerine yararlı araçlar sunuyor ve bunun sayesinde daha iyi performans ile bellek yönetimi elde edilmesi bekleniyor.
- Ancak bu teknolojinin geniş çapta benimsenmesi için daha fazla prototipleme ve topluluk geri bildirimi gerekiyor; ayrıca gerçek uygulamalara entegre edilerek etkisinin kanıtlanması gerekiyor.
- Şu anda React, Vue, Svelte gibi framework'lerin zaten kendi reaktif sistemleri bulunuyor; bu framework'lerle uyumluluk veya entegrasyon stratejileri de önemli bir değerlendirme konusu olacak.
1 yorum
Hacker News görüşleri
Vanilla JS vs. Signals örneği
isEvenveya parity'ye bağlıysa, tüm yaklaşımı değiştirmek gerekebilir.Promises ve JavaScript'in değişimi
new Promiseyazmak zorunda kalırım diye endişelenmiştim, ama pratikte neredeyse hiç kullanmadım..then'i çok kullandım ve bu, çeşitli üçüncü taraf kütüphanelerle arayüz kurmayı basitleştirdi.Dilin bir parçası olarak Signals
Uygulamalarda event kullanımı
window.dispatchEventvewindow.addEventListenerile event tetikliyor ve bunlara abone oluyorum.DOM durum yönetimi ve güncellemelerin zorluğu
Promises ve asenkron programlama
S.js ve Signals
MobX'e benzer Signals
Standart kütüphaneye framework eklemek
Signal önerisini anlama ve sorunlar
effectfonksiyonunun parity değişimini nasıl algıladığı ve herhangi bir signal değişiminde bu lambda'yı çağırıp çağırmadığı gibi sorularım var.