@tozd/vue-observer-utils
This NPM package exposes Vue observer internals (code which provides reactivity in Vue). Moreover, it extends the observer with additional API.
Installation
This is a NPM package. You can install it using NPM:
$ npm install @tozd/vue-observer-utils
It requires vue
peer dependency:
$ npm install vue
Usage
First, you have to register the package as a Vue plugin:
import Vue from 'vue';
import VueObserverUtils from '@tozd/vue-observer-utils';
Vue.use(VueObserverUtils);
After that, you can access:
For example, to know if you code is being executed in a reactive context you can now
check if Vue.util.Dep.target
is set. Moreover, Vue.util.Dep.target
gives you access to
the current reactive context, a Watcher
instance.
API
The following are additional API functions available.
Vue.util.nonreactive(f)
Calls function f
outside of a reactive context and returns its returned value.
Vue.util.onInvalidate(callback)
Registers callback
to run when current reactive context is next invalidated,
or runs it immediately if the reactive context is already invalidated.
The callback is run exactly once and not upon future invalidations unless
onInvalidate
is called again after the reactive context becomes valid again.
Registered callback
is run also when the reactive context is teared down.
Vue.util.onTeardown(callback)
Registers callback
to run when current reactive context is teared down,
or runs it immediately if the reactive context is already teared down.
The callback is run after any onInvalidate
callbacks.
vm.$wait(condition, effect)
Runs condition
function repeatedly inside a reactive context until it returns
a truthy value, after which effect
function is called once provided that value.
effect
is run exactly once unless $wait
is called again.
$wait
returns unwait
function which you can call to stop waiting.
If condition
is already satisfied when $wait
is called, then effect
function is called immediately even before $wait
returns. Moreover, $wait
returns null
in this case.
vm.$await(promise, [options])
Returns undefined
until the promise
is resolved, after that
it returns the resolved value of the promise
. It is reactive
so the value changes once the promise
gets resolved.
Important: $await
is meant to be used inside a reactive context
which can rerun multiple times. Make sure that you are not creating
a new (different) promise
for every rerun by mistake, but reuse
an existing one.
Available options
:
-
key
, defaultpromise
: anObject
to identify the promise by, this is used to remember and retrieve resolved value in when reactive context is rerun -
forgetRejected
, defaultfalse
: ifpromise
is rejected, forget that it has been run so that if reactive context is rerun, a new attempt at resolving will be made (instead of returningundefined
) -
invalidateRejected
, defaultfalse
: ifpromise
is rejected, invalidate the reactive context to force rerun
Examples
See @tozd/vue-format-time
for an example
how you can use Vue.util.onInvalidate
to implement an efficient time-based reactive
transformations.
Alert queue
const component = {
data() {
return {
queue: [],
};
},
created() {
this.unwait = null;
this.showNextAlert();
},
methods: {
showNextAlert() {
// Wait for the first next message to be available.
this.unwait = this.$wait(function () {
// Messages are enqueued from oldest to newest and "find" searches array elements in
// same order as well, so the first one which matches is also the oldest one.
return this.queue.find((element) => element.shown === false);
}, function (message) {
this.unwait = null;
message.shown = true;
alert(message.text);
this.showNextAlert();
});
},
addAlert(text) {
this.queue.push({
text,
shown: false,
})
},
},
};
For a more feature-full implementation of a notification queue,
see @tozd/vue-snackbar-queue
.
Reactive fetch
In this example we maintain a simple cache of promises for all
fetch
requests we made. We assume the mapping does not change:
once user's ID is resolved to an username that never changes.
In your case you might want to have cache invalidation as well.
Moreover, this cache can grow indefinitely even if some values
are not used anymore (for example, a particular user is not
displayed anymore so we do not need their username). You can use
onInvalidate
to track when a particular promise is not being used
anymore and if it is not used again in a later rerun, you could
remove if from the cache (maybe after some expiration time).
// This is shared between all component instances.
const idToUsername = new Map();
const component = {
data() {
return {
users: [
893,
991,
140,
],
};
},
computed: {
usernames() {
// We want to fetch username for each user in a reactive manner.
return this.users.map((user) => {
return this.getUsername(user);
});
},
},
methods: {
// This method checks if we have already fetched (or are in process
// of fetching) a username for this user. We use a simple Map in this
// example to cache fetches made.
getUsername(id) {
if (!idToUsername.has(id)) {
idToUsername.set(id, this.fetchUsername(id));
}
// It is important that we "$await" same promise so we use a Map
// as a cache for that. In a way we create a new cache if an argument
// to the "async" function changes.
return this.$await(idToUsername.get(id));
},
// A regular "async" function without any reactivity.
async fetchUsername(id) {
const response = await fetch(`https://example.com/api/user/${id}`, {
mode: 'cors',
credentials: 'omit',
});
if (response.status !== 200) {
throw new Error(`Fetch error: ${response.statusText}`);
}
const result = await response.json();
return result.username;
},
},
};