DEV Community

Armedi
Armedi

Posted on • Originally published at Medium on

Menggunakan Vue Reactivity pada Komponen React

Bagi anda yang berkecimpung di dunia frontend web development, pasti sudah tidak asing dengan nama React atau Vue. Dua library yang paling populer saat ini untuk membangun aplikasi web modern.

Para penggemar framework Vue pun pastinya sudah tahu bahwa tidak lama lagi Vue akan merilis versi 3. Versi baru ini tidak hanya menambahkan fitur baru, tapi benar-benar rewrite dari awal (Lebih lanjut: https://increment.com/frontend/making-vue-3/).

Salah satu fitur baru di Vue 3 yang mungkin paling banyak disorot adalah Composition API (Tutorial cara menggunakan Composition API pernah dibuat oleh teman saya Semmi Verian di video youtube-nya). Namun hal yang lebih menarik bagi saya pribadi adalah model reaktivitas baru yang sama sekali berbeda dengan versi sebelumnya.

Lebih menariknya lagi, modul reaktivitas ini dibuat pada package terpisah, yang artinya tentu saja bisa kita pakai pada aplikasi lain tanpa harus menggunakan framework Vue.

Nah karena saya sama-sama menyukai React dan Vue, jadi saya memutuskan untuk mencoba modul @vue/reactivity pada aplikasi yang dibuat menggunakan React.

Tujuan akhir dari tulisan ini adalah membahas bagaimana kita bisa memutasi data tanpa kehilangan reaktivitas dari React dengan menggunakan package @vue/reactivity. Cara seperti ini bukanlah hal baru, tapi juga digunakan oleh library seperti mobx-react (dengan observable dari mobx).

Mutable VS Immutable

Mutability adalah perbedaan mendasar antara React dengan Vue. React memilih jalur full immutable data, sedangkan Vue memilih jalur mutable data.

Lalu apa perbedaannya?

Sebelumnya kita bahas dulu sedikit tentang declarative programming. Dengan library atau framework modern, web developer tidak lagi perlu memikirkan bagaimana meng-update DOM. Cukup dengan mendeklarasikan apa yang dimau, selebihnya mengenai proses update DOM dan bagaimana terjadinya akan diatur oleh library yang dipakai (Artikel ini menjelaskannya dengan lebih baik).

React atau Vue akan mendeteksi perubahan data, dan kemudian melakukan perubahan yang sesuai pada UI. Dan ini terjadi terus-menerus secara otomatis. Atau dalam kata lain, komponen UI bersifat reaktif terhadap perubahan data.

Nah, di sini lah perbedaan antara immutability-nya React dengan mutability Vue. React dengan cara yang sangat sederhana mendeteksi perubahan data menggunakan Object.is. Itulah mengapa kode di bawah tidak akan berjalan sesuai yang diharapkan.

// in react component

const [name, setName] = useState({first: 'John', last: 'Doe'})

const handleChange = (event) => {
  name.first = event.target.value
}
Enter fullscreen mode Exit fullscreen mode

Ketika anda melakukan mutasi terhadap data nameseperti kode diatas, React tidak akan melihat perubahan, sehingga rerender pun tidak akan pernah terjadi. Karena object name masih merupakan object yang sama dengan sebelumnya.

Vue menggunakan cara berbeda yang lebih kompleks untuk mendeteksi perubahan data (lebih jelasnya bisa disimak langsung di dokumentasi resminya). Intinya adalah data pada Vue bisa dimutasi, dan dengan cerdas Vue akan mendeteksi perubahan kemudian melakukan update yang sesuai.

Let’s start playing

Aplikasi yang akan kita buat adalah aplikasi counter dengan fitur increment dan decrement, serta ada satu kolom input teks.

Scroll ke paling bawah jika anda mau langsung melihat source code dan hasil jadinya

npm install @vue/reactivity
Enter fullscreen mode Exit fullscreen mode

Pertama kita akan buat sebuah fungsi HOC bernama setup yang lebih kurang berfungsi sama dengan setup pada composition API Vue. Penggunaanya akan seperti ini:

import React, { useMemo } from 'react';
import { reactive, ref } from '@vue/reactivity';

const setup = (setupFn) => (Component) => (props) => {
  const setupProps = useMemo(setupFn, []);

  // our little trick here

  return <Component {...props} {...setupProps} />;
};

const App = (props) => {
  // This is the react component
};

export default setup(() => {
  const count = ref(0);
  const name = reactive({ first: '' });

  return { count, name };
})(App);
Enter fullscreen mode Exit fullscreen mode

Fungsi setup akan memasukkan return value dari setupFn sebagai props pada komponen. Di sini kita gunakan useMemo agar invokasi hanya terjadi sekali ketika mounting saja.

Nah, sekarang bagaimana caranya untuk “memaksa” komponen agar rerender ketika props ini dimutasi?

@vue/reactivity menyediakan fungsi effect yang fungsinya adalah melakukan “sesuatu” (dengan meng-invoke callback yang diberikan) setiap kali value reaktif yang dipakai di dalam fungsi itu dimutasi. Fungsi effect ini akan kita tempatkan di dalam hooks useEffect React. Jangan lupa untuk menghentikan effect ketika komponen di-unmout dengan me-return sebuah fungsi untuk stop.

import React, { useMemo } from 'react';
import { reactive, ref, effect, stop } from '@vue/reactivity';

const setup = (setupFn) => (Component) => (props) => {
  const setupProps = useMemo(setupFn, []);

  useEffect(() => {
    const reactiveEffect = effect(() => {
      // here we can do something to trigger rerender
    });

    return () => stop(reactiveEffect);
  }, [setupProps]);

  return <Component {...props} {...setupProps} />;
};
Enter fullscreen mode Exit fullscreen mode

Untuk “memaksa” rerender kita akan buat satu state yang value-nya akan di-update ketika terjadi mutasi data.

const setup = (setupFn) => (Component) => (props) => {
  const setupProps = useMemo(setupFn, []);
  const [, setTick] = useState(0);

  useEffect(() => {
    const reactiveEffect = effect(() => {
      // Only one more thing to do here
      setTick((tick) => tick + 1);
    });

    return () => stop(reactiveEffect);
  }, [setupProps]);

  return <Component {...props} {...setupProps} />;
};
Enter fullscreen mode Exit fullscreen mode

Oke, tinggal satu langkah lagi

Sebelumnya saya katakan bahwa fungsi effect akan meng-invoke callback yang diberikan setiap kali value reaktif yang dipakai di dalam fungsi itu dimutasi. Terus bagaimana caranya fungsi ini tau ada mutasi?

Sangat sederhana sekali. Setiap value yang diakses di dalamnya akan secara otomatis di-track. Ya, cukup diakses saja!

useEffect(() => {
  const reactiveEffect = effect(() => {
    setupProps.count.value;
    setupProps.name.first;
    setTick((tick) => tick + 1);
  });

  return () => stop(reactiveEffect);
}, [setupProps]);
Enter fullscreen mode Exit fullscreen mode

Karena di dalam komponen kita akan menggunakan value count dan name.first, dua value ini perlu diakses terlebih dahulu di dalam fungsi effect agar komponen bisa melacak mutasi dan melakukan rerender setiap kali ada perubahan.

Oke, cara diatas sebenarnya sudah “benar”. Tapi akan mengakibatkan error no-unused-expressions pada linter, dan juga membuat fungsi setup hanya bisa dipakai untuk satu komponen ini saja.

Sekarang mari kita generalisasi menjadi fungsi access yang secara rekursif akan mengakses setiap properti di dalam object

import React, { useState, useEffect, useMemo } from 'react';
import { reactive, ref, isRef, effect, stop } from '@vue/reactivity';

const access = (object) => {
  if (isRef(object)) {
    access(object.value);
  } else if (typeof object === 'object') {
    for (let key of Object.keys(object)) {
      access(object[key]);
    }
  }
};

const setup = (setupFn) => (Component) => (props) => {
  const setupProps = useMemo(setupFn, []);
  const [, setTick] = useState(0);
  useEffect(() => {
    const reactiveEffect = effect(() => {
      access(setupProps);
      setTick((tick) => tick + 1);
    });

    return () => stop(reactiveEffect);
  }, [setupProps]);

  return <Component {...props} {...setupProps} />;
};
Enter fullscreen mode Exit fullscreen mode

Akhirnya sekarang di dalam komponen React kita bisa melakukan update data dengan cara memutasinya seperti dibawah ini. Perhatikan onChange pada input dan onClick pada button.

function App({ count, name }) {
  return (
    <div>
      <input
        placeholder="Input your name"
        onChange={(e) => (name.first = e.target.value)}
      />
      <div>
        <div>Hi, {name.first}</div>
        <div>{count.value}</div>
        <div>
          <span>
            <button type="button" onClick={() => count.value--}>
              Decrement
            </button>
          </span>
          <span>
            <button type="button" onClick={() => count.value++}>
              Increment
            </button>
          </span>
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Dari sini anda bisa lanjutkan bereksperimen untuk membuat global state management menggunakan modul reactivity dari Vue

source code:

codesandbox:

AWS Q Developer image

Build your favorite retro game with Amazon Q Developer CLI in the Challenge & win a T-shirt!

Feeling nostalgic? Build Games Challenge is your chance to recreate your favorite retro arcade style game using Amazon Q Developer’s agentic coding experience in the command line interface, Q Developer CLI.

Participate Now

Top comments (0)

Tiger Data image

🐯 🚀 Timescale is now TigerData: Building the Modern PostgreSQL for the Analytical and Agentic Era

We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future.

So we’re changing our name: from Timescale to TigerData. Not to change who we are, but to reflect who we’ve become. TigerData is bold, fast, and built to power the next era of software.

Read more

👋 Kindness is contagious

Explore this insightful piece, celebrated by the caring DEV Community. Programmers from all walks of life are invited to contribute and expand our shared wisdom.

A simple "thank you" can make someone’s day—leave your kudos in the comments below!

On DEV, spreading knowledge paves the way and fortifies our camaraderie. Found this helpful? A brief note of appreciation to the author truly matters.

Let’s Go!