3 puan yazan GN⁺ 2024-04-01 | 1 yorum | WhatsApp'ta paylaş

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

 
GN⁺ 2024-04-01
Hacker News görüşleri
  • Vanilla JS vs. Signals örneği

    • Vanilla JS örneğinin daha okunabilir ve üzerinde çalışması daha kolay olduğunu düşünen tek kişi ben miyim?
      • Kurulumun karmaşık ve boilerplate'in fazla olduğunu düşünüyorum.
      • Sayaç değeri değiştiğinde gereksiz hesaplamalar ve render işlemleri oluşabilir.
      • Arayüzün başka bölümlerinin yalnızca sayaç güncellendiğinde render edilmesi için durum yönetimi yöntemini değiştirmek gerekebilir.
      • Arayüzün başka bölümleri yalnızca isEven veya parity'ye bağlıysa, tüm yaklaşımı değiştirmek gerekebilir.
  • Promises ve JavaScript'in değişimi

    • Başta sık sık new Promise yazmak zorunda kalırım diye endişelenmiştim, ama pratikte neredeyse hiç kullanmadım.
    • Bunun yerine .then'i çok kullandım ve bu, çeşitli üçüncü taraf kütüphanelerle arayüz kurmayı basitleştirdi.
    • Signal önerisi reaktif UI framework'lerine benzer bir etki getirecekse desteklerim.
  • Dilin bir parçası olarak Signals

    • Signals'ın dilin bir parçası olması gerekmiyor; bir kütüphane olarak yeterli.
    • Mevcut JS UI kütüphanelerinin tasarladığı Signals'ın dilin parçası olacak kadar iyi olduğunu düşünmek kibirli görünüyor.
    • Her trendi dil çalışma zamanına eklemek kısa vadeli bir bakış gibi duruyor.
  • Uygulamalarda event kullanımı

    • Uygulamanın genelinde sinyal göndermek için event'ler kullanıyorum.
    • window.dispatchEvent ve window.addEventListener ile event tetikliyor ve bunlara abone oluyorum.
  • DOM durum yönetimi ve güncellemelerin zorluğu

    • İnsanların onlarca yıldır durum yönetimi ve DOM güncellemelerinde neden zorlandığını anlamaya çalışıyorum.
    • Basit DOM fonksiyonlarını gereksiz yere karmaşıklaştırıyor gibi gelmesi bana tuhaf geliyor.
  • Promises ve asenkron programlama

    • Promises başarılı bir örnek, ancak async/await olmadan standartlaştırılmasına gerek yoktu.
    • Çeşitli kütüphane yazarlarının bu öneri hakkında ne düşündüğünü merak ediyorum.
  • S.js ve Signals

    • Signals'ı seviyorum ve UI geliştirirken diğer primitive'lere tercih ediyorum.
    • Ama JavaScript diline dahil edilmesi gerektiğini düşünmüyorum.
  • MobX'e benzer Signals

    • MobX en sevdiğim JS efekt sistemi.
    • MobX sürümüne ait bir kod örneği veriliyor.
  • Standart kütüphaneye framework eklemek

    • Bu, o an tercih ettiğiniz framework'ü standart kütüphaneye eklemeyi önermeye benziyor.
  • Signal önerisini anlama ve sorunlar

    • Signal önerisinin örneklerini anlamakta zorlanıyorum.
    • effect fonksiyonunun 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.
    • Signal fikri makul, ancak karmaşık uygulamalarda event takibini zorlaştırabilir.