Skip to the solution
MediumFrontend12 min read

Why was Reactivity Transform ($ref, $computed) removed in Vue 3.4?

415 views

The problem

Question presented to candidate: "Some older Vue 3 code uses let count = $ref(0) with no .value. What was that feature, why did the Vue team remove it, and what happens if that code meets Vue 3.4 or later?"

What a strong answer should cover:

  • Reactivity Transform was an experimental, opt-in compile-time transform: $ref, $computed, $shallowRef and friends declared "reactive variables" that the compiler rewrote to ref() plus .value; $() destructured refs and $$() escaped back to the raw ref.
  • History: shipped experimentally in 3.2.0 (Aug 2021), dropped by the team in Feb 2023, deprecated in 3.3.0 (May 2023), removed in 3.4.0 (Dec 2023). Removal in a minor was allowed because the feature was experimental.
  • Reasons: without .value it is harder to see what is reactive and what triggers effects; it behaved differently inside and outside SFCs; refs were still needed at function boundaries ($$()), adding a concept; and it risked splitting the ecosystem into two dialects.
  • On 3.4+ the macros are just undeclared identifiers: the SFC still compiles, then fails at runtime with ReferenceError: $ref is not defined; type-checking reports Cannot find name '$ref'.
  • Options: migrate to plain ref/computed with .value, or keep the syntax via the Vue Macros plugin. Reactive props destructure is the one piece that survived, stable since 3.5.

Clarifying questions expected:

  • "Is this a codebase that is still on 3.3, or one that already upgraded and is broken?"
  • "Do you want the migration path, or the design reasoning?"

Code / implementation expected: Optional β€” the same counter written with $ref and with ref, and what the compiler does with each.

  • reactivity-transform
  • compiler
  • vue-3.4

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read Β· 10 sections

Read the code

VueThe modern equivalent: ref, computed and passing the ref to a composable
<script setup>
import { ref, computed, watch } from "vue";

// Before (Reactivity Transform, removed in 3.4):
//   let count = $ref(0)
//   const double = $computed(() => count * 2)
//   useLogger($$(count))   // $$() was needed to pass the ref itself
// After: plain refs. The .value tells the reader "this is reactive".
const count = ref(0);
const double = computed(() => count.value * 2);

// Passing the ref object into a composable needs no escape hatch: it is just `count`.
function useLogger(source, label) {
  watch(source, (now, before) => console.log(`${label}: ${before} -> ${now}`));
}
useLogger(count, "count");

function increment() {
  count.value++; // explicit: this line triggers effects
}
</script>

<template>
  <!-- templates unwrap top-level refs, so no .value here -->
  <button @click="increment">{{ count }} x 2 = {{ double }}</button>
</template>
VueLegacy Reactivity Transform syntax (compiles on 3.4+, then ReferenceError: $ref is not defined)
<script setup>
// Reactivity Transform syntax (Vue 3.2-3.3, experimental). Removed from core in 3.4.
let count = $ref(0);
const double = $computed(() => count * 2);

function increment() {
  count++;
}
</script>

<template>
  <button @click="increment">{{ count }} x 2 = {{ double }}</button>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 56 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

How do you create a custom directive in Vue?

Continue