I'm a fan of Vue which a try to use on some occasions. Anyway, there is something I always found not so handy with it: reactivity lies within $data. Well not always, as external data can be tracked by Vue, as in computed properties, in templates… But I found this way uncomfortable and not always consistent (see another question about it, here Reactivity on Variables Not Associated With Data, Computed, etc). So my decision now is use $data as the main source of reactivity and stop trying to find other ways.

However, reactivity within $data poses me a problem in what is a common case for me: many pieces of data here and there in other imported objects. This makes even more sense as I consider Vue as the View end not the business logic. Those imported objects are sometimes complex and within Vue components, I found no way to cherry pick pieces of information and kind of ask Vue to bind to them. The only way was to declare entire objects in the $data section which makes tracking very heavy: loads of setters/getters when only one would be enough in a simple component, for example.

So I designed a class called 'Reactor' whose instances role is to install getter/setters on any piece data of my wish in a complex object (or more than one). Those instances are imported into Vue components and then $watchers of Reactor instances have properties which can contain as many functions as I wish which are called when pieces of data are altered through the Reactor. To make things simple by default is filled with the same property name as the data it bounds to. This precisely those function which will update $data when external data change.

class Reactor {
  constructor() {
    this.$watchers = {};
  }
  addProperty(originalObject, keyString, aliasKeyString) {
    if(aliasKeyString === undefined) {
      aliasKeyString = keyString;
    }
    if(this[aliasKeyString] !== undefined || originalObject[keyString] === undefined) {
      const errorMessage = `Reactor: cannot add property '${aliasKeyString}'!`;
      console.error(errorMessage);
      throw errorMessage;
    }
    this.$watchers[aliasKeyString] = [];
    Object.defineProperty(this, aliasKeyString, {
      set(newValue) {
        const oldValue = originalObject[keyString];
        originalObject[keyString] = newValue;
        this.$watchers[aliasKeyString].forEach((f) => {
          if(typeof f === "function") {
            f(newValue, oldValue, aliasKeyString);
          }
        });
      },
      get() {
        return originalObject[keyString];
      },
   });
  }
}

An example can be seen in the codepen here: https://codepen.io/Djee/pen/gyVZMG So it's sort of an 'inverted' Vue which allows updating $data on external conditions.

This pattern also helped me resolve a case which was rather difficult before: have a double-bind on an input with a filter in-between which will set the input and its attached external value straight upon @change event only. This can be seen in the same codepen given above.

I was a little surprised to have found nothing taking this in charge in Vue itself. Did I miss something obvious? This is mainly the purpose of this somewhat long introduction. I had no time to check whether Vuex would solve this nicely.

Thanks for any comments as well.

0 Answers