WEB-COMPONENTS FRONTEND REACT VUE ANGULAR INTEROPERABILITY FRAMEWORK-AGNOSTIC UI-UX COMPONENT-DEVELOPMENT SHADOW-DOM BEST-PRACTICES WEB-STANDARDS

Mengintegrasikan Web Components dengan Framework Frontend Modern (React, Vue, Angular): Tips dan Trik untuk Interoperabilitas yang Mulus

⏱️ 10 menit baca
👨‍💻

1. Pendahuluan

Di dunia pengembangan web yang dinamis, kita sering dihadapkan pada pilihan teknologi yang beragam. Di satu sisi, ada framework frontend modern seperti React, Vue, dan Angular yang menawarkan ekosistem kaya dan pola pengembangan yang efisien. Di sisi lain, ada Web Components, standar web native yang memungkinkan kita membangun komponen UI yang reusable dan framework-agnostic.

Pertanyaannya, bisakah kita memanfaatkan kedua dunia ini secara bersamaan? Jawabannya adalah ya! Mengintegrasikan Web Components dengan framework frontend modern bukan hanya mungkin, tapi juga menawarkan segudang manfaat:

Namun, seperti halnya setiap integrasi, ada tantangan. Frameworks memiliki cara mereka sendiri dalam mengelola data, event, dan styling. Memahami bagaimana Web Components berinteraksi dengan mekanisme internal framework adalah kunci untuk interoperabilitas yang mulus. Artikel ini akan memandu Anda melalui tips dan trik praktis untuk mengintegrasikan Web Components dengan React, Vue, dan Angular.

2. Memahami Dasar Interoperabilitas

Inti dari Web Components adalah bahwa mereka adalah native HTML elements. Ini berarti browser mengenali mereka seperti <p>, <div>, atau <a>. Framework frontend, pada dasarnya, juga bekerja dengan elemen HTML. Ini adalah fondasi utama interoperabilitas.

📌 Poin Penting:

Mari kita lihat bagaimana framework populer menangani hal-hal ini.

3. Integrasi dengan React

React seringkali menjadi framework yang sedikit “rewel” dalam berinteraksi dengan Web Components karena ia memiliki sistem event sintetisnya sendiri dan cenderung mengabaikan atribut non-standar pada elemen DOM.

💡 Tips Praktis:

  1. Passing Data: Gunakan atribut HTML untuk meneruskan data sederhana. Untuk data kompleks (objek/array), Anda mungkin perlu meneruskannya sebagai prop JavaScript ke komponen pembungkus React, lalu secara manual menetapkannya ke properti Web Component menggunakan ref dan useEffect.
  2. Menangani Event: React tidak secara otomatis mendengarkan custom events dari Web Components. Anda perlu menambahkan event listener secara manual menggunakan ref dan useEffect.
  3. Membungkus Web Component: Seringkali lebih baik membungkus Web Component di dalam komponen React untuk mengelola interaksi.
// my-web-component.js (contoh Web Component)
class MyWebComponent extends HTMLElement {
  static get observedAttributes() { return ['name']; }

  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>span { color: blue; }</style>
      <span>Halo, <slot>Dunia</slot>!</span>
      <button>Klik Saya</button>
    `;
  }

  connectedCallback() {
    this.shadowRoot.querySelector('button').addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('buttonClick', {
        detail: { message: `Tombol diklik oleh ${this.getAttribute('name') || 'anonim'}` },
        bubbles: true,
        composed: true
      }));
    });
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'name') {
      this.shadowRoot.querySelector('span').textContent = `Halo, ${newValue || 'Dunia'}!`;
    }
  }
}
customElements.define('my-web-component', MyWebComponent);

// App.js (Integrasi dengan React)
import React, { useRef, useEffect } from 'react';
import './my-web-component'; // Pastikan Web Component terdaftar

function App() {
  const webComponentRef = useRef(null);

  useEffect(() => {
    const handleButtonClick = (event) => {
      console.log('Custom event diterima di React:', event.detail.message);
      alert(event.detail.message);
    };

    const currentRef = webComponentRef.current;
    if (currentRef) {
      // Menambahkan event listener untuk custom event
      currentRef.addEventListener('buttonClick', handleButtonClick);
    }

    return () => {
      if (currentRef) {
        currentRef.removeEventListener('buttonClick', handleButtonClick);
      }
    };
  }, []);

  // Membungkus Web Component dalam komponen React
  const MyReactWebComponent = ({ name, onButtonClick }) => {
    const ref = useRef(null);

    useEffect(() => {
      const currentRef = ref.current;
      if (currentRef && onButtonClick) {
        currentRef.addEventListener('buttonClick', onButtonClick);
      }
      return () => {
        if (currentRef && onButtonClick) {
          currentRef.removeEventListener('buttonClick', onButtonClick);
        }
      };
    }, [onButtonClick]);

    // React meneruskan prop 'name' sebagai atribut HTML secara otomatis
    return <my-web-component ref={ref} name={name}>React User</my-web-component>;
  };

  const handleGlobalButtonClick = (event) => {
    console.log('Event dari komponen pembungkus:', event.detail.message);
  };

  return (
    <div>
      <h1>Integrasi Web Component dengan React</h1>
      <MyReactWebComponent name="React Developer" onButtonClick={handleGlobalButtonClick} />

      {/* Contoh langsung (kurang disarankan untuk event handling) */}
      <my-web-component ref={webComponentRef} name="Langsung">
        <p>Konten dari React</p>
      </my-web-component>
    </div>
  );
}

export default App;

Dalam contoh React, kita perlu secara eksplisit menambahkan dan menghapus event listener menggunakan useEffect dan useRef untuk custom event. Untuk data, React meneruskan string props sebagai atribut secara default.

4. Integrasi dengan Vue

Vue memiliki dukungan yang lebih baik untuk Web Components secara out-of-the-box dibandingkan React, berkat sistem event dan prop-nya yang lebih fleksibel.

💡 Tips Praktis:

  1. Passing Data: Vue secara otomatis akan meneruskan props yang Anda deklarasikan di komponen Vue sebagai atribut HTML ke Web Component.
  2. Menangani Event: Vue secara otomatis akan mendengarkan custom events yang dipancarkan oleh Web Components. Anda bisa menggunakan v-on seperti biasa.
  3. Slot Content: Vue bisa meneruskan konten ke slot Web Component dengan mudah.
<!-- my-web-component.js (sama seperti di atas) -->

<!-- App.vue (Integrasi dengan Vue) -->
<template>
  <div>
    <h1>Integrasi Web Component dengan Vue</h1>
    <!-- Vue secara otomatis meneruskan 'name' sebagai atribut dan mendengarkan 'buttonClick' -->
    <my-web-component :name="vueUserName" @buttonClick="handleButtonClick">
      <p>Konten dari Vue</p>
    </my-web-component>
  </div>
</template>

<script>
import './my-web-component'; // Pastikan Web Component terdaftar

export default {
  data() {
    return {
      vueUserName: 'Vue Developer'
    };
  },
  methods: {
    handleButtonClick(event) {
      console.log('Custom event diterima di Vue:', event.detail.message);
      alert(event.detail.message);
    }
  }
};
</script>

Vue secara intuitif menangani atribut dan custom event, membuat integrasi terasa lebih natural.

5. Integrasi dengan Angular

Angular juga memiliki dukungan yang cukup baik untuk Web Components, terutama dengan adanya CUSTOM_ELEMENTS_SCHEMA yang memberitahu Angular untuk tidak mengeluh tentang elemen HTML yang tidak dikenal.

💡 Tips Praktis:

  1. Passing Data: Anda bisa menggunakan input properties Angular ([property]="value") untuk meneruskan data ke Web Component. Angular akan meneruskannya sebagai atribut.
  2. Menangani Event: Gunakan output properties Angular ((event)="handler()") untuk mendengarkan custom events dari Web Component.
  3. Content Projection: Angular mendukung content projection ke slot Web Component.
  4. Schema Configuration: Tambahkan CUSTOM_ELEMENTS_SCHEMA ke NgModule Anda.
// my-web-component.js (sama seperti di atas)

// app.module.ts (Konfigurasi Angular)
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

// Pastikan Web Component terdaftar sebelum Angular bootstrap
import './my-web-component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA] // Ini penting!
})
export class AppModule { }

// app.component.ts (Integrasi dengan Angular)
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Integrasi Web Component dengan Angular</h1>
    <!-- Angular meneruskan input 'name' sebagai atribut dan mendengarkan event 'buttonClick' -->
    <my-web-component [name]="angularUserName" (buttonClick)="handleButtonClick($event)">
      <p>Konten dari Angular</p>
    </my-web-component>
  `,
  styles: []
})
export class AppComponent {
  angularUserName = 'Angular Developer';

  handleButtonClick(event: CustomEvent) {
    console.log('Custom event diterima di Angular:', event.detail.message);
    alert(event.detail.message);
  }
}

CUSTOM_ELEMENTS_SCHEMA adalah kunci untuk menginformasikan Angular agar tidak memperlakukan Web Components sebagai elemen yang tidak dikenal, memungkinkan integrasi yang mulus untuk data dan event.

6. Tantangan Umum dan Solusi

Meskipun integrasi dasar sudah kita bahas, ada beberapa tantangan umum yang mungkin Anda temui:

🎨 Styling

🔄 State Management

⚡ Server-Side Rendering (SSR)