3 poin oleh GN⁺ 2024-04-01 | 1 komentar | Bagikan ke WhatsApp

Draf usulan standar JavaScript Signals

  • Ini adalah dokumen yang menjelaskan arah bersama awal untuk signals di JavaScript, mirip dengan upaya Promises/A+ sebelum Promises distandardisasi oleh TC39 di ES2015.
  • Upaya ini berfokus pada penyelarasan ekosistem JavaScript, dan jika penyelarasan ini berhasil, standar dapat muncul berdasarkan pengalaman tersebut.
  • Para penulis berbagai framework sedang berkolaborasi pada model bersama yang dapat mendukung inti reaktivitas.
  • Draf saat ini didasarkan pada masukan desain dari penulis/pemelihara Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, dan lainnya.

Latar belakang: mengapa signals?

  • Untuk mengembangkan antarmuka pengguna (UI) yang kompleks, pengembang aplikasi JavaScript perlu menyimpan, menghitung, menginvalidasi, menyinkronkan, dan mendorong state ke lapisan view aplikasi secara efisien.
  • UI sering kali tidak hanya melibatkan pengelolaan nilai sederhana, tetapi juga merender state terhitung yang bergantung pada nilai atau state lain.
  • Tujuan signals adalah menyediakan infrastruktur untuk mengelola state aplikasi ini agar pengembang bisa lebih fokus pada logika bisnis daripada detail-detail berulang.
Contoh - penghitung VanillaJS
  • Ada variabel bernama counter, dan setiap kali variabel ini berubah, kita ingin memperbarui di DOM apakah penghitung tersebut genap atau ganjil.
  • Di Vanilla JS, kodenya bisa seperti berikut:
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);
  • Kode ini memiliki beberapa masalah:
    • Pengaturan counter berisik dan penuh boilerplate.
    • State counter terikat erat dengan sistem rendering.
    • Saat counter berubah tetapi parity tidak berubah (misalnya berubah dari 2 ke 4), tetap terjadi komputasi dan rendering yang tidak perlu.
    • Bisa jadi ada bagian lain dari UI yang hanya ingin dirender saat counter diperbarui.
    • Bagian lain dari UI yang hanya bergantung pada isEven atau parity tidak bisa diperbarui tanpa berinteraksi langsung dengan counter.

Pengenalan signals

  • Abstraksi data binding antara model dan view telah lama menjadi inti framework UI, meskipun JS maupun platform web tidak memiliki mekanisme seperti itu secara bawaan.
  • Di dalam framework dan library JS, telah banyak eksperimen dilakukan terhadap berbagai cara untuk merepresentasikan binding ini, dan kekuatan pendekatan nilai reaktif kelas satu untuk merepresentasikan komputasi yang diturunkan dari state atau data lain—yang sering disebut "Signals"—telah terbukti.
  • Jika contoh di atas dibayangkan ulang dengan API signals, hasilnya seperti berikut:
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);

Motivasi untuk standarisasi signals

Interoperabilitas
  • Setiap implementasi signals memiliki mekanisme pelacakan otomatisnya sendiri, sehingga sulit untuk berbagi model, komponen, dan library lintas framework.
  • Tujuan usulan ini adalah memisahkan model reaktif sepenuhnya dari view rendering agar pengembang tidak perlu menulis ulang kode non-UI saat berpindah ke teknologi rendering baru, atau agar dapat mengembangkan model reaktif bersama dalam JS yang bisa diterapkan di konteks lain.
Kinerja/penggunaan memori
  • Mengirim kode lebih sedikit karena library yang umum digunakan tersedia secara bawaan selalu dapat membawa sedikit peningkatan performa potensial, tetapi karena implementasi signals umumnya cukup kecil, efek ini tidak diharapkan terlalu besar.
Developer tools
  • Saat menggunakan library signals bahasa JS yang sudah ada, sulit untuk melacak call stack melalui rantai signal terhitung, graf referensi antar signal, dan sebagainya.
  • Signals bawaan memungkinkan runtime JS dan developer tools memberikan dukungan yang lebih baik untuk memeriksa signal.
Manfaat tambahan
Manfaat standard library
  • Secara umum, JavaScript selama ini memiliki standard library yang cukup minimal, tetapi tren TC39 adalah menjadikan JS sebagai bahasa "batteries included" dengan kumpulan fitur bawaan berkualitas tinggi.
Integrasi HTML/DOM (kemungkinan di masa depan)
  • W3C dan implementator browser saat ini sedang mengerjakan pengenalan template native ke HTML.
  • Untuk mencapai tujuan tersebut, pada akhirnya HTML akan memerlukan nilai primitif reaktif.

Tujuan desain signals

  • Library signals yang ada pada dasarnya tidak terlalu berbeda di level inti.
  • Usulan ini ingin membangun di atas keberhasilan mereka dengan mengimplementasikan karakteristik penting dari banyak library.

Fitur inti

  • Tipe Signal yang merepresentasikan state, yaitu Signal yang dapat ditulis.
  • Tipe Signal terhitung/memo/turunan yang bergantung pada signal lain serta dihitung secara lazy dan di-cache.
  • Memungkinkan framework JS melakukan scheduling mereka sendiri.

Sketsa API

  • Ide awal API signals adalah seperti di bawah ini. Ini hanyalah draf awal, dan diperkirakan akan berubah seiring waktu.
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;
  }
}

Algoritme signals

  • Menjelaskan algoritme yang diimplementasikan untuk setiap API yang diekspos ke JavaScript.
  • Ini dapat dianggap sebagai spesifikasi awal, yang bertujuan mengunci serangkaian makna semaksimal mungkin sambil tetap terbuka terhadap perubahan besar.

Opini GN⁺

  • Usulan standar JavaScript Signals bertujuan meningkatkan interoperabilitas antar-framework dan memudahkan pengembang mengimplementasikan pemrograman reaktif.
  • Usulan ini adalah upaya untuk menstandarkan fitur inti dari berbagai library signals yang sudah ada, dan dapat memberikan model pemrograman yang konsisten bagi pengembang.
  • Konsep signals dapat berguna tidak hanya untuk pengembangan UI tetapi juga dalam konteks non-UI, khususnya untuk membantu menghindari rebuild yang tidak perlu dalam sistem build.
  • API yang diusulkan diharapkan menyediakan alat yang berguna bagi pengembang framework, sehingga mereka dapat mencapai performa dan pengelolaan memori yang lebih baik.
  • Namun, agar teknologi ini diadopsi secara luas, masih dibutuhkan lebih banyak prototyping dan umpan balik komunitas, serta pembuktian efektivitasnya melalui integrasi ke aplikasi nyata.
  • Saat ini framework seperti React, Vue, dan Svelte sudah memiliki sistem reaktif mereka sendiri, sehingga kompatibilitas atau strategi integrasi dengan framework-framework ini juga akan menjadi pertimbangan penting.

1 komentar

 
GN⁺ 2024-04-01
Pendapat Hacker News
  • Contoh Vanilla JS vs. Signals

    • Apakah hanya saya yang merasa contoh Vanilla JS lebih mudah dibaca dan lebih nyaman dikerjakan?
      • Menurut saya pengaturannya rumit dan banyak boilerplate.
      • Saat nilai penghitung berubah, bisa terjadi komputasi dan rendering yang tidak diperlukan.
      • Jika bagian lain dari UI hanya perlu dirender saat penghitung diperbarui, mungkin metode manajemen state perlu diubah.
      • Jika bagian lain dari UI hanya bergantung pada isEven atau parity, mungkin perlu mengubah seluruh pendekatannya.
  • Promises dan perubahan di JavaScript

    • Awalnya saya khawatir harus sering menulis new Promise, tetapi kenyataannya hampir tidak pernah.
    • Sebagai gantinya saya jadi sering menggunakan .then, dan ini menyederhanakan antarmuka dengan berbagai pustaka pihak ketiga.
    • Jika proposal Signal bisa membawa efek serupa ke framework UI reaktif, saya mendukungnya.
  • Signals sebagai bagian dari bahasa

    • Signals tidak harus menjadi bagian dari bahasa, pustaka saja sudah cukup.
    • Menganggap Signals yang dirancang pustaka UI JS saat ini cukup bagus untuk dijadikan bagian dari bahasa terasa arogan.
    • Menambahkan setiap tren ke runtime bahasa terlihat seperti pandangan jangka pendek.
  • Penggunaan event dalam aplikasi

    • Menggunakan event di seluruh aplikasi untuk mengirim sinyal.
    • Event dipicu dan dilanggan melalui window.dispatchEvent dan window.addEventListener.
  • Sulitnya manajemen state DOM dan pembaruan

    • Mencoba memahami mengapa selama puluhan tahun orang kesulitan dengan manajemen state dan pembaruan DOM.
    • Rasanya aneh karena seolah membuat fungsi DOM yang sederhana menjadi rumit.
  • Promises dan pemrograman asinkron

    • Promises adalah contoh yang sukses, tetapi tanpa async/await, sebenarnya tidak perlu distandardisasi.
    • Saya penasaran bagaimana pendapat berbagai penulis pustaka tentang proposal ini.
  • S.js dan Signals

    • Saya menyukai Signals dan lebih memilihnya dibanding elemen dasar lain saat membuat UI.
    • Namun saya tidak berpikir ini harus dimasukkan ke dalam bahasa JavaScript.
  • Signals yang mirip MobX

    • MobX adalah sistem efek JS favorit saya.
    • Menyediakan contoh kode versi MobX.
  • Menambahkan framework ke pustaka standar

    • Ini mirip dengan mengusulkan agar framework yang saat ini disukai ditambahkan ke pustaka standar.
  • Pemahaman dan masalah pada proposal Signal

    • Saya kesulitan memahami contoh pada proposal Signal.
    • Ada pertanyaan tentang bagaimana fungsi effect mendeteksi perubahan parity, dan apakah lambda ini dipanggil untuk perubahan sinyal apa pun.
    • Gagasan Signal masuk akal, tetapi dalam aplikasi yang kompleks pelacakan event bisa menjadi sulit.