Skip to the solution
MediumFrontend9 min read

What does readonly do in Vue's reactivity?

362 views

The problem

Question presented to candidate: "A store exposes readonly(state) to components. Explain what readonly actually does: what happens when a component writes to it, does it still update when the store changes, and how is it different from Object.freeze?"

What a strong answer should cover:

  • readonly() returns a Proxy over the same data that rejects set and delete; it is deep, so nested objects and arrays are read-only too.
  • It is still reactive for reads: effects reading the view re-run when the owner mutates the original.
  • In development a blocked write prints Set operation on key "x" failed: target is readonly.; in production the write is silently ignored and does not throw, even in strict mode.
  • shallowReadonly blocks only top-level writes; component props are shallow read-only.
  • readonly is not Object.freeze: freeze changes the object itself and stops everyone, readonly is a view that only stops the holder of the view.
  • Related checks: isReadonly, isReactive, isProxy, toRaw.

Clarifying questions expected:

  • "Is the goal to protect state from components, or to make data truly immutable?"
  • "Do you care about development warnings only, or runtime guarantees in production?"

Code / implementation expected: Optional β€” provide a readonly view to a child and show a blocked write plus an update through the owner.

  • readonly

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

VueProvide a readonly view: the child can read, not write
<script setup>
import { defineComponent, h, inject, isReadonly, provide, reactive, readonly } from "vue";

// The owner keeps the writable original...
const cart = reactive({ items: [{ name: "Pen", qty: 1 }] });
const addPen = () => cart.items[0].qty++;

// ...and hands out a read-only VIEW of the same data, plus the one sanctioned action.
provide("cart", { state: readonly(cart), addPen });

const CartBadge = defineComponent({
  setup() {
    const { state, addPen } = inject("cart");
    const hack = () => {
      state.items[0].qty = 99; // blocked: warns in dev, silently ignored in prod
      state.items.push({ name: "Free laptop", qty: 1 }); // blocked too: readonly is deep
    };
    return () =>
      h("div", [
        h("p", { id: "badge" }, `child sees: ${state.items.map((i) => i.name + " x" + i.qty).join(", ")}`),
        h("p", `isReadonly(state.items[0]): ${isReadonly(state.items[0])}`),
        h("button", { id: "hack", onClick: hack }, "child tries to write"),
        h("button", { id: "action", onClick: addPen }, "child calls addPen()"),
      ]);
  },
});
</script>

<template>
  <p id="owner">owner sees: {{ cart.items.map((i) => i.name + " x" + i.qty).join(", ") }}</p>
  <CartBadge />
</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 62 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 do toRef and toRefs do?

Continue