0
0
Vueframework~15 mins

Custom refs with customRef in Vue - Deep Dive

Choose your learning style9 modes available
Overview - Custom refs with customRef
What is it?
In Vue, a ref is a way to create a reactive reference to a value or an object. Custom refs let you create your own reactive references with custom behavior, using the customRef API. This means you can control how Vue tracks and updates the value, adding features like debouncing or throttling. Custom refs help you build more flexible and efficient reactive data in your Vue apps.
Why it matters
Without custom refs, you can only use Vue's built-in reactivity, which might not fit all needs. For example, if you want to delay updates or control when changes trigger reactions, you would struggle. Custom refs solve this by letting you define exactly how and when Vue reacts to changes. This makes your app smoother and more performant, especially with complex or frequent updates.
Where it fits
Before learning custom refs, you should understand Vue's basic reactivity system, including refs and reactive objects. After mastering custom refs, you can explore advanced state management, custom watchers, and performance optimization techniques in Vue.
Mental Model
Core Idea
A custom ref is a reactive value whose tracking and updating behavior you fully control.
Think of it like...
Imagine a mailbox where you control when the mail carrier can deliver letters and when you check the mail, instead of it happening automatically every time a letter arrives.
┌───────────────┐
│ customRef()   │
│ ┌───────────┐ │
│ │ get()     │◄─── Vue tracks dependency here
│ └───────────┘ │
│ ┌───────────┐ │
│ │ set(value)│─── Vue triggers updates here
│ └───────────┘ │
└───────────────┘
Build-Up - 6 Steps
1
FoundationUnderstanding Vue refs basics
🤔
Concept: Learn what a ref is and how Vue tracks and reacts to its changes.
A ref in Vue holds a reactive value. When you read the ref's value, Vue tracks that dependency. When you change the value, Vue triggers updates to any code using it. Example: const count = ref(0); console.log(count.value); // tracks count.value = 1; // triggers update
Result
You can create reactive values that Vue tracks and updates automatically.
Understanding basic refs is essential because custom refs build on this reactive tracking and triggering mechanism.
2
FoundationWhat is customRef API?
🤔
Concept: customRef lets you create a ref with custom get and set behavior controlling tracking and triggering.
customRef takes a factory function that returns an object with get and set methods. Inside, you call track() to tell Vue to track dependencies and trigger() to notify Vue of changes. Example: const myRef = customRef((track, trigger) => { let value; return { get() { track(); return value; }, set(newValue) { value = newValue; trigger(); } }; });
Result
You get a reactive ref where you decide exactly when Vue tracks and triggers updates.
Knowing how to manually call track and trigger unlocks full control over Vue's reactivity.
3
IntermediateAdding debounce behavior with customRef
🤔Before reading on: do you think Vue's built-in refs can delay updates automatically? Commit to yes or no.
Concept: Use customRef to delay triggering updates until a pause in changes, called debouncing.
Debouncing means waiting for a pause before reacting. With customRef, you can set a timer in set() and only call trigger() after no changes for some time: function useDebouncedRef(initial, delay = 200) { let value = initial; let timeout; return customRef((track, trigger) => ({ get() { track(); return value; }, set(newValue) { value = newValue; clearTimeout(timeout); timeout = setTimeout(() => { trigger(); }, delay); } })); }
Result
Updates to the ref only trigger after the user stops changing the value for the delay period.
Understanding debouncing with customRef shows how to optimize performance by reducing unnecessary updates.
4
IntermediateControlling tracking and triggering manually
🤔Before reading on: do you think track() and trigger() must always be called in get() and set()? Commit to yes or no.
Concept: You can decide exactly when to track dependencies and when to trigger updates, even skipping some times.
In customRef, track() tells Vue to watch this ref for changes, and trigger() tells Vue to update dependents. You can call track() conditionally or not at all to control reactivity. For example, only track on certain conditions or trigger only if the value changed meaningfully.
Result
You can fine-tune reactivity to avoid unnecessary updates or tracking.
Knowing manual control prevents performance issues and lets you build smarter reactive data.
5
AdvancedUsing customRef for complex reactive logic
🤔Before reading on: do you think customRef can replace Vue watchers or computed properties? Commit to yes or no.
Concept: customRef can embed complex logic like validation, transformation, or side effects inside reactive references.
You can add logic inside get/set to validate input, transform values, or trigger side effects. For example, a customRef that only accepts numbers or automatically formats strings. This reduces boilerplate and keeps reactive logic encapsulated. Example: const numericRef = customRef((track, trigger) => { let value = 0; return { get() { track(); return value; }, set(newValue) { if (typeof newValue === 'number') { value = newValue; trigger(); } } }; });
Result
Reactive values with built-in rules and logic, improving code clarity and safety.
Understanding this shows how customRef can replace some watchers or computed properties with cleaner, encapsulated reactive logic.
6
ExpertInternal reactivity mechanism of customRef
🤔Before reading on: do you think customRef creates a new reactive system separate from Vue's core? Commit to yes or no.
Concept: customRef hooks into Vue's reactivity system by exposing manual track and trigger calls, integrating with Vue's dependency tracking and update queue.
Vue's reactivity system uses dependency tracking to know which effects to run when data changes. customRef provides manual access to this tracking via track() and trigger(). When get() calls track(), Vue records the current effect as dependent. When set() calls trigger(), Vue schedules those effects to re-run. This means customRef is not a separate system but a controlled interface into Vue's core reactivity engine.
Result
You understand that customRef is a powerful extension point, not a replacement, allowing precise control without breaking Vue's reactivity.
Knowing this prevents misuse and helps debug complex reactive behaviors by understanding the underlying system.
Under the Hood
customRef works by exposing two functions, track and trigger, that connect to Vue's internal dependency tracking system. When you read the ref's value, calling track() registers the current reactive effect as dependent on this ref. When you write to the ref, calling trigger() tells Vue to re-run those dependent effects. This manual control lets you decide exactly when Vue should track or update, enabling custom behaviors like debouncing or conditional updates.
Why designed this way?
Vue's reactivity system is designed to be flexible and efficient. customRef was introduced to give developers fine-grained control over reactivity without rewriting the whole system. It allows advanced use cases that built-in refs can't handle, like delaying updates or adding validation, while still integrating seamlessly with Vue's core. This design balances power and simplicity, avoiding complexity in the core while enabling extensibility.
┌───────────────┐
│ customRef API │
├───────────────┤
│ get()         │
│  └─► track() ─┼─► Vue records dependency
│ set(value)    │
│  └─► trigger()┼─► Vue schedules updates
└───────────────┘
       │
       ▼
┌─────────────────────┐
│ Vue Reactivity Core  │
│ - Dependency Graph  │
│ - Effect Scheduler  │
└─────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does customRef create a completely separate reactive system from Vue's core? Commit to yes or no.
Common Belief:customRef creates its own independent reactive system separate from Vue's built-in reactivity.
Tap to reveal reality
Reality:customRef hooks directly into Vue's core reactivity system by manually calling track and trigger functions provided by Vue.
Why it matters:Believing this leads to confusion and bugs because developers might expect customRef to behave differently or not integrate with Vue's reactivity, causing misuse.
Quick: Can you skip calling track() in get() without losing reactivity? Commit to yes or no.
Common Belief:You can omit track() in the get method and still have Vue track dependencies automatically.
Tap to reveal reality
Reality:If you don't call track() in get(), Vue won't know to track dependencies, so reactive updates won't happen.
Why it matters:Missing track() causes silent bugs where UI or computed properties don't update as expected, frustrating developers.
Quick: Does customRef automatically debounce or throttle updates? Commit to yes or no.
Common Belief:customRef automatically adds features like debouncing or throttling without extra code.
Tap to reveal reality
Reality:customRef only provides the mechanism; you must implement debouncing or throttling logic yourself inside get/set.
Why it matters:Assuming automatic behavior leads to unexpected immediate updates and performance issues if you rely on customRef without adding logic.
Quick: Can you use customRef to replace all watchers and computed properties? Commit to yes or no.
Common Belief:customRef can fully replace watchers and computed properties in all cases.
Tap to reveal reality
Reality:customRef is powerful but not a full replacement; watchers and computed properties have specialized roles and optimizations.
Why it matters:Trying to replace watchers/computed with customRef can lead to more complex and error-prone code.
Expert Zone
1
customRef's manual track and trigger calls allow you to optimize performance by batching or skipping updates selectively.
2
Using customRef inside libraries enables reusable reactive primitives with custom behavior, improving code modularity.
3
customRef can be combined with other Vue APIs like watchEffect or computed to build advanced reactive patterns.
When NOT to use
Avoid customRef when simple refs or reactive objects suffice, as customRef adds complexity. For straightforward reactive data, use built-in refs or reactive. Use watchers or computed properties for derived or side-effect logic instead of embedding all logic in customRef.
Production Patterns
In production, customRef is often used to implement debounced inputs, validated form fields, or reactive wrappers around non-Vue APIs. Libraries use customRef to expose reactive interfaces with custom update rules, improving UX and performance.
Connections
Debouncing in User Interfaces
customRef can implement debouncing, a common UI pattern to delay reactions to rapid input.
Understanding debouncing in UI helps grasp why customRef's manual trigger control improves performance by reducing unnecessary updates.
Observer Pattern (Software Design)
Vue's reactivity and customRef are implementations of the observer pattern where dependencies are notified on changes.
Knowing the observer pattern clarifies how track and trigger manage subscriptions and notifications in Vue's reactive system.
Event Handling in Real Life
customRef's control over when to notify changes is like deciding when to send alerts or notifications in daily life.
This connection shows how controlling event timing improves efficiency and user experience, just like in software.
Common Pitfalls
#1Forgetting to call track() in get method causes no reactivity.
Wrong approach:const myRef = customRef((track, trigger) => ({ get() { return value; // missing track() }, set(newValue) { value = newValue; trigger(); } }));
Correct approach:const myRef = customRef((track, trigger) => ({ get() { track(); return value; }, set(newValue) { value = newValue; trigger(); } }));
Root cause:Misunderstanding that Vue needs explicit track() calls to register dependencies.
#2Triggering updates immediately without control causes performance issues.
Wrong approach:const debouncedRef = customRef((track, trigger) => ({ get() { track(); return value; }, set(newValue) { value = newValue; trigger(); // triggers immediately every time } }));
Correct approach:const debouncedRef = customRef((track, trigger) => { let timeout; return { get() { track(); return value; }, set(newValue) { value = newValue; clearTimeout(timeout); timeout = setTimeout(() => { trigger(); }, 200); } }; });
Root cause:Not implementing debouncing logic inside set to delay trigger calls.
#3Trying to use customRef as a full replacement for computed properties.
Wrong approach:const computedLike = customRef((track, trigger) => { let value = 0; return { get() { track(); return value; }, set(newValue) { value = newValue; trigger(); } }; }); // used as computed replacement
Correct approach:const computedValue = computed(() => someReactiveValue.value * 2); // use computed for derived data
Root cause:Misunderstanding the distinct roles of computed properties and custom refs.
Key Takeaways
customRef lets you create reactive references with full control over when Vue tracks and triggers updates.
You must call track() in get() and trigger() in set() to integrate with Vue's reactivity system properly.
Custom refs enable advanced behaviors like debouncing, validation, and conditional updates inside reactive data.
Understanding customRef's internals helps avoid common bugs and optimize performance in Vue applications.
Use customRef when built-in refs or computed properties don't fit your reactive logic needs.