Vue 3 Reactivity Transform 2026: $ref, $computed und Interview-Fragen

Umfassende Analyse des Vue 3 Reactivity Transform mit $ref, $computed und $$ Makros. Praktische Codebeispiele, Migration von ref() zu $ref, und häufige Vue-Interview-Fragen für Senior-Entwickler.

Vue 3 Reactivity Transform mit $ref und $computed Makros Visualisierung

Der Reactivity Transform in Vue 3 stellt einen fundamentalen Wandel dar, wie Entwickler mit reaktiven Zuständen arbeiten. Durch Compile-Time-Makros wie $ref und $computed entfällt die Notwendigkeit, ständig .value zu schreiben. Diese Syntax-Vereinfachung führt zu sauberem, lesbarem Code und wird in technischen Interviews immer häufiger abgefragt.

Experimenteller Status

Reactivity Transform war experimentell in Vue 3.2-3.3 und wurde in Vue 3.4 entfernt. Vue Macros oder das offizielle @vue/reactivity-transform Plugin ermöglichen die Nutzung weiterhin. In Interviews sollte der historische Kontext und die aktuelle Alternative bekannt sein.

Die Grundlagen von $ref und $computed

Der klassische Composition API Ansatz erfordert explizite .value Zugriffe bei jedem Lesen oder Schreiben eines reaktiven Wertes. Der Reactivity Transform eliminiert diese Boilerplate durch Compiler-Makros, die zur Build-Zeit in Standard-Refs umgewandelt werden.

typescript
// Klassischer Ansatz mit 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
// Mit Reactivity Transform
let count = $ref(0)
const doubled = $computed(() => count * 2)

function increment() {
  count++
  console.log(doubled)
}

Der Compiler transformiert $ref(0) zu ref(0) und fügt automatisch .value an allen Stellen ein, wo count verwendet wird. Das Ergebnis ist identischer Runtime-Code mit verbesserter Entwicklererfahrung.

Tiefes Verständnis der $$() Escape-Funktion

Das $$() Makro löst ein kritisches Problem: Wie übergibt man eine reaktive Variable als Ref-Objekt, ohne dass der Compiler sie zu .value auflöst? Diese Situation tritt häufig bei Composables und Watchers auf.

typescript
import { watch } from 'vue'

let count = $ref(0)

// Falsch: watch erhält den primitiven Wert, nicht das Ref
watch(count, (newVal) => {
  console.log(newVal)
})

// Korrekt: $$() verhindert die Transformation
watch($$(count), (newVal) => {
  console.log(newVal)
})

Ohne $$() würde der Compiler count zu count.value transformieren, was watch einen primitiven Wert statt einer reaktiven Referenz übergibt. Das Ergebnis wäre ein nicht funktionierender Watcher.

Verwendung mit Composables

Composables, die Refs erwarten oder zurückgeben, erfordern besondere Aufmerksamkeit:

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 }
}

// Verwendung in Component
let initialValue = $ref(10)

// $$() übergibt das Ref-Objekt
const { count, increment } = useCounter($$(initialValue))

$shallowRef und $customRef

Neben $ref existieren Varianten für spezifische Anwendungsfälle. $shallowRef erstellt eine oberflächlich reaktive Referenz, ideal für große Objekte oder externe Datenstrukturen:

typescript
interface LargeDataset {
  items: Array<{ id: number; data: unknown }>
  metadata: Record<string, unknown>
}

// Shallow Ref: Nur die Referenz selbst ist reaktiv
let dataset = $shallowRef<LargeDataset>({
  items: [],
  metadata: {}
})

// Trigger nur bei Neuzuweisung
dataset = { ...dataset, items: [...dataset.items, newItem] }

// Kein Trigger bei tiefen Änderungen
dataset.items.push(newItem) // Vue reagiert nicht

$customRef ermöglicht vollständige Kontrolle über Tracking und 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 werden um 300ms verzögert
searchQuery = 'vue reactivity'

Migration von ref() zu $ref

Die Migration existierender Codebases erfordert systematisches Vorgehen. Der Prozess beginnt mit der Konfiguration des Build-Tools:

vite.config.tstypescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      script: {
        defineModel: true,
        propsDestructure: true
      }
    })
  ]
})

Für aktuelle Vue 3.4+ Projekte wird das externe Plugin benötigt:

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

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

Schrittweise Migration

typescript
// Vorher: Standard 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
// Nachher: Mit 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 Integration und Typisierung

TypeScript-Unterstützung erfordert globale Typdeklarationen. Die Makros sind zur Compile-Zeit verfügbar und benötigen entsprechende Definitionen:

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

Generische Typen funktionieren identisch zu Standard-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 mit Reactivity Transform

Vue 3.5 führte reaktives Props Destructuring ein, das mit dem Reactivity Transform harmoniert:

typescript
<script setup lang="ts">
interface Props {
  initialCount?: number
  title: string
  disabled?: boolean
}

// Reaktives Destructuring mit Defaults
const { 
  initialCount = 0, 
  title,
  disabled = false 
} = defineProps<Props>()

// initialCount, title, disabled sind reaktiv
let internalCount = $ref(initialCount)

const displayTitle = $computed(() => 
  disabled ? `${title} (deaktiviert)` : title
)
</script>

Häufige Interview-Fragen zu Vue Reactivity

Technische Interviews für Vue-Senior-Positionen fokussieren sich auf tiefes Verständnis der Reaktivität:

Frage 1: Warum wurde Reactivity Transform aus Vue Core entfernt?

Die Entscheidung basierte auf mehreren Faktoren: Die Syntax-Transformation führte zu implizitem Verhalten, das bei Code-Reviews und Debugging Verwirrung stiftete. Die Unterscheidung zwischen reaktiven Variablen und normalen Variablen war nicht mehr visuell erkennbar. Das Vue-Team entschied, dass explizite .value Zugriffe trotz der Verbosität besser wartbar sind.

Frage 2: Erkläre den Unterschied zwischen ref() und reactive()

typescript
import { ref, reactive } from 'vue'

// ref: Wrapper für beliebige Werte
const count = ref(0)           // RefImpl { _value: 0 }
const user = ref({ name: '' }) // RefImpl { _value: { name: '' } }

// reactive: Proxy für Objekte
const state = reactive({ count: 0, user: { name: '' } })

// ref kann primitive Werte halten
// reactive nur Objekte
const primitive = reactive(0)  // Warning: value cannot be made reactive

Frage 3: Wie funktioniert Vue's Dependency Tracking?

typescript
import { ref, effect } from 'vue'

const count = ref(0)

// effect registriert sich als Dependency
effect(() => {
  // Beim Lesen von count.value wird der effect als subscriber registriert
  console.log(count.value)
})

// Bei Änderung werden alle subscriber benachrichtigt
count.value++ // Triggert den effect

Vue's Reaktivitätssystem nutzt Proxy (für reactive) und Getter/Setter (für ref) um Zugriffe zu tracken. Bei jedem Lesen wird der aktuelle Effect als Dependency registriert, bei jedem Schreiben werden alle Dependencies benachrichtigt.

Frage 4: Was ist der Unterschied zwischen $computed und computed?

typescript
import { computed } from 'vue'

// Standard computed: Gibt ComputedRef zurück
const doubled = computed(() => count.value * 2)
console.log(doubled.value) // Zugriff mit .value

// $computed: Automatische .value Auflösung
const tripled = $computed(() => count * 3)
console.log(tripled) // Direkter Zugriff

Beide produzieren identischen Runtime-Code. Der Unterschied liegt ausschließlich in der Entwicklererfahrung und wird zur Build-Zeit eliminiert.

Bereit für deine Vue.js / Nuxt.js-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Performance-Überlegungen

Der Reactivity Transform hat keinen Runtime-Overhead, da die Transformation zur Build-Zeit erfolgt. Die generierten Ausgaben sind identisch zu handgeschriebenem Code:

typescript
// Source mit $ref
let count = $ref(0)
count++

// Kompilierte Ausgabe
import { ref } from 'vue'
const count = ref(0)
count.value++

Dennoch sollten Performance-Best-Practices beachtet werden:

typescript
// Vermeiden: Neue Objekte in computed
const derived = $computed(() => {
  return { ...largeObject, modified: true } // Neues Objekt bei jedem Aufruf
})

// Besser: Gezielte Berechnungen
const isModified = $computed(() => largeObject.modified === true)

Fazit und Empfehlungen

Der Vue 3 Reactivity Transform bietet eine elegante Lösung für die Verbosität des Composition API. Trotz der Entfernung aus dem Vue Core bleibt die Funktionalität über externe Plugins verfügbar. Für neue Projekte sollte die Entscheidung zwischen Standard-Refs und Reactivity Transform bewusst getroffen werden.

In technischen Interviews demonstriert tiefes Wissen über beide Ansätze Verständnis für Vue's Architekturentscheidungen. Die Kenntnis der Gründe für die Entfernung aus dem Core sowie die praktische Anwendung mit Vue Macros zeigt sowohl historisches Bewusstsein als auch aktuelle Kompetenz.

Tägliche Challenge

Findest du den Bug in Vue.js / Nuxt.js?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 9. September 2026

Tags

#vue
#vue 3
#reactivity
#composition api
#typescript
#interview

Teilen

Verwandte Artikel