@tozd/vue-observer-utils
v0.5.0
Published
Exposing Vue observer internals + extensions
Downloads
66
Maintainers
Readme
@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-utilsIt requires vue peer dependency:
$ npm install vueUsage
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: anObjectto identify the promise by, this is used to remember and retrieve resolved value in when reactive context is rerunforgetRejected, defaultfalse: ifpromiseis 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: ifpromiseis 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;
},
},
};