Cached Getters - complete

by Michael Gallagher

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.7/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://unpkg.com/vue-pursue@0.1.3/dist/vue-pursue.js"></script>
<script type="text/x-template" id="comp">
  <div>{{ task.name }}<br/>
    <pre>{{ Date.now() }}</pre>
  </div>
</script>


<div id="app" />
<div class="output" />

JavaScript

let store;
let hits = 0;
let misses = 0;

// recreation of a computed property
const computed = (vm, func) => {
  vm.$watch(
    func,
    null, // callback not used for lazy
    { lazy: true }, // lazy means we can just flag as dirty
  );
  // eslint-disable-next-line no-underscore-dangle
  const watcher = vm._watchers[vm._watchers.length - 1];

  // add an accessor
  watcher.lazyValue = () => {
    if (watcher.dirty) {
    	misses += 1;
      watcher.evaluate();
    } else {
      hits += 1;
    }
    console.log(`H:${hits} M:${misses}`);
    watcher.depend(); // means any calling computed will have transitive deps
    return watcher.value;
  };
  return watcher;
};

// Cache Wrapper for Method-style Getter
const methodStyleCache = (getVM, getter) => {
  let watchers = {};
  return (...args) => {
    // Remove old watchers if the getter is rebuilt
    Object.values(watchers).forEach((w) => w.teardown());
    watchers = {};
    const innerMethod = getter.call(null, ...args);

    return (id) => {
      let watcher = watchers[id];
      if (!watcher) {
        watcher = computed(getVM(), () => innerMethod(id));
        watchers[id] = watcher;
      }
      return watcher.lazyValue();
    };
  };
};

// Cacher that can be attached as a plugin to get a store reference
const methodGetterCacher = () => {
  let store;
  const getVM = () => store._vm;
  return {
    plugin: (st) => { store = st; },
    cache: (getter) => methodStyleCache(getVM, getter),
  };
}

const { plugin, cache } = methodGetterCacher();

store = new Vuex.Store({
  plugins: [plugin],
  state: {
    tasks: {
      1: {
        id: 1,
        name: 'Setup'
      },
      2: {
        id: 2,
        name: 'Impl'
      },
      3: {
        id: 3,
        name: 'Testing'
      },
    },
  },
  getters: {
    mappedTask: cache((state) => (id) => ({
      name: state.tasks[id].name.toUpperCase(),
    })),
  },
  mutations: {
    changeOne: (state) => {
      state.tasks[1].name = 'Preparation';
    },
 ...