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.

Vue 3 Reactivity Transform met $ref en $computed macro's visualisatie

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.

Experimentele Status

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.

typescript
// 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)
}
typescript
// 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.

typescript
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:

useCounter.tstypescript
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:

typescript
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:

typescript
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:

vite.config.tstypescript
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:

bash
npm install -D @vue-macros/reactivity-transform
vite.config.tstypescript
import ReactivityTransform from '@vue-macros/reactivity-transform'

export default defineConfig({
  plugins: [
    vue(),
    ReactivityTransform()
  ]
})

Stapsgewijze Migratie

typescript
// 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>
typescript
// 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:

env.d.ts of shims-vue.d.tstypescript
/// <reference types="@vue-macros/reactivity-transform/macros-global" />

Generieke types werken identiek aan standaard refs:

typescript
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:

typescript
<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()

typescript
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 reactive

Vraag 3: Hoe werkt Vue's Dependency Tracking?

typescript
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 effect

Vue'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?

typescript
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 toegang

Beide 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:

typescript
// 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:

typescript
// 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.

Dagelijkse challenge

Zie jij de bug in Vue.js / Nuxt.js?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter 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

#vue
#vue 3
#reactivity
#composition api
#typescript
#sollicitatie

Delen

Gerelateerde artikelen