This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

What you will learn from this article:

  • Time to mixin.
  • Strategies for different situations:
    • Function stack mixin (data, provide)
    • Array overlay (Hook, watch)
    • Prototype chain stack mixed (Components, filters, directives)
    • Object overrides mixed (props, methods, computed, inject)
    • Replace overlay mixin (EL, Template, propData)

When developing with Vue, mixins are often found to be really useful. Mixins provide a very flexible way to distribute reusable functionality in Vue components. A mixin object can contain any component option. When a component uses a mixin object, all the options for the mixin object are “mixed” into the component’s own options.

Although easy to use, but has been stuck in the document will use, “know it but do not know why”, and before the group to share, some students also shared about mixin (mixin), so recently interested to see the implementation principle, found that still a bit around, is really a bit around (vegetable chicken a 😆). This article shares some of my explorations that I hope will help you. In general, it’s about exploring two questions:

  • When do you mixin?
  • What is the strategy for a mixin?

We look down with questions.

Pre-knowledge

1. How to use

  • With global
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
Vue.mixin({
  data() {
    return {
      a: 1}; }});new Vue({
  render: (h) = > h(App)
}).$mount("#app");
Copy the code

The test source

  • Local blending (component blending)
<template>
  <div class="hello">
    <h1>{{ a }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld".props: {
    msg: String,},mixins: [{data() {
        return {
          a: 2}; }},].data() {
    return {
      // a: 2,}; }};</script>
Copy the code

The test source

2. What are the basic global options?

The basic options are components, Directives, and filters. These are set in Vue. Options when the global API is initialized. So these are the first three that have global options.

When do you mixin?

Mixing is divided into two cases.

1. Global mixins mixed with underlying global options

Mixins should be performed before, not after, initializing the instance so that you can incorporate your custom options.

2. Custom options mixed with basic global options

Each component generates a VM (component instance) when it is initialized. Before creating a component instance, globally registered options are passed to each component in order to combine with global options and component options so that the component can access the global options. So the time is before the component instance is created.

For globally registered options, Vue surreptiviously merges a global option reference to each component. However, to ensure that global options are not contaminated, and it is impossible for each component to deeply clone a copy of global options, resulting in excessive overhead, different options are processed according to different options. Now let’s look at what is the strategy of blending into a merger?

What is the strategy for a mixin?

Before we do that, going back to the above two mixins, we find that both mixins end up calling mergeOptions. This method is the point of mixing.

// Merge the configuration object you just obtained from the class inheritance chain with the configuration object you wrote in your own code (from the first merge must be new Vue(options) the options
export function mergeOptions (
  parent: Object,
  child: Object, vm? : Component) :Object {...// Formalize the props, inject, directive, and other components
  // Verify that the developer's code conforms to the specification
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  if(! child._base) {Parent is merged with mixins first, and then with child
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  // Handle parent's key
  // First iterate over the merged parent key and store it in options
  // Initialize: parent is global
  for (key in parent) {
    mergeField(key)
  }
  // Handle the child's key
  // Select * from parent; // Select * from parent; // Select * from parent
  // Initialize: child is a component custom option
  for (key in child) {
    if(! hasOwn(parent, key)) {// Exclude the keys in parent that have already been handled
      mergeField(key)
    }
  }
  // Get the merge function for the type and merge the fields
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
Copy the code

The source code is very long, the key code is in the last function, this function is “get type merge function, merge field”, here the type might be: ‘data’, hook, ‘props’, ‘methods’, ‘inject’, ‘computed’, ‘provide’, and so on. Of course, if none of these exist, we will default to defaultStrat.

1. defaultStrat

const defaultStrat = function (parentVal: any, childVal: any) :any {
  return childVal === undefined
    ? parentVal
    : childVal
}
Copy the code

Component Options > Global Options

2. Data blending strategy

Data, as we develop it, is usually defined as a function, and (to a lesser extent) as an object. Let’s discuss blending strategies with functions as the main thread.

Here’s a brief explanation of why, in general, we use functions to define data: Components are reusable in Vue. Once a component is created, it can be used elsewhere, and the data in the components should not affect each other, no matter how many times they are reused. Data is a function. Each function has its own storage space. Each function creates its own execution context every time it is executed. Function is similar to creating a private data space for each component instance and having each component instance maintain its own data. However, simply writing the data in object form causes all component instances to share the same data, resulting in a result that all components will change.

function mergeData (to: Object.from:?Object) :Object {
  if (!from) return to
  let key, toVal, fromVal

  const keys = hasSymbol
    ? Reflect.ownKeys(from)
    : Object.keys(from)

  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') continue
    toVal = to[key]
    fromVal = from[key]
    // If this property exists, set it again
    if(! hasOwn(to, key)) { set(to, key, fromVal) }else if (
      // Merge objects if they have the same propertiestoVal ! == fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { mergeData(toVal, fromVal) } }return to
}

/** * Data */
export function mergeDataOrFn (parentVal: any, childVal: any, vm? : Component): ?Function {
  if(! vm) {if(! childVal) {return parentVal
    }
    if(! parentVal) {return childVal
    }
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this.this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this.this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

strats.data = function (parentVal: any, childVal: any, vm? : Component): ?Function {
  if(! vm) {if (childVal && typeofchildVal ! = ='function') {
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}
Copy the code

The source code is very long, and if you go through it line by line, it really hurts. Two data functions are merged into a single function return, and the data objects returned by the data function are also merged.

  • The functions are merged into one
  • The function returns a data merge, which is applied with higher priority

But note that the merged data is also prioritized. Let’s look at this with an example.

// Global configuration
Vue.mixin({
  data() {
    return {
      a: 1}; }});/ / child component
<template>
  <div class="child">
    <h1>{{ a }}</h1>
  </div>
</template>
<script>
export default {
  name: "Child".mixins: [{data() {
        return {
          a: 5}; },mixins: [{data() {
            return {
              a: 4}; },},],},],data() {
    return {
      a: 6}; }};</script>
Copy the code

The test source

In this example, we set four types of data Option functions:

  • The component’s own data function, A
  • Component mixin data function, B
  • Component mixin, in the mixin data function, C
  • Global mixin data function, DIn fact, no matter how many data functions are nested in mixins, it will only return a merge function, which will return a merged object, the merged data priority of the merged object,Mixin data > mixin data >… > Global mixin data.

3. Provide’s blending strategy

The provide blending policy is the same as the data blending policy. The underlying implementation is the mergeDataOrFn function.

4. Blending strategy of hook

// all hook functions in Vue
export const LIFECYCLE_HOOKS = [
  'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'.'serverPrefetch'
]

// Register callbacks for all hooks. Callbacks are mergeHooks
LIFECYCLE_HOOKS.forEach(hook= > {
  strats[hook] = mergeHook
})

// The function of mergeHook in conjunction with dedupeHooks is to store hook functions in an array
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function|?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}
Copy the code

Mixing in hook, compared with mixing in data, is easier, is to save all hook functions in an array, although the sequential execution.

/ / global
Vue.mixin({
  created() {
    console.log(1); }});/ / child component
<template>
  <div class="child">
  </div>
</template>
<script>
export default {
  name: "Child".mixins: [{created() {
        console.log(3);
      },
      mixins: [{created() {
            console.log(4); },},],},],created() {
    console.log(2); }};</script>
Copy the code

The test source }}}}}}}}}}}}}}}}}}}}}}}}}}

[global mixin hook, component mixin hook, component mixin hook],Copy the code

When we execute, we execute in this array order.

5. Watch’s blending strategy

The blending strategy of Watch is consistent with the blending strategy of Hook

[Global mixin watch,..., component mixin watch, component mixin Watch, component mixin Watch, component watch]Copy the code

This sequence is mixed into the merge watch, and the final execution is executed sequentially (note: although the mixin test is the same as the hook, the underlying implementation is different, so I won’t post the source code here).

6. Blending strategies for Component, Directives, and filters

Component, Directives, and filters are put together. The main reason is that they are combined and are set to vue. options when the global API is initialized.

// Transfer function
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object, vm? : Component, key: string) :Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    return extend(res, childVal)
  } else {
    return res
  }
}

// Bind callbacks for Component, Directives, and filters
ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

//
export function extend (to: Object, _from: ?Object) :Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}
Copy the code

Here is the most important thing is to “const res = Object. The create (parentVal | | null)” this one line of code, component, directives, the essence of strategy were mixed with filters.

The object.create () method creates a new Object, using an existing Object to provide the proto of the newly created Object.

Create objects. Create objects, create objects, create objects, create objects, create objects, create objects, create objects, create objects, create objects, create objects, create objects, and create objects.

<script>
    / / the global filter
    Vue.filter("g_filter".function (params) {})
    / / a mixin mixins
    var mixin_mixin_filter={
        filters: {mixin_mixin_filter(){}}}// mixin filter
    var mixins_filter={
        mixins:[mixin_mixin_filter],
        filters: {mixins_filter(){}}}/ / filter components
    var vue = new Vue({
        mixins:[mixins_filter],
        filters: {self_filter(){}}})console.log(vue.$options);
</script>
Copy the code

While implementing this example demo, an episode occurred. Codesandbox /s/vue-mixin /s/vue-mixin /s/vue-mixin /s/vue-mixin /s/vue-mixin…

7. Mixed policies for props, computed, methods, and Inject

The blending strategy of all four is the same, so put it all together. And their blending strategy is relatively simple.

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object, vm? : Component, key: string): ?Object {
  if(childVal && process.env.NODE_ENV ! = ='production') {
    assertObjectType(key, childVal, vm)
  }
  if(! parentVal)return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}
Copy the code

Simple object merge, with the same key value, with the higher priority overriding the lower priority. Component Object > Component mixin object > Component mixin-mixin object >… > Global mixin object.

Take methods as an example:

// Global configuration
Vue.mixin({
  methods: {
    test() {
      console.log(1); }}});/ / child component
<template>
  <div class="hello"></div>
</template>

<script>
export default {
  name: "HelloWorld".props: {
    msg: String,},mixins: [{methods: {
        test() {
          console.log(3); }},mixins: [{methods: {
            test() {
              console.log(4); },},},],},methods: {
    test() {
      console.log(2); }},created() {
    this.test(); }};</script>
Copy the code

The test source

8. El, Template, and propData blending strategies

This is the default way to do it, and it’s the kind of bottom-line approach that’s used when all of the blending strategies above don’t exist, such as El, Template, and propData. Their blending strategy is to cover the heavy weight of the weight of the small. Component > component mixin > Component mixin-mixin >… > Global mixin.

conclusion

This article takes you to explore the strategies of Vue Mixin. There are different blending strategies in different scenarios, Data, provide, hook function, watch, Component, directives, filters, props, computed, methods, inject, EL, template, and propData are involved. From the way of mixing, we can summarize it in five broad directions:

  • Function stack mixin (data, provide)
  • Array overlay (Hook, watch)
  • Prototype chain stack mixed (Components, filters, directives)
  • Object overrides mixed (props, methods, computed, inject)
  • Replace overlay mixin (EL, Template, propData)

“Know its know its why”, grasping the source code research for a long time, also read a lot of articles, summed up, hope to help you.

If you think it’s ok, please give it a thumbs up.

reference

  • Note.youdao.com/web/#/file/…
  • Developer.mozilla.org/zh-CN/docs/…
  • Mp.weixin.qq.com/s/uW8i0rkTM…
  • Mp.weixin.qq.com/s/IVqb9vkOd…
  • Ustbhuangyi. Making. IO/vue – analysi…
  • Cn.vuejs.org/v2/guide/mi…