Who Gets to Change the State? A Closer Look at readonly() in Vue
readonly() in Vue creates a reactive, read-only proxy of state, allowing it to be observed but preventing direct mutation, helping enforce controlled and predictable data flow.
At some point, managing state stops being about what it isâŚ
and starts being about who is allowed to change it.
Because the moment multiple parts of your application can access the same dataâ
youâre no longer just dealing with values.
Youâre dealing with responsibility.
When Sharing Becomes Dangerous
Vue makes it easy to share state.
You can create something once, and use it everywhere.
But that convenience comes with a quiet risk.
If everyone can change the stateâŚ
then no one really owns it.
And when something breaks, it becomes difficult to answer a simple question:
âWhere did this change come from?â
The Need for Boundaries
This is where the idea behind readonly() begins.
Not as a feature for convenienceâ
but as a way to introduce boundaries.
A way to say:
âThis data can be seen, but not touched.â
A Layer of Protection
When you wrap a reactive object with readonly():
const safeState = readonly(state)You donât remove reactivity.
You donât freeze the data.
You simply create a different way of accessing it.
One that allows readingâ
but blocks writing.
The original state is still alive.
Still mutable.
But only from places you control.
Two Views of the Same State
This creates an interesting separation.
Internally:
- the state is fully reactive
- it can be changed
Externally:
- the same state is visible
- but protected
Itâs the same dataâ
seen through two different lenses.
Where This Becomes Useful
The most natural place this shows up is in composables.
You define state inside a function.
You control how it changes.
And then you expose only what others need.
const state = reactive({ count: 0 })
export function useCounter() {
function increment() {
state.count++
}
return {
count: readonly(state),
increment
}
}Now, anyone using this can observe the count.
But they cannot change it directly.
If they want to modify itâ
they must go through the function you provided.
Why This Matters
Because it introduces discipline.
State changes become intentional.
Predictable.
Traceable.
Instead of being scattered across the codebase.
A Familiar Pattern, Reappearing
If this feels familiar, it should.
Itâs the same idea behind:
- props in Vue (which are readonly)
- state management libraries
- even APIs in general
You expose what should be used.
You protect what should not be changed freely.
Not About RestrictionâBut Clarity
At first, readonly() might feel limiting.
Why block something you might want to change?
But thatâs not its purpose.
Itâs not about restriction.
Itâs about clarity.
It defines:
- where changes are allowed
- and where they are not
And that clarity becomes more valuable as your application grows.
Still Reactive, Just Safer
One subtle but important detail:
readonly() does not stop updates.
If the original state changesâ
the readonly version reflects it.
Itâs not frozen.
Itâs just protected from direct mutation.
A Shift in Thinking
Without readonly(), state is just shared.
With readonly(), state becomes structured.
You begin to separate:
- reading from writing
- usage from control
And that separation leads to more predictable systems.
A Final Thought
In small components, this might not seem necessary.
But as complexity grows, uncontrolled mutation becomes one of the hardest problems to manage.
And sometimes, the best solution is not adding more logicâ
but adding clearer boundaries.
Because in the end, itâs not just about what your state is.
Itâs about who is allowed to change it.
And readonly() is one way to answer that question.