Mengenal Provide dan Inject di Vue.js: Berbagi Data antar Komponen Tanpa Props

Dalam pengembangan aplikasi menggunakan Vue.js, salah satu tantangan umum adalah bagaimana cara membagikan data antara komponen tanpa membuat rantai props yang panjang dan rumit. Biasanya, data dikirim dari parent ke child menggunakan props, lalu dikirim kembali ke parent dengan emit. Namun, ketika struktur komponen semakin dalam — misalnya parent → child → grandchild → great-grandchild — pola ini menjadi tidak efisien dan sulit dikelola.

Untuk mengatasi hal tersebut, Vue menyediakan fitur provide dan inject, yang memungkinkan data dibagikan secara langsung dari komponen induk ke turunan mana pun, tanpa harus melewati setiap lapisan props.

Artikel ini akan membahas secara lengkap bagaimana provide dan inject bekerja, kapan sebaiknya digunakan, dan bagaimana menggunakannya dengan benar dalam konteks aplikasi Vue 3.


1. Mengapa Kita Perlu provide dan inject

Bayangkan kita memiliki struktur komponen seperti ini:

App.vue
 ├─ Dashboard.vue
 │   ├─ Sidebar.vue
 │   │   └─ SidebarItem.vue

Misalnya kita ingin mengirimkan data pengguna (user) dari App.vue ke SidebarItem.vue. Dengan pendekatan props, kita harus meneruskan data melalui setiap komponen:

<!-- App.vue -->
<Dashboard :user="user" />
<!-- Dashboard.vue -->
<Sidebar :user="user" />
<!-- Sidebar.vue -->
<SidebarItem :user="user" />

Setiap level harus meneruskan props yang sama, meskipun tidak semua komponen tersebut benar-benar membutuhkan data itu. Ini disebut prop drilling, dan bisa menjadi sangat membingungkan pada proyek besar.

Nah, provide dan inject hadir untuk menghilangkan masalah tersebut. Dengan provide, komponen induk dapat “menyediakan” data, dan dengan inject, komponen turunan mana pun dapat “mengambil” data itu — tanpa harus melewati props di setiap lapisan.


2. Cara Kerja provide dan inject

Konsepnya sederhana:

  • provide mendefinisikan data atau fungsi yang dapat diakses oleh komponen di bawahnya.
  • inject digunakan oleh komponen anak (atau cucu, dst.) untuk mengambil data tersebut.

Secara teknis, Vue menggunakan context inheritance — komponen turunan dapat mewarisi konteks dari parent chain di atasnya.


3. Contoh Dasar Penggunaan

a. Komponen Induk (App.vue)

<script setup>
import { provide, ref } from 'vue'
import Child from './Child.vue'

const user = ref({
  name: 'Budi',
  role: 'Administrator'
})

// menyediakan data agar bisa diakses oleh komponen turunan
provide('user', user)
</script>

<template>
  <div>
    <h2>Aplikasi Vue</h2>
    <Child />
  </div>
</template>

b. Komponen Anak (Child.vue)

<script setup>
import { inject } from 'vue'

const user = inject('user')
</script>

<template>
  <div>
    <p>Nama pengguna: {{ user.name }}</p>
    <p>Peran: {{ user.role }}</p>
  </div>
</template>

Penjelasan:

  • Komponen App.vue menggunakan provide('user', user) untuk “menyediakan” data.
  • Komponen Child.vue cukup memanggil inject('user') untuk mengaksesnya, tanpa props.
  • Data tetap reaktif, karena kita menggunakan ref.

4. Menyediakan Lebih dari Satu Nilai

Kita bisa menyediakan beberapa data sekaligus dengan memanggil provide() berkali-kali:

provide('user', user)
provide('theme', ref('dark'))
provide('language', ref('id'))

Kemudian, di komponen lain, cukup injeksikan sesuai kebutuhan:

const theme = inject('theme')
const language = inject('language')

Ini membuat setiap bagian aplikasi hanya mengambil data yang relevan, tanpa harus membawa semua props dari parent.


5. Menggunakan reactive pada Provide

Kita juga bisa menggunakan reactive() untuk menyediakan objek kompleks:

<script setup>
import { provide, reactive } from 'vue'
import SettingsPanel from './SettingsPanel.vue'

const settings = reactive({
  theme: 'light',
  notifications: true
})

provide('settings', settings)
</script>

<template>
  <SettingsPanel />
</template>

Lalu di komponen anak:

<script setup>
import { inject } from 'vue'
const settings = inject('settings')
</script>

<template>
  <div>
    <p>Tema: {{ settings.theme }}</p>
    <button @click="settings.theme = 'dark'">Ubah ke Dark Mode</button>
  </div>
</template>

Vue akan memperbarui tampilan di semua komponen yang menggunakan settings secara otomatis, karena reactive membuat semua propertinya reaktif.


6. Menggunakan Provide dan Inject dengan Function

Selain data, kita juga bisa membagikan fungsi antar komponen.
Ini berguna untuk membuat API lokal antar bagian UI.

Contoh:

Induk (App.vue)

<script setup>
import { provide } from 'vue'
import Child from './Child.vue'

function logMessage(msg) {
  console.log('Dari parent:', msg)
}

provide('logger', logMessage)
</script>

<template>
  <Child />
</template>

Anak (Child.vue)

<script setup>
import { inject } from 'vue'
const logger = inject('logger')
</script>

<template>
  <button @click="logger('Tombol diklik!')">Klik Saya</button>
</template>

Dengan cara ini, komponen Child dapat memanggil fungsi yang didefinisikan di parent tanpa harus lewat props atau event.


7. Menggunakan Default Value di inject

Jika komponen anak mencoba mengakses sesuatu yang belum disediakan dengan provide, maka hasilnya undefined.
Namun, kita bisa menambahkan nilai default agar lebih aman:

const theme = inject('theme', 'light')

Jika tidak ada provide('theme'), maka theme akan otomatis bernilai 'light'.


8. Studi Kasus: Manajemen Tema Global

Kita bisa menggunakan provide dan inject untuk mengelola tema global aplikasi dengan mudah.

App.vue

<script setup>
import { ref, provide } from 'vue'
import Navbar from './Navbar.vue'
import Footer from './Footer.vue'

const theme = ref('light')

function toggleTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>

<template>
  <div :class="theme">
    <Navbar />
    <main>
      <p>Konten utama di sini</p>
    </main>
    <Footer />
  </div>
</template>

Navbar.vue

<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
  <nav>
    <p>Tema sekarang: {{ theme }}</p>
    <button @click="toggleTheme()">Ubah Tema</button>
  </nav>
</template>

Footer.vue

<script setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>

<template>
  <footer :class="theme">
    <p>Footer dalam tema: {{ theme }}</p>
  </footer>
</template>

Dengan pendekatan ini, semua komponen di dalam aplikasi otomatis mengetahui tema yang sedang digunakan dan dapat beradaptasi secara dinamis, tanpa perlu meneruskan props di setiap lapisan.


9. Kapan Menggunakan Provide & Inject

Gunakan ketika:

  • Data perlu diakses oleh banyak komponen di bawah satu hirarki (misal tema, konfigurasi, user session).
  • Anda ingin menghindari prop drilling pada struktur komponen yang dalam.
  • Anda membuat komponen library atau layout kompleks seperti sidebar, modal manager, atau form wizard.

Hindari jika:

  • Data hanya dibutuhkan oleh parent dan satu child langsung (props lebih jelas).
  • Anda membutuhkan state global lintas halaman — gunakan Vuex atau Pinia untuk itu.

10. Best Practices

Gunakan nama key yang jelas dan konsisten.
Contoh: 'theme', 'userContext', 'config'.

Gunakan Symbol sebagai key jika proyek besar, untuk menghindari konflik key antar modul.

const themeKey = Symbol('theme')
provide(themeKey, theme)
inject(themeKey)

Dokumentasikan data yang di-provide.
Agar developer lain tahu data apa yang tersedia dari parent.

Jangan jadikan provide sebagai pengganti store global.
Jika data perlu diakses lintas halaman, gunakan Pinia atau Vuex.


Kesimpulan

provide dan inject adalah fitur elegan di Vue 3 untuk berbagi data antar komponen tanpa harus melewati props berlapis. Fitur ini sangat berguna untuk:

  • Membuat komponen kompleks seperti layout, modal, dan form group.
  • Menyediakan data global dalam satu konteks tertentu.
  • Menjaga kode tetap bersih dan mudah dipelihara.

Dengan memahami cara kerja dan batasan provide serta inject, kita dapat membuat arsitektur komponen yang lebih efisien, modular, dan fleksibel tanpa harus bergantung pada store global di setiap kasus kecil.