Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vue.js: scoping custom option merge strategies instead going global

Tags:

vue.js

Good Day Fellows,

Quick summary: how can I use custom option merge strategies on an individual basis per component and not globaly?

My problem: I am extending my components via Mixins and it is working great so far. However, while it is working great with the likes of component methods, I often need to override some lifecycle hooks, like mounted, created, etc. The catch is, Vue - by default - queues them up in an array and calls them after another. This is of course defined by Vues default merge strategies.

However in some specific cases I do need to override the hook and not have it stack. I know I can customize Vue.config.optionMergeStrategies to my liking, but I want the mergeStrategy customized on a per component basis and not applying it globably.

My naive approach on paper was to create a higher function which stores the original hooks, applies my custom strategy, calls my component body and after that restores Vues original hooks.

Let's say like this

export default function executeWithCustomMerge(fn) {
    const orig = deep copy Vue.config.optionMergeStrategies;


    Vue.config.optionMergeStrategies.mounted = (parent, child) => [child];

    fn();
    Vue.config.optionMergeStrategies = deep copy orig;

}

And here's it in action

executeWithCustomMerge(() => {
    Vue.component('new-comp', {
         mixins: [Vue.component("old-comp")],
    },
    mounted() {
       //i want to override my parent thus I am using a custom merge strategy
    });
});

Now, this is not going to work out because restoring the original hook strategies still apply on a global and will be reseted before most hooks on my component are being called.

I wonder what do I need to do to scope my merge strategy to a component.

like image 983
Martin Sebastian Avatar asked Oct 25 '18 11:10

Martin Sebastian


1 Answers

I had a look at optionMergeStrategies in more detail and found this interesting quote from the docs (emphasis mine):

The merge strategy receives the value of that option defined on the parent and child instances as the first and second arguments, respectively. The context Vue instance is passed as the third argument.

So I thought it would be straightforward to implement a custom merging strategy that inspects the Vue instance and looks at its properties to decide which strategy to use. Something like this:

const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = function strategy(toVal, fromVal, vm) {
  if (vm.overrideCreated) {
    // If the "overrideCreated" prop is set on the component, discard the mixin's created()
    return [vm.created];
  }
  return mergeCreatedStrategy(toVal, fromVal, vm);
};

It turns out though that the 3rd argument (vm) is not set when the strategy function is called for components. It's a new bug! See https://github.com/vuejs/vue/issues/9623

So I found another way to inform the merge strategy on what it should do. Since JavaScript functions are first-class objects, they can have properties and methods just like any other object. Therefore, we can set a component's function to override its parents by setting a property on it and looking for its value in the merge strategy like so:

Vue.mixin({
  created() {
    this.messages.push('global mixin hook called');
  }
});

const mixin = {
  created() {
    this.messages.push('mixin hook called');
  },
};

const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = function strategy(toVal, fromVal) {
  if (fromVal.overrideOthers) {
    // Selectively override hooks injected from mixins
    return [fromVal];
  }
  return mergeCreatedStrategy(toVal, fromVal);
};

const app = {
  el: '#app',
  mixins: [mixin],
  data: { messages: [] },
  created() {
    this.messages.push('component hook called');
  },
};
// Comment or change this line to control whether the mixin created hook is applied
app.created.overrideOthers = true;

new Vue(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <h1>Messages from hooks</h1>
  <p v-for="message in messages">{{ message }}</p>
</div>
like image 169
bernie Avatar answered Nov 11 '22 04:11

bernie