Pada bagian sebelumnya, kita sudah membuat mini framework JavaScript sederhana dengan konsep Virtual DOM dan Reactive Rendering. Framework itu sudah mampu membuat tampilan dinamis dan memperbarui elemen DOM tanpa reload halaman.
Namun, aplikasi modern seperti React, Vue, atau Svelte tidak berhenti sampai situ. Mereka juga punya fitur:
-
Routing (navigasi halaman tanpa reload),
-
Lifecycle hooks (fungsi yang dijalankan saat komponen dibuat, diperbarui, atau dihapus), dan
-
Store global (state bersama yang bisa digunakan antar komponen).
Dalam artikel ini, kita akan menambahkan ketiga fitur penting itu agar mini framework kita bertransformasi menjadi SPA (Single Page Application) framework sederhana.
Menambahkan Router Sederhana
Router memungkinkan kita berpindah antar halaman tanpa memuat ulang seluruh situs.
Langkah-langkahnya:
Buat Router.js
const Router = {
routes: {},
root: null,
init(rootId) {
this.root = document.getElementById(rootId);
window.addEventListener("hashchange", () => this.render());
this.render();
},
add(path, component) {
this.routes[path] = component;
},
render() {
const path = window.location.hash.replace("#", "") || "/";
const component = this.routes[path];
if (component) {
this.root.innerHTML = "";
createApp(component, this.root.id);
} else {
this.root.innerHTML = "<h2>404 - Page Not Found</h2>";
}
}
};
Contoh Penggunaan Router:
const Home = {
state: {},
view: () => h("div", null,
h("h2", null, "Halaman Home"),
h("a", { href: "#/about" }, "Ke Halaman About")
)
};
const About = {
state: {},
view: () => h("div", null,
h("h2", null, "Halaman About"),
h("a", { href: "#/" }, "Kembali ke Home")
)
};
Router.add("/", Home);
Router.add("/about", About);
Router.init("root");
Sekarang kamu bisa navigasi antar halaman dengan # tanpa reload!
Contoh:#/ → Home#/about → About
Lifecycle Hook (onMount, onUpdate, onDestroy)
Lifecycle memungkinkan kita menjalankan fungsi tertentu saat:
-
Komponen dipasang (mount) ke DOM,
-
Komponen diperbarui, atau
-
Komponen dihapus.
Tambahkan dukungan ini ke fungsi createApp().
Perbarui createApp()
function createApp(component, rootId) {
const root = document.getElementById(rootId);
let oldVNode = null;
let state = component.state;
function renderApp() {
const newVNode = component.view(state);
if (oldVNode && component.onUpdate) component.onUpdate();
updateElement(root, newVNode, oldVNode);
oldVNode = newVNode;
}
component.setState = (newState) => {
state = { ...state, ...newState };
renderApp();
};
if (component.onMount) component.onMount();
renderApp();
window.addEventListener("beforeunload", () => {
if (component.onDestroy) component.onDestroy();
});
}
Contoh Penggunaan Lifecycle
const Counter = {
state: { count: 0 },
onMount() {
console.log("Counter dipasang ke DOM!");
},
onUpdate() {
console.log("Counter diperbarui!");
},
onDestroy() {
console.log("Counter dihapus dari DOM!");
},
view: (state) =>
h("div", null,
h("h3", null, `Count: ${state.count}`),
h("button", { onclick: "Counter.setState({count: Counter.state.count + 1})" }, "Tambah")
)
};
Sekarang kamu punya event lifecycle seperti di React (componentDidMount, componentDidUpdate, componentWillUnmount).
Store Global (State Bersama untuk Semua Komponen)
Store global memungkinkan banyak komponen berbagi data tanpa harus saling mengirim props.
Membuat Store.js
const Store = {
state: {},
listeners: [],
init(initialState) {
this.state = { ...initialState };
},
getState() {
return this.state;
},
setState(newState) {
this.state = { ...this.state, ...newState };
this.listeners.forEach(fn => fn(this.state));
},
subscribe(fn) {
this.listeners.push(fn);
}
};
Contoh Penggunaan Store
Store.init({ totalItem: 0 });
const Cart = {
state: {},
view: () =>
h("div", null,
h("h3", null, `Total Item: ${Store.getState().totalItem}`),
h("button", { onclick: "Store.setState({ totalItem: Store.getState().totalItem + 1 })" }, "Tambah Item")
)
};
Store.subscribe((state) => {
console.log("Store diperbarui:", state);
document.getElementById("root").innerHTML = "";
createApp(Cart, "root");
});
Sekarang setiap kali Store diperbarui, semua komponen yang terhubung akan ikut merender ulang.
Menggabungkan Semuanya
Kita bisa membuat aplikasi sederhana dengan 2 halaman dan state global:
Store.init({ count: 0 });
const Home = {
view: () =>
h("div", null,
h("h2", null, "Home"),
h("p", null, `Counter: ${Store.getState().count}`),
h("button", { onclick: "Store.setState({count: Store.getState().count + 1})" }, "Tambah"),
h("a", { href: '#/about' }, "Ke Halaman About")
)
};
const About = {
view: () =>
h("div", null,
h("h2", null, "About Page"),
h("p", null, `Total Count: ${Store.getState().count}`),
h("a", { href: '#/' }, "Kembali ke Home")
)
};
Router.add("/", Home);
Router.add("/about", About);
Router.init("root");
Kini aplikasi kamu:
✅ Bisa berpindah halaman tanpa reload
✅ Memiliki global state management
✅ Mendukung lifecycle hooks
Pengembangan Lebih Lanjut
Setelah memiliki router, lifecycle, dan store global, kamu bisa menambahkan fitur lanjutan seperti:
-
Custom event binding (
onClick,onInputseperti React). -
Templating engine berbasis tag.
-
Component nesting (komponen dalam komponen).
-
Persistensi state ke LocalStorage.
-
Hot reload sederhana untuk dev mode.
Dengan semua ini, mini framework kamu sudah berada satu level di bawah framework besar seperti React atau Vue — namun kamu benar-benar memahaminya dari dasar.
Kesimpulan
Menambahkan router, lifecycle, dan store global membuat mini framework JavaScript kita berkembang dari sekadar alat render menjadi sistem SPA (Single Page Application) yang lengkap.
Dengan memahami setiap lapis pembentuknya, kamu tidak hanya belajar cara pakai framework modern, tetapi juga cara membuatnya dari nol — sesuatu yang akan sangat berguna dalam karier sebagai developer frontend tingkat lanjut.