Dalam pengembangan aplikasi modern, sistem reaktif menjadi inti dari banyak framework populer seperti Vue.js, React, dan Svelte. Konsep dasarnya adalah membuat data dan tampilan (UI) selalu sinkron tanpa perlu menulis ulang logika render secara manual. Ketika data berubah, tampilan akan otomatis menyesuaikan, inilah esensi dari reactivity-driven UI update.
Setelah sebelumnya kita membahas Reactive System dengan Proxy, Reflect, dan Dependency Tracking, langkah selanjutnya adalah melakukan integrasi reactive system dengan DOM renderer. Dengan pendekatan ini, JavaScript murni dapat menghasilkan antarmuka dinamis layaknya framework modern, di mana pembaruan data langsung terlihat pada halaman web secara otomatis tanpa manual re-rendering.
Konsep Integrasi Reactive System dan DOM Renderer
Integrasi ini bekerja dengan cara menghubungkan dua hal penting:
-
State Reactive, yaitu data yang dipantau perubahan nilainya (menggunakan
ProxydanReflect). -
DOM Renderer, yaitu fungsi yang bertugas menampilkan data ke halaman dan memperbaruinya ketika state berubah.
Ketika sebuah data diubah, dependency tracking system memicu proses re-render hanya pada elemen DOM yang terkait. Pendekatan ini sangat efisien karena tidak perlu menggambar ulang seluruh halaman — cukup bagian yang relevan saja.
Struktur Dasar Reactive Renderer
Berikut struktur umum sistem reaktif dengan renderer DOM otomatis:
const targetMap = new WeakMap();
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
if (activeEffect) dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) dep.forEach(effect => effect());
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key);
return typeof result === "object" ? reactive(result) : result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
});
}
Kode di atas membentuk fondasi reaktivitas, lengkap dengan pelacakan dependensi (dependency tracking). Selanjutnya, kita akan menghubungkannya ke DOM Renderer.
Membuat Renderer Otomatis untuk DOM
Kita bisa membuat sistem auto renderer sederhana seperti ini:
function mountApp(root, renderFn) {
effect(() => {
root.innerHTML = "";
root.appendChild(renderFn());
});
}
Fungsi mountApp() akan secara otomatis merender ulang UI setiap kali data reaktif berubah, berkat sistem effect() yang sudah kita buat.
Contoh penerapan:
<div id="app"></div>
<button id="btn">Tambah</button>
<script>
const state = reactive({ count: 0 });
function render() {
const container = document.createElement("div");
container.textContent = `Nilai Count: ${state.count}`;
return container;
}
const root = document.getElementById("app");
mountApp(root, render);
document.getElementById("btn").addEventListener("click", () => {
state.count++;
});
</script>
Sekarang setiap kali tombol diklik, nilai state.count akan naik dan tampilan otomatis diperbarui tanpa kita memanggil fungsi render() secara manual.
Optimasi: Virtual DOM Sederhana
Agar sistem lebih efisien, kita dapat menambahkan lapisan Virtual DOM. Dengan cara ini, hanya bagian yang berubah yang akan diperbarui, bukan seluruh elemen.
Contoh konsep sederhananya:
function diff(oldNode, newNode) {
if (oldNode.textContent !== newNode.textContent) {
oldNode.textContent = newNode.textContent;
}
}
Lalu ubah fungsi mountApp menjadi:
function mountApp(root, renderFn) {
let oldNode = renderFn();
root.appendChild(oldNode);
effect(() => {
const newNode = renderFn();
diff(oldNode, newNode);
oldNode = newNode;
});
}
Dengan sistem ini, hanya teks atau atribut yang berubah yang akan diperbarui, bukan seluruh elemen DOM.
Studi Kasus: Aplikasi Counter Reaktif
Berikut contoh implementasi lengkap dari Integrasi Reactive System dengan DOM Renderer (Auto Update UI):
<div id="app"></div>
<button id="increment">Tambah</button>
<button id="decrement">Kurangi</button>
<script>
// Reactive core
const targetMap = new WeakMap();
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
if (activeEffect) dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) dep.forEach(effect => effect());
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key);
return typeof result === "object" ? reactive(result) : result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
});
}
// Renderer
function mountApp(root, renderFn) {
effect(() => {
root.innerHTML = "";
root.appendChild(renderFn());
});
}
// State dan UI
const state = reactive({ count: 0 });
function render() {
const el = document.createElement("div");
el.textContent = `Jumlah: ${state.count}`;
return el;
}
const root = document.getElementById("app");
mountApp(root, render);
document.getElementById("increment").onclick = () => state.count++;
document.getElementById("decrement").onclick = () => state.count--;
</script>
Ketika pengguna menekan tombol, nilai count akan berubah dan tampilan otomatis diperbarui tanpa perlu menulis ulang fungsi render().
Keuntungan Sistem Reactive DOM Renderer
-
⚡ Pembaruan Otomatis: Tampilan selalu sinkron dengan data tanpa render manual.
-
Struktur Modular: State dan UI dipisahkan secara jelas.
-
Mudah Dikembangkan: Bisa diperluas ke konsep Virtual DOM, template binding, dan component system.
-
Performa Lebih Baik: Hanya elemen yang terpengaruh yang di-render ulang.
Langkah Pengembangan Selanjutnya
Jika kamu ingin melangkah lebih jauh, sistem ini bisa dikembangkan menjadi:
-
Mini Vue-like Framework, lengkap dengan
computed,watch, dantemplate parser. -
Component System Modular, seperti
App(),Button(),Counter(). -
Binding Dua Arah (Two-Way Binding) untuk input form.
-
Partial Rendering agar hanya bagian tertentu dari UI yang diperbarui.
Kesimpulan
Dengan memahami integrasi reactive system dengan DOM renderer, kita bisa membangun UI JavaScript modern yang benar-benar dinamis tanpa framework besar. Kombinasi antara Proxy, Reflect, dan dependency tracking menciptakan sistem cerdas yang secara otomatis memperbarui tampilan ketika data berubah.
Pendekatan ini tidak hanya melatih logika pemrograman reaktif, tapi juga memberi pemahaman mendalam tentang bagaimana framework populer seperti Vue dan React bekerja di balik layar — dimulai dari JavaScript murni yang sederhana dan efisien.