Vue uses mixins to optimize components

Vue uses mixins to optimize components

Vue provides the mixins API, which allows us to extract reusable functions from components, put them into mixins, and then introduce mixins into components, which can make components less bloated and improve the reusability of the code.

How to understand mixins? We can think of mixins as an array, which contains one or more mixins. The essence of a mixin is a JS object, which can have all the properties of a Vue instance, such as data, created, methods, etc., and you can even nest mixins in mixins. It's amazing!

Here is a simple example:

<div id="app">
  <h1>{{ message }}</h1>
</div>
const myMixin = {
  data() {
    return {
      message: 'this is a 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
  }
})

When mixins are merged with Vue Instance, hook functions such as created will be merged into an array, and the hooks of mixins will be called first. When the key values ​​of data and methods objects conflict, components will be given priority.

PS: If you are still not clear about the concept of mixins, you can go to the Vue official documentation to see the basic concepts and usage of Vue mixins.

Mixins implementation

So how are mixins implemented? When vue is instantiated, it calls the mergeOptions function to merge options. The function is declared in the vue/src/core/util/options.js file.

export function mergeOptions(
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  ...
  // If there is child.extends, recursively call mergeOptions to implement attribute copy const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // If there is child.mixins, recursively call mergeOptions to copy properties if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  // Declare options empty object to save the attribute copy result const options = {}
  let key
  // Traverse the parent object and call mergeField to copy the attributes for (key in parent) {
    mergeField(key)
  }
  // Traverse parent objects and call mergeField to copy attributes for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  // Attribute copy implementation method function mergeField(key) {
    // Penetration assignment, default is defaultStrat
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

To keep the code concise, the unimportant code of the mergeOptions function has been deleted. Let's take a look at the remaining parts.

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

First, declare that the extendsFrom variable saves child.extends. If extendsFrom is true, recursively call mergeOptions to copy the properties and save the merge result 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)
  }
}

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

Next is the property assignment of parent and child:

const options = {}
let key

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

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

Declare an empty options object to save the result of attribute copying and also as the return value of recursively calling mergeOptions.

Here, for...in is first called to loop parent, and the mergeField function is continuously called in the loop.

Then call for...in to loop over the child. There is a little difference here. It will call hasOwn to determine whether the key exists on the parent. If not, it will call the mergeField function to avoid repeated calls.

So what exactly is this mergeField function used for?

function mergeField(key) {
  // Penetration assignment, default is defaultStrat
  const strat = strats[key] || defaultStrat
  options[key] = strat(parent[key], child[key], vm, key)
}

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

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

strats is actually Object.create(null). Object.create is used to create a new object. By default, strats is an empty object generated by calling Object.create(null).

By the way, vue also exposes Vue.config.optionMergeStrategies, which can implement custom option merging strategies.

If strats[key] is false, || will be used here to perform a penetrating assignment and assign the defaultStrat default function to strat.

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

The defaultStrat function returns a ternary expression. If childVal is undefined, it returns parentVal, otherwise it returns childVal. ChildVal takes precedence here, which is why there is a priority of component > mixins > extends.

The mergeField function will finally assign the result of calling strat to options[key].

The mergeOptions function will finally merge all options, mixins, and extends, return the options object, and then instantiate Vue.

Hook function merging

Let's take a look at how the hook functions are merged.

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
})

Loop through the LIFECYCLE_HOOKS array, continuously call the mergeHook function, and assign the return value to strats[hook].

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

LIFECYCLE_HOOKS is the declared string of all hook functions of vue.

The mergeHook function returns a ternary expression nested three levels deep.

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

At the first level, if childVal is true, it returns the second level ternary expression, if it is false, it returns parentVal.

The second level, if parentVal is true, returns the array of parentVal and childVal merged, if parentVal is false, returns the third level ternary expression.

The third layer, if childVal is an array, returns childVal, otherwise childVal is packaged into an array and returned.

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

Project Practice

Friends who use vue, of course, also need to use element-ui in their projects. For example, when using a Table, you have to declare tableData, total, pageSize and other parameters required by the 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: {
    //Pre-declare to prevent error searchData() {},

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

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

    handleSearchData() {
      this.pageNo = 1
      this.searchData()
    }
  }
}

When we need to use it, we can directly import it:

import tableMixin from './tableMixin'

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

We will redeclare the searchData method in the component. For keys in the form of methods objects, if the keys are the same, the key in the component will overwrite the key in tableMixin.

Of course, we can also nest mixins in mixins, declaring 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
        })
    }
  }
}

Import axiosMixin:

import axiosMixin from './axiosMixin'

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

In axios, we can pre-process the subsequent calls of axios's success and error, which saves us a lot of code.

extend

By the way, extend is similar to mixins. It can only pass in one options object, and mixins have a higher priority and will overwrite the key value of the same name in extend.

// If there is child.extends, recursively call mergeOptions to implement attribute copy const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
// If there is child.mixins, recursively call mergeOptions to copy properties if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
// If there is child.extends, recursively call mergeOptions to implement attribute copy const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
// If there is child.mixins, recursively call mergeOptions to copy properties if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}

In the mergeOptions function, the properties of extends will be copied first, and then the mixin will be copied. When the mergeField function is called, the child key will be taken first.

Although the same-name key of extends will be overwritten by mixins, extends takes precedence.

Summarize

Note the priority of mixins in vue, component > mixins > extends.

We will temporarily call mixins component modularization. Flexible use of component modularization can extract duplicate code within the component, achieve code reuse, make our code clearer, and greatly improve efficiency.

Of course, mixins have more magical operations waiting for you to explore.

The above is the details of how Vue uses mixins to optimize components. For more information about how Vue uses mixins to optimize components, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • How to use mixins in Vue
  • How to use Mixins and plugins in Vue
  • How to use Vue Mixins
  • Vue uses mixin to distribute reusable functions of components
  • In-depth analysis of the differences and usage scenarios of mixin and extend in Vue
  • Detailed explanation of the use of Vue mixin

<<:  How to use Samba to build a shared file service on a Linux server

>>:  Detailed tutorial on installing ElasticSearch 6.4.1 on CentOS7

Recommend

Basic operations on invisible columns in MySQL 8.0

Table of contents 01 Create invisible columns 02 ...

MySQL 8.0.15 winx64 installation and configuration method graphic tutorial

This article shares the installation and configur...

How to use the concat function in mysql

As shown below: //Query the year and month of the...

HTML Tutorial: Collection of commonly used HTML tags (5)

Related articles: Beginners learn some HTML tags ...

jQuery implements percentage scoring progress bar

This article shares the specific code of jquery t...

How to optimize a website to increase access speed update

Recently, the company has begun to evaluate all s...

Centos7.5 installs mysql5.7.24 binary package deployment

1. Environmental preparation: Operating system: C...

How to analyze SQL execution plan in MySQL through EXPLAIN

Preface In MySQL, we can use the EXPLAIN command ...

In-depth analysis of MySQL deadlock issues

Preface If our business is at a very early stage ...

CocosCreator ScrollView optimization series: frame loading

Table of contents 1. Introduction 2. Analysis of ...

Summary of common MySQL table design errors

Table of contents Mistake 1: Too many columns of ...

XHTML Getting Started Tutorial: Using the Frame Tag

<br />The frame structure allows several web...

How to enable Flash in Windows Server 2016

I recently deployed and tested VMware Horizon, an...

Vue uses dynamic components to achieve TAB switching effect

Table of contents Problem Description What is Vue...

Comprehensive understanding of HTML Form elements

As shown below: XML/HTML CodeCopy content to clip...