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
Pendapat Hacker News
Contoh Vanilla JS vs. Signals
isEvenatauparity, mungkin perlu mengubah seluruh pendekatannya.Promises dan perubahan di JavaScript
new Promise, tetapi kenyataannya hampir tidak pernah..then, dan ini menyederhanakan antarmuka dengan berbagai pustaka pihak ketiga.Signals sebagai bagian dari bahasa
Penggunaan event dalam aplikasi
window.dispatchEventdanwindow.addEventListener.Sulitnya manajemen state DOM dan pembaruan
Promises dan pemrograman asinkron
S.js dan Signals
Signals yang mirip MobX
Menambahkan framework ke pustaka standar
Pemahaman dan masalah pada proposal Signal
effectmendeteksi perubahan parity, dan apakah lambda ini dipanggil untuk perubahan sinyal apa pun.