Provide/Inject — Vue

provide dan inject memungkinkan data dilewatkan ke komponen keturunan tanpa prop drilling. // Di parent provide("theme", ref("dark")) provide("appName"…

provide dan inject mengirim data dari satu komponen ke semua keturunannya, sedalam apa pun, tanpa mengoper props lewat setiap lapis di tengah. Bayangin papan nama dan saklar lampu Warung Bu Sri yang dipasang di pintu masuk. Siapa pun di dalam warung, di dapur atau di meja paling belakang, bisa membaca nama warungnya dan tahu lampunya sedang terang atau gelap, tanpa pesan itu dioper dari orang ke orang. App yang memasang papannya (provide), dan komponen mana pun di bawahnya tinggal membaca (inject).

🧪 Tebakan cepat: App punya const tema = ref("terang") dan menulis provide("tema", tema.value). Tombol Ganti tema di App mengubah tema jadi "gelap". MenuCard, dua lapis di bawah App, menampilkan "terang" atau "gelap"? Tebak dulu, jawabannya ada di bagian "Provide ref-nya, bukan isinya".

Props berlapis

Pohon komponen warung: App berisi WarungHeader dan HalamanMenu, dan HalamanMenu berisi MenuCard. Tema disimpan di App, tapi yang memakainya MenuCard. Dengan props, HalamanMenu harus menerima tema lalu mengopernya lagi, padahal dia sendiri gak memakainya:

<!-- App.vue -->
<HalamanMenu :tema="tema" />

<!-- HalamanMenu.vue: cuma jadi perantara -->
<script setup>
defineProps(["tema"])
</script>
<template>
  <MenuCard :tema="tema" />
</template>

Mengoper props lewat komponen yang gak memakainya disebut prop drilling. Dua lapis masih wajar. Lima lapis, setiap komponen di tengah ikut berubah tiap kali datanya bertambah.

provide dan inject

<!-- App.vue -->
<script setup>
import { ref, provide } from "vue"

const tema = ref("terang")
provide("namaWarung", "Warung Bu Sri")
provide("tema", tema)

function gantiTema() {
  tema.value = tema.value === "terang" ? "gelap" : "terang"
}
</script>

<template>
  <WarungHeader />
  <HalamanMenu />
  <button @click="gantiTema">Ganti tema</button>
</template>
<!-- MenuCard.vue, di dalam HalamanMenu -->
<script setup>
import { inject } from "vue"

const tema = inject("tema")
</script>

<template>
  <div class="kartu" :class="tema">Nasi Goreng · Rp15000 · tema {{ tema }}</div>
</template>

Kalau gak ada penyedia

inject("alamat") tanpa satu pun leluhur yang menyediakan "alamat" mengembalikan undefined. Template menampilkannya kosong, dan build development menulis [Vue warn]: injection "alamat" not found. Beri nilai bawaan di argumen kedua supaya aman:

const tema = inject("tema", "terang")   // "terang" kalau gak ada yang menyediakan

Provide ref-nya, bukan isinya

Yang di-provide App App mengganti tema jadi gelap MenuCard bisa menulis tema.value?
provide("tema", tema) MenuCard ikut gelap bisa, dan ikut mengubah tema App
provide("tema", tema.value) MenuCard tetap terang itu cuma teks, bukan ref
provide("tema", readonly(tema)) MenuCard ikut gelap gak bisa, ditolak

tema.value dibaca sekali waktu provide dipanggil, jadi yang dikirim cuma teks "terang". Teks gak bisa berubah, jadi MenuCard tetap terang walau App sudah gelap. Itu jawaban tebakannya. Kirim ref-nya, provide("tema", tema), maka setiap keturunan memegang kotak yang sama dengan App.

Tapi ref yang di-provide bisa ditulis siapa saja di bawahnya, dan data App berubah tanpa App tahu dari mana. Bungkus dengan readonly(tema): keturunan tetap ikut berubah, tapi tulisan mereka ditolak. Build production diam, build development menulis [Vue warn] Set operation on key "value" failed: target is readonly. Kalau keturunan memang boleh mengganti tema, provide juga fungsinya: provide("gantiTema", gantiTema). Perubahan datanya tetap terjadi di App.

Di editor latihan

Di editor, komponen utama jadi leluhurnya. Keturunannya ditulis sebagai objek dengan setup(), lalu dipasang dengan <component :is>:

provide("theme", "dark")

const Anak = {
  setup() {
    const theme = inject("theme")
    return { theme }
  },
  template: `<p id="theme">{{ theme }}</p>`,
}

---template---
<component :is="Anak" />

Hasilnya dark. inject ditulis di setup() milik Anak, bukan di script utama: di aplikasi sungguhan, inject di komponen yang sama dengan provide menghasilkan undefined, seperti di bagian sebelumnya, dan latihannya menolak bentuk itu. Latihan kedua sama bentuknya: provide("appName", "Hyper Sheets") dan provide("version", "2.0") di script utama, lalu inject keduanya di setup() milik Info, yang menampilkan {{ appName }} di <h3 id="app"> dan {{ version }} di <span id="ver">.

📌 Props, provide/inject, atau Pinia?

⚠️ Jebakan yang sering ketemu

TL;DR: provide("tema", tema) di leluhur dan inject("tema") di keturunan mengirim data lewat pohon komponen tanpa prop drilling. inject mencari ke atas sampai ketemu penyedia pertama; tanpa penyedia hasilnya undefined, jadi beri bawaan: inject("tema", "terang"). Provide ref-nya, bukan .value, supaya keturunan ikut berubah, dan bungkus dengan readonly kalau keturunan gak boleh menulisnya. inject gak membaca provide di komponennya sendiri; di editor latihan, keturunannya ditulis sebagai objek dengan setup() dan dipasang dengan <component :is>.

Yang akan kamu pelajari