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() and reactive() 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 set waited 400 ms before calling trigger().

What You Will Learn

  1. How to make state read-only.
  2. Shallow refs and shallow reactive objects.
  3. How to trigger an update by hand.
  4. How to pull out properties without losing reactivity.
  5. How to check and unwrap refs.
  6. How to get the raw object, and how to stop Vue from watching one.
  7. How customRef() works.
  8. 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 forDisplay was blocked, with a warning.
  • shallow.prices.dosa worked, because only the top level is protected.
  • The change to the original menu showed through forDisplay.

Shallow Reactivity

  • A normal ref watches every level inside its value. For huge data, this costs time.
  • shallowRef(x) watches only .value itself. 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 if x is a ref.
  • unref(x) gives x.value for a ref, or x itself. 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 markRaw for 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() in get, so Vue knows who reads it.
  • Call trigger() in set, 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 inside scope.run(...).
  • scope.stop() stops all of them at once.
  • What happened: "More" added two log lines each time.
  • After "Stop the scope", plates still 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 .value only. Replace it, or call triggerRef().
  • 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 with track() and trigger().
  • effectScope() = collects effects. scope.stop() stops them all.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Changing a readonly viewIt changes the originalChange the originalA warning, nothing changes
push on a shallowRef arrayThe list updatesReplace the array, or triggerRef()The screen stays the same
Destructuring reactive()Values stay linkedUse toRefs()Values freeze
Changing data through toRaw()The screen updatesChange the reactive versionThe screen is out of date
Forgetting track() in customRefVue tracks it by itselfCall track() in getReaders never update
Watchers outside components, never stoppedVue stops themUse 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