Vue provides mixins apis that allow us to extract reusable functions from components and put them into mixins, and then introduce mixins into components to make components less bloated and improve code reusability.

How to understand mixins? Mixins can be understood as an array with one or more mixins in it. The essence of a mixin is a JS object, which can have all the properties of data, created, methods and other vUE instances. You can even nest mixins again within mixins, It’s amazing!

Here’s a simple chestnut:

<div id="app">
  <h1>{{ message }}</h1>
</div>
Copy the code
const myMixin = {
  data() {
    return {
      message: 'this is mixin message'}},created() {
    console.log('mixin created')}}const vm = new Vue({
  el: '#app'.mixins: [myMixin],

  data() {
    return {
      message: 'this is vue instance message'}},created() {
    console.log(this.message)
    // => Root Vue Instance
    console.log('vue instance created')
    // => created myMixin
    // => created Root Vue Instance}})Copy the code

When mixins and Vue Instance are merged, created hook functions are merged into an array. Mixins hooks are called first. When data and methods object keys and values conflict, components take precedence.

PS: If you are not familiar with the concept of mixins, you can go to the official vue documentation to see the basic concept and usage of Vue mixins.

Mixins implementation

So how do mixins work? When the vue at the time of instantiation, will call mergeOptions function for the consolidation of the options, function declaration in the vue/SRC/core/util/options. Js file.

export function mergeOptions(
  parent: Object,
  child: Object, vm? : Component) :Object {...// If child.extends recursively calls mergeOptions to copy the property
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // If there is a child. Mixins recursively call mergeOptions to copy the property
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  // declare options as an empty object to hold property copy results
  const options = {}
  let key
  // Iterate over the parent object and call mergeField to copy the properties
  for (key in parent) {
    mergeField(key)
  }
  // Iterate over the parent object and call mergeField to copy the properties
  for (key in child) {
    if(! hasOwn(parent, key)) { mergeField(key) } }// Property copy implementation method
  function mergeField(key) {
    // Pass assignment, defaultStrat by default
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
Copy the code

In order to keep the code concise, we have removed the unimportant mergeOptions function, so we can take a look at the rest.

const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
Copy the code

The extendsFrom variable is first declared to hold child.extends. If extendsFrom is true, mergeOptions is recursively called to copy the property and the merge result is saved to the parent variable.

if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
Copy the code

If child.mixins is true, loop through the mixins array and recursively call mergeOptions to copy the properties, still saving the merge result to the parent variable.

Next is the assignment of the parent and child attributes:

const options = {}
let key

for (key in parent) {
  mergeField(key)
}

for (key in child) {
  if(! hasOwn(parent, key)) { mergeField(key) } }Copy the code

Declare the options empty object, used to hold the result of the property copy and as the return value of the recursive call to mergeOptions.

The first call is for… In loops over parent, calling mergeField over and over again.

Then call for… In loops over the child, which is a bit different here, calling hasOwn to see if the parent has the key. If mergeField is not called again, this avoids repeating the call.

So what does this mergeField function do?

function mergeField(key) {
  // Pass assignment, defaultStrat by default
  const strat = strats[key] || defaultStrat
  options[key] = strat(parent[key], child[key], vm, key)
}
Copy the code

When the mergeField function receives a key, it first declares the strat variable and assigns strats[key] to strat if strats[key] is true.

const strats = config.optionMergeStrategies
...
optionMergeStrategies: Object.create(null),...Copy the code

Create (null) is used to create a new Object. Strats defaults to the empty Object generated by calling Object.create(null).

By the way, the vue also exposed the outward vue. Config. OptionMergeStrategies, can realize the custom option merge strategy.

If strats [key] is false, here will use | | do through the assignment, the defaultStrat default function assigned to strat.

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

DefaultStrat returns parentVal if childVal is undefined, or childVal if not. That’s why there’s a precedence like Component > mixins > extends.

The mergeField function finally assigns the result of the strat call to options[key].

The mergeOptions function finally merges all options, mixins, extends and returns the Options object before instantiating the VUE.

Merge of hook functions

Let’s look at how hook functions merge.

function mergeHook(
  parentVal: ?Array<Function>,
  childVal: ?Function|?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
      ? childVal
      : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook= > {
  strats[hook] = mergeHook
})
Copy the code

Loop through the LIFECYCLE_HOOKS array to call the mergeHook function over and over, assigning the return value to Strats [hook].

export const LIFECYCLE_HOOKS = [
  'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'
]
Copy the code

LIFECYCLE_HOOKS are all the hook function strings declared for vue.

The mergeHook function returns three nested ternary expressions.

return childVal
  ? parentVal
    ? parentVal.concat(childVal)
    : Array.isArray(childVal)
    ? childVal
    : [childVal]
  : parentVal
Copy the code

The first level, if childVal is true, returns the second level ternary, if false, returns parentVal.

If parentVal is true, return the combined array of parentVal and childVal. If parentVal is false, return the third ternary expression.

At the third level, return childVal if it is an array, otherwise return childVal wrapped as an array.

new Vue({
  created: [
    function() {
      console.log('Go, go! ')},function() {
      console.log('Duck, duck, duck! ')}]})// => flush!
// => duck duck duck!
Copy the code

The project practice

For those of you who use VUE, of course, use element-UI in your projects as well. For example, when using a Table Table, it is inevitable to declare tableData, total, and pageSize parameters required for Table and Pagination.

We can write repeated data and methods in a tableMixin.

export default {
  data() {
    return {
      total: 0.pageNo: 1.pageSize: 10.tableData: [].loading: false}},created() {
    this.searchData()
  },

  methods: {
    // predeclare to prevent error
    searchData() {},

    handleSizeChange(size) {
      this.pageSize = size
      this.searchData()
    },

    handleCurrentChange(page) {
      this.pageNo = page
      this.searchData()
    },

    handleSearchData() {
      this.pageNo = 1
      this.searchData()
    }
  }
}
Copy the code

We can introduce it directly when we need to use it:

import tableMixin from './tableMixin'

export default{...mixins: [tableMixin],
  methods: {
    searchData(){... }}}Copy the code

We redeclare the searchData method within the component. For keys in the form of methods objects, if the key is the same, the key in the component overrides the key in tableMixin.

We can also nest mixins within mixins and declare axiosMixin:

import tableMixin from './tableMixin'

export default {
  mixins: [tableMixin],

  methods: {
    handleFetch(url) {
      const { pageNo, pageSize } = this
      this.loading = true

      this.axios({
        method: 'post',
        url,
        data: {
          ...this.params,
          pageNo,
          pageSize
        }
      })
        .then(({ data = [] }) = > {
          this.tableData = data
          this.loading = false
        })
        .catch(error= > {
          this.loading = false})}}}Copy the code

The introduction of axiosMixin:

import axiosMixin from './axiosMixin'

export default{...mixins: [axiosMixin],
  created() {
    this.handleFetch('/user/12345')}}Copy the code

In AXIos, we can pre-process subsequent calls to AXIos’ success and error, saving a lot of code.

extend

Extend, by the way, is similar to mixins in that only an options object is passed, and mixins have a higher priority and overwrite keys with the same name.

// If child.extends recursively calls mergeOptions to copy the property
const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
// If there is a child. Mixins recursively call mergeOptions to copy the property
if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
Copy the code

In mergeOptions, the extends property is copied first, then the mixin, and the Child’s key is taken first when mergeField is called.

Extends takes precedence, although its namesake key is overridden by mixins.

conclusion

Notice the precedence of mixins in vUE, Component > mixins > extends.

We will temporarily call mixins component modularization, flexible use of component modularization, can be the component of the repeated code extraction, code reuse, but also make our code clearer, efficiency is also greatly improved.

Of course, mixins have even more magical operations to explore.