@tozd/vue-observer-utils

0.5.0 • Public • Published

@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, default promise: an Object to identify the promise by, this is used to remember and retrieve resolved value in when reactive context is rerun
  • forgetRejected, default false: if promise 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 returning undefined)
  • invalidateRejected, default false: if promise 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;
    },
  },
};

Package Sidebar

Install

npm i @tozd/vue-observer-utils

Weekly Downloads

8

Version

0.5.0

License

BSD-3-Clause

Unpacked Size

14.1 kB

Total Files

4

Last publish

Collaborators

  • mitar