DEV Community

Aisyah
Aisyah

Posted on

Immutable JavaScript

Saat membuat aplikasi JavaScript, salah satu sumber masalah (bug) yang paling sering ditemui adalah perubahan data yang tidak sengaja (state mutation). Ketika sebuah data diubah langsung di satu tempat, bagian code lain yang menggunakan data tersebut bisa terpengaruh tanpa disadari.

Apa itu Immutable.js?

Prinsip dasarnya sederhana: data yang sudah dibuat tidak bisa diubah lagi (immutable).

Jika ingin melakukan operasi perubahan (seperti menambahkan atau mengganti elemen), Immutable.js tidak mengutak-atik data lama secara langsung (in-place), melainkan menghasilkan koleksi data baru yang sudah diperbarui.

Mengapa Immutable.js?

  • Pengembangan Lebih Sederhana, menghindari kebutuhan membuat salinan manual (defensive copying) hanya demi melindungi data.
  • Mendukung Fungsi Murni (Pure Functions), mendorong arsitektur data-in, data-out tanpa side-effects.
  • Deteksi Perubahan Cepat dan Efisien, deteksi perubahan dan memoization menjadi sangat ringan dengan memanfaatkan kesetaraan referensi (===) maupun kesetaraan nilai (value equality).
  • Hemat Memori (Structural Sharing), data baru dan data lama saling berbagi simpul struktur internal di memori, sehingga proses pembuatan data baru tidak menyalin seluruh isi data dari nol.

Cara Instalasi

Immutable.js dapat dipasang menggunakan package manager atau langsung melalui CDN di browser.

Menggunakan npm / yarn / pnpm / bun
Jalankan salah satu perintah berikut di terminal proyek yang dibuat:

# Menggunakan npm
npm install immutable

# Menggunakan yarn
yarn add immutable

# Menggunakan pnpm
pnpm add immutable

# Menggunakan bun
bun add immutable
Enter fullscreen mode Exit fullscreen mode

Kemudian, impor ke modul mana pun:

// Menggunakan ES Modules (import)
import { Map, List } from 'immutable';

// Atau menggunakan CommonJS (require)
const { Map, List } = require('immutable');
Enter fullscreen mode Exit fullscreen mode

Menggunakan Browser / CDN
Jika tidak menggunakan bundler atau build tools, bisa memuatnya langsung via CDN (seperti CDNJS atau jsDelivr):

<!-- Menggunakan script tag standar -->
<script src="https://cdn.jsdelivr.net/npm/immutable@5.1.9/dist/immutable.min.js"></script>
<script>
    const map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
    const map2 = map1.set('b', 50);

    console.log('map1', map1.get('b')) // 2
    console.log('map2', map2.get('b')) // 50
</script>
Enter fullscreen mode Exit fullscreen mode

API berbasis JavaScript (JavaScript-first API)

Meskipun mengusung konsep pemrograman fungsional, Immutable.js tetap menggunakan gaya penulisan yang sangat familiar bagi pengembang JavaScript (menggunakan pemanggilan perintah bertanda titik seperti data.push() atau data.set()).

Bedanya ada pada behavior nya saat mengubah data:

  • Pada JavaScript biasa: Perintah seperti push() atau splice() akan langsung merusak dan mengubah isi data asli di tempat.
  • Pada Immutable.js: Perintah seperti push(), set(), splice(), maupun concat() tidak pernah merusak data lama. Setiap kali dipanggil, perintah tersebut selalu menghasilkan koleksi data baru yang terpisah dan membiarkan data lama tetap utuh.

Contoh: List (Pengganti Array)

import { List } from 'immutable';

// Membuat List baru
const list1 = List([1, 2]);

// Menambahkan data (tidak mengubah list1)
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);

console.log(list1.size); // 2  (data awal tetap utuh)
console.log(list2.size); // 5
console.log(list3.size); // 6
console.log(list4.size); // 13
Enter fullscreen mode Exit fullscreen mode

Contoh: Map (Pengganti Objek Key-Value)

import { Map } from 'immutable';
const map1 = Map({ a: 1, b: 2, c: 3 });

// Mengubah nilai key 'b'
const map2 = map1.set('b', 50);

console.log(map1.get('b')); // 2  (data lama tidak tersentuh)
console.log(map2.get('b')); // 50 (data baru)

// Optimalisasi performa: jika nilai yang di-set sama, referensinya tetap sama
const map3 = map1.set('b', 2);
console.log(map1 === map3); // true
Enter fullscreen mode Exit fullscreen mode

Core Collections di Immutable.js v5

Dokumentasi v5 menyediakan beragam struktur data sesuai kebutuhan:

Collection Deskripsi Standard JS Equivalent
List Daftar data berurutan dengan indeks posisi (mirip daftar antrean) Array
Map Kumpulan data berpasangan antara key dan value Object / Map
OrderedMap Mirip Map, namun urutan datanya dijamin sesuai saat dimasukkan via set() -
Set Kumpulan nilai unik yang otomatis mengabaikan data duplikat Set
OrderedSet Mirip Set, namun tetap menjaga urutan data sesuai waktu dimasukkan -
Stack Daftar data yang sangat cepat untuk penambahan dan penghapusan dari urutan paling depan -

Contoh kode penggunaan Core Collections:

import { List, Map, OrderedMap, Set, OrderedSet, Stack, Record } from 'immutable';

// 1. List
const list = List([1, 2, 3]);
const newList = list.push(4); // List [ 1, 2, 3, 4 ]

// 2. Map
const map = Map({ name: 'Nailong', age: 25 });
const newMap = map.set('age', 26); // Map { "name": "Nailong", "age": 26 }

// 3. OrderedMap (Urutan iterasi terjamin sesuai urutan set)
let orderedMap = OrderedMap();
orderedMap = orderedMap.set('z', 100).set('a', 200);
console.log(orderedMap.keys().toArray()); // [ 'z', 'a' ]

// 4. Set (Nilai unik, duplikat otomatis diabaikan)
const set = Set([1, 2, 2, 3]); // Set [ 1, 2, 3 ]

// 5. OrderedSet (Nilai unik yang menjaga urutan penambahan)
const orderedSet = OrderedSet([3, 1, 2, 1]); // OrderedSet [ 3, 1, 2 ]

// 6. Stack (Sangat efisien untuk operasi dari depan)
const stack = Stack([1, 2, 3]);
const newStack = stack.unshift(0); // Stack [ 0, 1, 2, 3 ]

// 7. Record (Struktur mirip objek dengan key tetap & nilai default)
const UserRecord = Record({ name: 'Tanpa Nama', role: 'User' });

const user1 = UserRecord({ name: 'Nailong }); // Record { "name": "Nailong", "role": "User" };

console.log(user1.name); // 'Nailong' (Bisa diakses langsung seperti properti objek)
Enter fullscreen mode Exit fullscreen mode

Fitur-Fitur Penting

A. Membaca & Mengubah Nested Data
Jika data memiliki struktur bertingkat (nested), maka tidak perlu menyalin atau membongkar objek satu per satu secara manual. Immutable.js menyediakan fungsi-fungsi pembantu seperti getIn, setIn, updateIn, dan mergeDeep:

import { fromJS } from 'immutable';
const data = fromJS({
  user: {
    profile: {
      name: 'Nailong',
      city: 'Jakarta'
    }
  }
});

// Mengakses nested data
const name = data.getIn(['user', 'profile', 'name']); // 'Nailong'

// Mengubah nested data tanpa mengubah variabel 'data'
const newData = data.setIn(['user', 'profile', 'city'], 'Bandung');

console.log(data.getIn(['user', 'profile', 'city']));    // 'Jakarta'
console.log(newData.getIn(['user', 'profile', 'city'])); // 'Bandung'
Enter fullscreen mode Exit fullscreen mode

B. Lazy Evaluation (Seq)
Fitur Seq (Sequence) di Immutable.js memungkinkan proses berantai seperti .map() dan .filter() dieksekusi secara lazy (ditunda sampai hasil akhirnya benar-benar dibutuhkan), tanpa membuat koleksi perantara (intermediate collections).

Mengapa ini penting? (Analogi Sederhana)

  1. Mengalikan setiap angka dengan 2 (.map())
  2. Mengambil angka yang lebih besar dari 10 (.filter())
  3. Mengambil 2 angka pertama saja
    • .slice(0, 2): JavaScript biasa
    • .take(2): Immutable.js dengan Seq
  4. Tanpa Seq (Cara Biasa): Program akan mengalikan seluruh 1.000.000 angka di memori (membuat Array baru #1), lalu memfilter seluruh 1.000.000 angka tersebut (membuat Array baru #2), baru kemudian mengambil 2 angka teratas. Ini membuang memori dan waktu komputasi untuk 999.997 data sisanya.
  5. Dengan Seq (Lazy Evaluation): Program tidak melakukan apa-apa dulu saat menulis .map() dan .filter(). Program baru berjalan ketika meminta hasilnya (misal via .take(2) atau .toJS()). Hasilnya, program hanya akan memproses 2 data pertama yang memenuhi syarat, lalu langsung berhenti.
import { Seq } from 'immutable';
const arrayNumber = Seq([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

// Operasi chaining ini BELUM dieksekusi di memori
const result = arrayNumber
  .filter(x => x % 2 === 0) // Cari angka genap
  .map(x => x * 10)         // Kalikan 10
  .take(2);                 // Ambil 2 data pertama saja, ini merupakan metode khusus Immutable.js untuk membatasi eksekusi

// Eksekusi BARU berjalan di sini, dan HANYA memproses data seperlunya
console.log(result.toArray()); // [20, 40]
Enter fullscreen mode Exit fullscreen mode

Keunggulannya: Metode .take(2) di Seq memberi tahu mesin: "Cukup proses sampai dapat 2 data yang cocok, setelah itu STOP!". Angka 5 sampai 10 sama sekali tidak diubah atau diperiksa.

C. Menggabungkan Banyak Perubahan Sekaligus (Batching Mutations)
Jika harus melakukan ratusan modifikasi sekaligus, membuat objek baru di setiap langkah kecil bisa membebani performa. Immutable.js menyediakan .withMutations() untuk menerapkan beberapa perubahan ke salinan sementara yang mutable sebelum mengembalikannya menjadi satu koleksi immutable baru:

import { List } from 'immutable';
const initialList = List([1, 2, 3]);

const result = initialList.withMutations((mutable) => {
  mutable.push(4).push(5).push(6);
});

console.log(result.toArray()); // [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

D. Kompatibilitas dengan JavaScript Biasa
Immutable.js dapat berinteraksi langsung dengan tipe data bawaan JavaScript:
1. Mengubah Data Biasa ke Immutable: fromJS(...)
Fungsi fromJS() melakukan konversi otomatis secara menyeluruh (deep conversion). Jika memasukkan objek atau array JavaScript biasa yang bertingkat (nested), fungsi ini akan mengubah seluruh tingkatan sampai ke dalam menjadi koleksi Immutable.js.

import { fromJS } from 'immutable';
const data = {
  user: {
    name: "Nailong",
    hobby: ["membaca", "berenang"] // Array di dalam objek
  }
};

// Mengubah seluruh tingkatan data menjadi Immutable
const immutableData = fromJS(data);

// 'hobby' di tingkat dalam otomatis berubah menjadi List Immutable
console.log(immutableData.getIn(['user', 'hobby']).push('memasak')); // List [ "membaca", "berenang", "memasak" ]
Enter fullscreen mode Exit fullscreen mode

2. Mengubah Kembali ke JavaScript Biasa: .toJS() & .toArray()
Saat ingin mengirim data ke library atau tampilan UI lain yang hanya menerima tipe data JavaScript polos, maka bisa mengembalikannya dengan sangat mudah:

  • .toJS() (Konversi Menyeluruh / Deep): Mengubah seluruh tingkatan data kembali menjadi Objek atau Array JavaScript biasa
  • .toArray() / .toObject() (Konversi Terluar / Shallow): Hanya mengubah tingkatan terluar saja menjadi Array atau Objek biasa
import { Map, List } from 'immutable';
const immutableData = Map({
  name: 'Nailong',
  hobby: List(['membaca', 'berenang'])
});

// Mengubah seluruh struktur kembali ke objek JavaScript biasa
const plainJSData = immutableData.toJS();

console.log(plainJSData); 
// Output: { name: 'Nailong', hobby: ['membaca', 'berenang'] }
Enter fullscreen mode Exit fullscreen mode

3. Menggabungkan Langsung dengan Array Biasa ([...])
Semua koleksi di Immutable.js bersifat Iterable. Artinya, bisa menggabungkan atau mengurai isi data Immutable.js langsung ke dalam Array biasa menggunakan spread operator ([...]) atau perulangan (looping) for...of.

import { List } from 'immutable';
const aList = List([1, 2, 3]);

// 1. Mengurai List ke dalam Array biasa menggunakan [...]
const standardArray = [0, ...aList, 4, 5];
console.log(standardArray); // Output: [0, 1, 2, 3, 4, 5]

// 2. Menggunakan perulangan (loop) for...of
for (const item of aList) {
  console.log(item); // Mencetak 1, lalu 2, lalu 3
}
Enter fullscreen mode Exit fullscreen mode

Kesimpulan

Immutable.js membuat pengelolaan data di aplikasi jauh lebih aman tanpa membuat aplikasi terasa lambat. Dengan gaya penulisan yang mirip JavaScript biasa, library ini membantu mencegah data terubah secara tidak sengaja, sehingga aplikasi menjadi lebih stabil, mudah diprediksi, dan bebas dari masalah (bug) yang membingungkan.

References

Immutable.js

Top comments (0)