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 menulisprovide("tema", tema.value). Tombol Ganti tema di App mengubahtemajadi"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>
provide(kunci, nilai): kuncinya teks seperti"tema", nilainya apa saja: teks, angka, ref, object, fungsi.inject(kunci): mencari ke atas, ke parent, lalu parent-nya parent, sampai ketemu komponen pertama yang menyediakan kunci itu.HalamanMenugak perlu tahu apa-apa.WarungHeadercukupconst nama = inject("namaWarung")untuk mendapat"Warung Bu Sri".- Kuncinya harus sama persis, termasuk huruf besar-kecil:
"Tema"dan"tema"dua kunci berbeda. injectgak melihat ke komponennya sendiri. KalauAppmenulisprovide("tema", ...)laluinject("tema")di script yang sama, hasilnyaundefined.
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?
- Props untuk satu atau dua lapis: jelas siapa memberi apa ke siapa.
- provide/inject untuk "suasana" yang dibaca banyak komponen di berbagai kedalaman: tema, nama warung, bahasa. Nilai yang berlaku untuk seluruh aplikasi bisa di-provide di
main.jslewatapp.provide("namaWarung", "Warung Bu Sri"). - Pinia untuk state aplikasi yang diubah dari banyak tempat, seperti keranjang. Dibahas di pelajaran Pinia.
⚠️ Jebakan yang sering ketemu
- Provide
tema.value. Yang terkirim salinan teks, jadi keturunannya gak pernah ikut berubah. Providetema. - inject di komponen yang sama dengan provide. Di aplikasi sungguhan hasilnya
undefined.providedi leluhur,injectdi keturunan. - Lupa nilai bawaan. Tanpa penyedia,
inject("tema")memberiundefined. Tulisinject("tema", "terang"). - Kunci atau nilai beda dari brief. Latihan membaca teks di halaman:
#themeharusdark,#appharusHyper Sheets,#verharus2.0, dan kodemu harus memanggilprovidedaninjectdengan kunci-kunci itu. Isi#appdan#verdiambil lewat{{ }}, bukan diketik langsung.
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>.