Advanced Reactivity
By Syncfusion®, Inc. · Last updated
readonly(), shallowReadonly() and isReadonly(), shallowRef(), shallowReactive() and triggerRef(), toRef(), toRefs(), isRef() and unref(), toRaw() and markRaw(), a debounced ref with customRef(), and grouping effects with effectScope().
Beyond ref and reactive
ref()andreactive()cover most apps.Some jobs need finer control:
- stop others from changing state
- make huge data cheaper to watch
- keep a link when you pull out one property
- build your own kind of ref
- Vue has small helper functions for each job.
Simple lesson: these helpers fine-tune how Vue watches your data.
A Real-World Example
- Meera's search box looks up dishes as people type.
- Searching after every key wastes work.
- She wants to search only when typing pauses for 400 ms. This is called debouncing.
customRef()builds a ref that waits.
- What happened: type fast. The Console logs one search, after you pause.
- The
setwaited 400 ms before callingtrigger().
What You Will Learn
- How to make state read-only.
- Shallow refs and shallow reactive objects.
- How to trigger an update by hand.
- How to pull out properties without losing reactivity.
- How to check and unwrap refs.
- How to get the raw object, and how to stop Vue from watching one.
- How
customRef()works. - How to stop many watchers at once with
effectScope().
Read-Only State
readonly(obj)returns a copy that cannot be changed. It still updates when the original changes.- Trying to change it gives a warning and does nothing.
shallowReadonly(obj)protects only the top level.isReadonly(x)tells you if something is read-only.- Use it to share state with code that should only read it, as in Slots, Attributes and Provide/Inject.
- What happened: the change through
forDisplaywas blocked, with a warning. shallow.prices.dosaworked, because only the top level is protected.- The change to the original
menushowed throughforDisplay.
Shallow Reactivity
- A normal ref watches every level inside its value. For huge data, this costs time.
shallowRef(x)watches only.valueitself. Changing deep inside does nothing.- Replace the whole value to update:
list.value = [...]. - Or change it in place and call
triggerRef(list)to update by hand. shallowReactive(obj)watches only the top-level properties.
Use shallow refs for big data you replace as a whole, such as a 10 000-row list from a server. You will meet this again in Performance.
Simple lesson: shallow means "watch the top only". Replace the value, or call triggerRef().
Working with Refs
toRef(obj, "key")makes a ref linked to one property of a reactive object.toRefs(obj)does this for every property. Then destructuring is safe.isRef(x)checks ifxis a ref.unref(x)givesx.valuefor a ref, orxitself.toValue()also calls getters.
Simple lesson: toRefs() makes destructuring safe. toRef() links one property.
Composables often accept a reactive object and use toRefs() on it, so callers can destructure the result. You saw composables return refs in Composables, Directives and Plugins.
Raw Objects
toRaw(proxy)gives back the plain object behind a reactive proxy. Changes to it are not seen.markRaw(obj)tells Vue to never make that object reactive.- Use
markRawfor big objects from other libraries, such as a map or chart instance.
Changing data through toRaw() does not update the screen. Use it only to read, or to hand data to code outside Vue.
customRef()
customRef((track, trigger) => ({ get, set }))builds your own ref.- Call
track()inget, so Vue knows who reads it. - Call
trigger()inset, when you want the readers to update. - You decide when to trigger. That is how the debounced search box above works.
effectScope()
- Watchers and computed values are effects.
- In a component, Vue stops them on unmount.
- Outside a component, you must stop them yourself.
effectScope()collects every effect made insidescope.run(...).scope.stop()stops all of them at once.
- What happened: "More" added two log lines each time.
- After "Stop the scope",
platesstill changes, but nothing is logged.
Simple lesson: customRef() controls when a ref updates. effectScope() stops many effects together.
TL;DR
readonly()= a read-only view that still follows the original.shallowReadonly()= protects the top level only.shallowRef()= watches.valueonly. Replace it, or calltriggerRef().shallowReactive()= watches top-level properties only.toRef()/toRefs()= refs linked to reactive properties.isRef()/unref()= check a ref / read it or the plain value.toRaw()/markRaw()= the plain object behind a proxy / never reactive.customRef()= your own ref withtrack()andtrigger().effectScope()= collects effects.scope.stop()stops them all.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Changing a readonly view | It changes the original | Change the original | A warning, nothing changes |
push on a shallowRef array | The list updates | Replace the array, or triggerRef() | The screen stays the same |
Destructuring reactive() | Values stay linked | Use toRefs() | Values freeze |
Changing data through toRaw() | The screen updates | Change the reactive version | The screen is out of date |
Forgetting track() in customRef | Vue tracks it by itself | Call track() in get | Readers never update |
| Watchers outside components, never stopped | Vue stops them | Use effectScope() and stop() | Memory leaks and repeated work |
Check Yourself
Exercises
What to Remember
readonly()shares state that others may only read.- Shallow refs and objects watch only the top. Replace values or use
triggerRef(). toRefs()keeps destructured properties linked.markRaw()keeps big library objects out of reactivity.customRef()decides when a ref updates.effectScope()stops many effects at once.
Next: TypeScript with Vue