Skip to the solution
MediumFrontend9 min read

When should you use readonly, markRaw, and toRaw in Vue reactivity?

120 views

The problem

Question presented to candidate: "You are reviewing a Vue 3 codebase that uses readonly, markRaw and toRaw in various places. For each one, tell me a situation where it is the right tool, and a situation where using it would be a mistake."

What a strong answer should cover:

  • readonly: expose state from a composable, store or provide so consumers can read and react but must call actions to change it. Not for immutability, not in hot paths.
  • markRaw: third-party instances (map, chart, editor, SDK clients), classes with #private fields, component definitions held in state, large static config. Apply it at creation.
  • toRaw: hand plain data to code outside Vue (structuredClone, postMessage, IndexedDB, libraries doing identity checks), or read large data without proxy overhead in an event handler.
  • Mistakes: writing through toRaw (no re-render), markRaw on data the UI edits, treating readonly as a security boundary.
  • Related tools: shallowRef as an alternative to markRaw for a slot that is replaced as a whole.
  • Knowing the dev warning Vue prints when a component is made reactive.

Clarifying questions expected:

  • "Who owns this state, and who is allowed to change it?"
  • "Does this object come from a library that does its own change tracking?"

Code / implementation expected: Optional β€” a small screen using all three, or a review of where each belongs.

  • readonly
  • 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

VueOne screen, three escape hatches: markRaw widgets, readonly store, toRaw snapshot
<script setup>
import { computed, defineComponent, h, markRaw, reactive, readonly, ref, shallowRef, toRaw } from "vue";

// --- 1. markRaw: things Vue should hold but never track ---------------------
const Chart = defineComponent({ props: { title: String }, setup: (p) => () => h("em", "chart: " + p.title) });
const Table = defineComponent({ props: { title: String }, setup: (p) => () => h("em", "table: " + p.title) });

// Widgets carry a component AND data. The component must not become a proxy.
const widgets = ref([
  { id: 1, title: "Sales", view: markRaw(Chart) },
  { id: 2, title: "Orders", view: markRaw(Table) },
]);

// --- 2. readonly: a store that owns its writes ------------------------------
function createFilters() {
  const state = reactive({ query: "", onlyOpen: false });
  return {
    state: readonly(state), // consumers can read and watch, not write
    setQuery: (q) => (state.query = q.trim().toLowerCase()), // one place that validates
    toggleOpen: () => (state.onlyOpen = !state.onlyOpen),
    // --- 3. toRaw: hand a plain copy to code outside Vue ----------------------
    serialize: () => structuredClone(toRaw(state)),
  };
}
const filters = createFilters();
const visible = computed(() =>
  widgets.value.filter((w) => w.title.toLowerCase().includes(filters.state.query))
);
const saved = ref("");
</script>

<template>
  <input id="q" placeholder="filter widgets" @input="filters.setQuery($event.target.value)" />
  <button id="open" @click="filters.toggleOpen">only open: {{ filters.state.onlyOpen }}</button>
  <button id="save" @click="saved = JSON.stringify(filters.serialize())">save filters</button>
  <p id="saved">{{ saved }}</p>
  <ul>
    <li v-for="w in visible" :key="w.id"><component :is="w.view" :title="w.title" /></li>
  </ul>
</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 68 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

What are Vue lifecycle hooks?

Continue