Skip to the solution
HardFrontend9 min read

What do markRaw and toRaw do?

744 views

The problem

Question presented to candidate: "Explain what markRaw and toRaw do in Vue 3. How does each one work internally, and what can go wrong with object identity when proxies and raw objects mix?"

What a strong answer should cover:

  • Vue's reactive() returns a Proxy, a different object from the original; every nested object read through it is proxied too.
  • markRaw(obj) sets a hidden, non-enumerable __v_skip flag, so reactive(), ref() and nested access return the object unproxied, forever.
  • markRaw is shallow: objects nested inside a marked object can still become reactive if reached another way.
  • toRaw(proxy) returns the original object behind a reactive, readonly or shallow proxy (recursively); writes to it are not tracked.
  • Identity hazards: proxy !== raw, so Map/WeakMap lookups and === checks can fail; structuredClone(proxy) throws DataCloneError; classes with #private fields break behind a proxy.
  • Both are escape hatches: use them at boundaries with non-Vue code, not as everyday tools.

Clarifying questions expected:

  • "Is the object coming from a third-party library, or is it our own state?"
  • "Do we need the raw object for reading, or are we going to write to it?"

Code / implementation expected: Optional β€” a class instance that breaks when proxied and works with markRaw, plus toRaw used for a snapshot.

  • markraw
  • toraw

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

9 min read Β· 10 sections

Read the code

VuemarkRaw for a class with #private fields, toRaw for structuredClone
<script setup>
import { isReactive, markRaw, reactive, ref, toRaw } from "vue";

// Stand-in for a third-party class (a map, a chart, an SDK client).
// Real libraries often use #private fields, which a Proxy cannot reach.
class Engine {
  #rpm = 0;
  rev() {
    this.#rpm += 1000;
    return this.#rpm;
  }
}

const state = reactive({
  plain: new Engine(),          // becomes a Proxy: breaks #private access
  raw: markRaw(new Engine()),   // opted out: stays the real instance
  form: { title: "Draft", tags: ["vue"] },
});

const result = ref("");
function revBoth() {
  const lines = [];
  for (const key of ["plain", "raw"]) {
    try {
      lines.push(`${key}: rpm ${state[key].rev()} (isReactive ${isReactive(state[key])})`);
    } catch (e) {
      lines.push(`${key}: ${e.constructor.name}: ${e.message}`);
    }
  }
  result.value = lines.join(" | ");
}

const snapshot = ref("");
function save() {
  try {
    structuredClone(state.form); // a Proxy cannot be structured-cloned
  } catch (e) {
    snapshot.value = `proxy: ${e.name}. `;
  }
  // toRaw gives the original object behind the proxy, which clones fine.
  const copy = structuredClone(toRaw(state.form));
  snapshot.value += `toRaw copy: ${JSON.stringify(copy)}`;
}
</script>

<template>
  <button id="rev" @click="revBoth">rev both engines</button>
  <p id="result">{{ result }}</p>
  <button id="save" @click="save">snapshot the form</button>
  <p id="snapshot">{{ snapshot }}</p>
</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 81 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 nextHard

How does Vue's virtual DOM diff keyed lists?

Continue