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.