Vue 3 Reactivity Transform in 2026: $ref, $computed en Sollicitatievragen
Uitgebreide analyse van Vue 3 Reactivity Transform met $ref, $computed en $$ macro's. Praktische codevoorbeelden, migratie van ref() naar $ref, en veelgestelde sollicitatievragen voor senior Vue-ontwikkelaars.

De Reactivity Transform in Vue 3 vertegenwoordigt een fundamentele verschuiving in hoe ontwikkelaars met reactieve state werken. Door compile-time macro's zoals $ref en $computed verdwijnt de noodzaak om continu .value te schrijven. Deze syntaxvereenvoudiging leidt tot schonere, beter leesbare code en komt steeds vaker aan bod in technische sollicitatiegesprekken.
Reactivity Transform was experimenteel in Vue 3.2-3.3 en is verwijderd in Vue 3.4. Vue Macros of de officiële @vue/reactivity-transform plugin maken het gebruik nog steeds mogelijk. In sollicitatiegesprekken is kennis van de historische context en huidige alternatieven belangrijk.
De Basis van $ref en $computed
De klassieke Composition API aanpak vereist expliciete .value toegang bij elk lezen of schrijven van een reactieve waarde. De Reactivity Transform elimineert deze boilerplate door compiler-macro's die tijdens de build worden omgezet naar standaard refs.
// Klassieke aanpak met ref()
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
console.log(doubled.value)
}// Met Reactivity Transform
let count = $ref(0)
const doubled = $computed(() => count * 2)
function increment() {
count++
console.log(doubled)
}De compiler transformeert $ref(0) naar ref(0) en voegt automatisch .value toe op alle plaatsen waar count wordt gebruikt. Het resultaat is identieke runtime code met een verbeterde ontwikkelaarservaring.
Diepgaand Begrip van de $$() Escape-Functie
De $$() macro lost een kritiek probleem op: hoe geef je een reactieve variabele door als Ref-object zonder dat de compiler het naar .value omzet? Deze situatie komt vaak voor bij composables en watchers.
import { watch } from 'vue'
let count = $ref(0)
// Fout: watch ontvangt de primitieve waarde, niet de Ref
watch(count, (newVal) => {
console.log(newVal)
})
// Correct: $$() voorkomt de transformatie
watch($$(count), (newVal) => {
console.log(newVal)
})Zonder $$() zou de compiler count transformeren naar count.value, waardoor watch een primitieve waarde krijgt in plaats van een reactieve referentie. Het resultaat zou een niet-werkende watcher zijn.
Gebruik met Composables
Composables die Refs verwachten of retourneren vereisen bijzondere aandacht:
import { ref, Ref } from 'vue'
export function useCounter(initial: Ref<number>) {
const count = ref(initial.value)
function increment() {
count.value++
}
return { count, increment }
}
// Gebruik in Component
let initialValue = $ref(10)
// $$() geeft het Ref-object door
const { count, increment } = useCounter($$(initialValue))$shallowRef en $customRef
Naast $ref bestaan er varianten voor specifieke gebruikssituaties. $shallowRef creëert een oppervlakkig reactieve referentie, ideaal voor grote objecten of externe datastructuren:
interface LargeDataset {
items: Array<{ id: number; data: unknown }>
metadata: Record<string, unknown>
}
// Shallow Ref: Alleen de referentie zelf is reactief
let dataset = $shallowRef<LargeDataset>({
items: [],
metadata: {}
})
// Trigger alleen bij hertoewijzing
dataset = { ...dataset, items: [...dataset.items, newItem] }
// Geen trigger bij diepe wijzigingen
dataset.items.push(newItem) // Vue reageert niet$customRef biedt volledige controle over tracking en triggering:
function useDebouncedRef<T>(value: T, delay = 300) {
let timeout: ReturnType<typeof setTimeout>
return $customRef<T>((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}))
}
let searchQuery = useDebouncedRef('')
// Updates worden 300ms vertraagd
searchQuery = 'vue reactivity'Migratie van ref() naar $ref
De migratie van bestaande codebases vereist een systematische aanpak. Het proces begint met de configuratie van de build tool:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true
}
})
]
})Voor huidige Vue 3.4+ projecten is de externe plugin nodig:
npm install -D @vue-macros/reactivity-transformimport ReactivityTransform from '@vue-macros/reactivity-transform'
export default defineConfig({
plugins: [
vue(),
ReactivityTransform()
]
})Stapsgewijze Migratie
// Voorheen: Standaard Composition API
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
const firstName = ref('')
const lastName = ref('')
const fullName = computed(() =>
`${firstName.value} ${lastName.value}`.trim()
)
watch(firstName, (newVal) => {
console.log('First name changed:', newVal)
})
function updateFirstName(value: string) {
firstName.value = value
}
</script>// Na: Met Reactivity Transform
<script setup lang="ts">
import { watch } from 'vue'
let firstName = $ref('')
let lastName = $ref('')
const fullName = $computed(() =>
`${firstName} ${lastName}`.trim()
)
watch($$(firstName), (newVal) => {
console.log('First name changed:', newVal)
})
function updateFirstName(value: string) {
firstName = value
}
</script>TypeScript Integratie en Typering
TypeScript-ondersteuning vereist globale type-declaraties. De macro's zijn beschikbaar tijdens compile-time en hebben passende definities nodig:
/// <reference types="@vue-macros/reactivity-transform/macros-global" />Generieke types werken identiek aan standaard refs:
interface User {
id: number
name: string
email: string
}
let currentUser = $ref<User | null>(null)
let users = $ref<User[]>([])
const activeUsers = $computed<User[]>(() =>
users.filter(u => u.email.includes('@company.com'))
)
async function fetchUsers() {
const response = await fetch('/api/users')
users = await response.json()
}Props Destructure met Reactivity Transform
Vue 3.5 introduceerde reactieve Props Destructuring, die harmonieus samenwerkt met de Reactivity Transform:
<script setup lang="ts">
interface Props {
initialCount?: number
title: string
disabled?: boolean
}
// Reactieve destructuring met standaardwaarden
const {
initialCount = 0,
title,
disabled = false
} = defineProps<Props>()
// initialCount, title, disabled zijn reactief
let internalCount = $ref(initialCount)
const displayTitle = $computed(() =>
disabled ? `${title} (uitgeschakeld)` : title
)
</script>Veelgestelde Sollicitatievragen over Vue Reactivity
Technische sollicitatiegesprekken voor Vue senior-posities focussen op diepgaand begrip van reactiviteit:
Vraag 1: Waarom is Reactivity Transform uit Vue Core verwijderd?
De beslissing was gebaseerd op meerdere factoren: de syntax-transformatie leidde tot impliciet gedrag dat verwarring veroorzaakte bij code reviews en debugging. Het onderscheid tussen reactieve variabelen en normale variabelen was visueel niet meer herkenbaar. Het Vue-team besloot dat expliciete .value toegang, ondanks de omslachtigheid, beter onderhoudbaar is.
Vraag 2: Leg het verschil uit tussen ref() en reactive()
import { ref, reactive } from 'vue'
// ref: Wrapper voor willekeurige waarden
const count = ref(0) // RefImpl { _value: 0 }
const user = ref({ name: '' }) // RefImpl { _value: { name: '' } }
// reactive: Proxy voor objecten
const state = reactive({ count: 0, user: { name: '' } })
// ref kan primitieve waarden bevatten
// reactive alleen objecten
const primitive = reactive(0) // Warning: value cannot be made reactiveVraag 3: Hoe werkt Vue's Dependency Tracking?
import { ref, effect } from 'vue'
const count = ref(0)
// effect registreert zichzelf als dependency
effect(() => {
// Bij het lezen van count.value wordt het effect als subscriber geregistreerd
console.log(count.value)
})
// Bij wijziging worden alle subscribers genotificeerd
count.value++ // Triggert het effectVue's reactiviteitssysteem gebruikt Proxy (voor reactive) en getters/setters (voor ref) om toegangen te volgen. Bij elke lezing wordt het huidige effect als dependency geregistreerd; bij elke schrijving worden alle dependencies genotificeerd.
Vraag 4: Wat is het verschil tussen $computed en computed?
import { computed } from 'vue'
// Standaard computed: Retourneert ComputedRef
const doubled = computed(() => count.value * 2)
console.log(doubled.value) // Toegang met .value
// $computed: Automatische .value oplossing
const tripled = $computed(() => count * 3)
console.log(tripled) // Directe toegangBeide produceren identieke runtime code. Het verschil ligt uitsluitend in de ontwikkelaarservaring en wordt tijdens de build geëlimineerd.
Klaar om je Vue.js / Nuxt.js gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Performance Overwegingen
De Reactivity Transform heeft geen runtime overhead, omdat de transformatie tijdens de build plaatsvindt. De gegenereerde output is identiek aan handgeschreven code:
// Source met $ref
let count = $ref(0)
count++
// Gecompileerde output
import { ref } from 'vue'
const count = ref(0)
count.value++Desondanks moeten performance best practices worden nageleefd:
// Vermijden: Nieuwe objecten in computed
const derived = $computed(() => {
return { ...largeObject, modified: true } // Nieuw object bij elke aanroep
})
// Beter: Gerichte berekeningen
const isModified = $computed(() => largeObject.modified === true)Conclusie en Aanbevelingen
De Vue 3 Reactivity Transform biedt een elegante oplossing voor de omslachtigheid van de Composition API. Ondanks de verwijdering uit de Vue core blijft de functionaliteit beschikbaar via externe plugins. Voor nieuwe projecten moet de keuze tussen standaard refs en Reactivity Transform bewust worden gemaakt.
In technische sollicitatiegesprekken demonstreert diepgaande kennis van beide benaderingen begrip van Vue's architectuurbeslissingen. Kennis van de redenen voor verwijdering uit de core, gecombineerd met praktische toepassing via Vue Macros, toont zowel historisch bewustzijn als actuele competentie.
Zie jij de bug in Vue.js / Nuxt.js?
Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Geschreven door
Anthony Fillion-MailletOprichter van SharpSkill
Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.
Bijgewerkt op 9 september 2026
Tags
Delen
Gerelateerde artikelen

Vue 3 Composition API: Volledige gids voor reactiviteit
De Vue 3 Composition API in de praktijk: ref, reactive, computed, watch en composables voor performante Vue-applicaties.

Geavanceerde Vue 3 Composables: Herbruikbare Patronen en Interviewvragen 2026
Een diepgaande gids over geavanceerde Vue 3 composable-patronen, van async data-ophaling tot formuliervalidatie en dependency injection. Inclusief veelgestelde interviewvragen over de Composition API in 2026.

Vue 3 met TypeScript in 2026: type-safe props, emits en composables
Type-safe Vue 3-componenten beheersen met TypeScript: generieke defineProps, defineEmits als tuple, getypeerde composables, defineModel en InjectionKey, plus sollicitatievragen.