preface

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 mixin, all mixin options are “blended” into the component’s own options.

In daily development, we often encounter the same or similar code in different components, the functions of these codes are relatively independent. At this time, we can use the mixin function of Vue to propose the same or similar code, which facilitates the reuse of the code and makes the maintenance easier.

This article will show you how to use Vue mixins, and will give you a deeper understanding of mixins by implementing an image lazy loading feature.

Basic usage method

Local component mixing

We create a mixin folder to hold the mixin objects we want to mix and import them in components that need to use mixin objects.

src/mixin/demo.js

export default {
    data(){
      return {
        msg:"This is mixin's data.".        mixinMsg:"This is mixin's data.". }  },  created(){  console.log(123)  },  methods: { onClick(){  console.log('Triggered onClick in mixin')  }  } } Copy the code

Introduced and used in components, where only mixins are used.

<template>
  <div class='container'>
    <div>{{msg}}</div>
    <div>{{mixinMsg}}</div>
    <div @click="onClick">click</div>
 </div> </template>  <script> import mixin from '@/mixin/demo.js'; export default {  mixins:[mixin], } </script> Copy the code

As a result, you can see that the mixin object has been mixed into the component and is in action:


So how does vUE choose if the content defined in a component is in conflict with the content of a mixin object

Add data, methods, and Created to the component to see the result

<template>
  <div class='container'>
    <div>{{msg}}</div>
    <div>{{mixinMsg}}</div>
    <div @click="onClick">click</div>
 </div> </template>  <script> import mixin from '@/mixin/demo.js'; export default {  mixins:[mixin],  data () {  return {  msg: 'Data in component'  }  },  created(){  console.log('Created within components')  },  methods: {  onClick(){  console.log('Triggered onClick in component')  }  }, } </script> Copy the code

The results are as follows:

  1. Properties in data take precedence over data in the component in case of key-value conflicts
  2. The hook function of the same name is merged into an array and called sequentially, and the hook function of the mixed object is called before the component spawned the hook function
  3. Options that are values for objects, such as Methods, Components, and directives, will be combined into the same object. When two object key names conflict, the component object’s key takes precedence.

Global component mixing

Call vue.mixin () for global mixing before initializing Vue

It can be used in main.js

Vue.mixin({
  data(){
    return {
      $_globalMsg:"Global mixin data"
    }
 },  created(){  console.log('Trigger the creation of global mixins')  },  methods: { $_globalMixin(){  console.log('$_globalMixin')  }  } }) Copy the code

Say there are conflicts between global mixins, local mixins, and component instances

The priority values are as follows:

Components > Local mixins > Global mixins


Consider: Implementing a plugin-like image lazy loading feature with Mixin

What mixins can do for our daily development.

Mixins can not only simplify components, but they can also extract relatively functional code for separate maintenance and reuse.

Extract individual code from components by defining local mixins.

For example, add lazy loading to all images in the component

src/mixin/demo.js

import logo from '@/assets/logo.png'
export default {
  data() {
    return {
      baseImg: logo,
 $_timer: null  }  },  mounted() {  // First screen lazy load once  this.$_lazyLoadImage();  // Listen for croll events  window.addEventListener('scroll'.this.$_handelScroll);  // Remove the croll event  this.$on('hook:beforeDestroy', () = > { window.removeEventListener('scroll'.this.$_handelScroll)  })  },  methods: {  $_handelScroll() {  clearTimeout(this.$_timer);  this.$_timer = setTimeout((a)= > {  this.$_lazyLoadImage();  }, 20);  },  // lazy loading of images  $_lazyLoadImage() {  const imgList = this.$_getNeedLoadingImg();  if(imgList.length <= 0 ) return;  // Check whether the image is displayed  imgList.forEach(img= > {  if (this.$_imgInView(img)) {  this.$_showImg(img)  }  })  },  // Get the image to load  $_getNeedLoadingImg() {  let images = Array.from(document.querySelectorAll('img[data_src]'));  images = images.filter(ele= > {  return! ele.getAttribute('isloaded')  })  return images  },  // Calculate the position of the image to determine whether the image is displayed  $_imgInView(img) {  return window.innerHeight + document.documentElement.scrollTop >= img.offsetTop  },  // Display the image  $_showImg(img) {  const image = new Image();  const src = img.getAttribute('data_src')  image.src = src;  image.onload = (a)= > {  img.src = src;  // The tag is loaded  img.setAttribute('isloaded'.true);  }  }  } } Copy the code

Use in required components

<template>
  <div class='container'>
    <div><img :src="baseImg" alt=""></div>
    <div v-for="(item,index) in imgSrc" :key="index" ><img :src="baseImg" :data_src="item" alt=""></div>
  </div>
</template>  <script> import mixin from '@/mixin/demo.js'; export default {  mixins:[mixin],  data(){  return {  imgSrc: [ "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". ]  }  } } </script>  <style lang="scss" scoped> img{  width: 200px;  height: 200px; } </style> Copy the code

Of course you can blend in globally, but it’s best to add a conditional judgment to the components you need to avoid confusion when you blend in

Just add a custom attribute to the component that needs lazy loading, and then determine if the current component has this attribute when global mixing occurs

The concrete implementation is as follows

main.js

import logo from '@/assets/logo.png'
Vue.mixin({
  data() {
    return {
      baseImg: logo,
 $_timer: null. }  },  mounted() {  // Bind the following events to the component with the $needLazyLoad parameter  if (this.$options.$needLazyLoad) {  // First screen lazy load once  this.$_lazyLoadImage();  // Listen for croll events  window.addEventListener('scroll'.this.$_handelScroll);  // Remove the croll event  this.$on('hook:beforeDestroy', () = > { window.removeEventListener('scroll'.this.$_handelScroll)  })  }  },  methods: {  $_handelScroll() {  clearTimeout(this.$_timer);  this.$_timer = setTimeout((a)= > {  this.$_lazyLoadImage();  }, 20);  },  // lazy loading of images  $_lazyLoadImage() {  const imgList = this.$_getNeedLoadingImg();  if (imgList.length <= 0) return;  // Check whether the image is displayed  imgList.forEach(img= > {  if (this.$_imgInView(img)) {  this.$_showImg(img)  }  })  },  // Get the image to load  $_getNeedLoadingImg() {  let images = Array.from(document.querySelectorAll('img[data_src]'));  images = images.filter(ele= > {  return! ele.getAttribute('isloaded')  })  return images  },  // Calculate the position of the image to determine whether the image is displayed  $_imgInView(img) {  return window.innerHeight + document.documentElement.scrollTop >= img.offsetTop  },  // Display the image  $_showImg(img) {  const image = new Image();  const src = img.getAttribute('data_src')  image.src = src;  image.onload = (a)= > {  img.src = src;  // The tag is loaded  img.setAttribute('isloaded'.true);  }  }  } }) Copy the code

Used in components

<template>
  <div class='container'>
    <div><img :src="baseImg" alt=""></div>
    <div v-for="(item,index) in imgSrc" :key="index" ><img :src="baseImg" :data_src="item" alt=""></div>
  </div>
</template>  <script> export default {  // Add the $needLazyLoad attribute to components that need lazy loading  $needLazyLoad:true. data(){  return {  imgSrc: [ "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". "Https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991, & FM = 26 & gp = 0. 859863778 JPG". ]  }  } } </script>  <style lang="scss" scoped> img{  width: 200px;  height: 200px; } </style> Copy the code

This is not the best practice for vue image lazy loading, but it is an example to better illustrate the mixin feature.

Global mixin needs to be used with caution because it affects every Vue instance, and all officials prefer to release it as a plug-in to avoid repeated application of mixin. When using local mixing, we should also pay attention to the definition of variable names do not repeat, usually can use some special symbols as the beginning, such as $_

You can also do something similar with the V-imglazy directive, which I’ll explain in the next article

P: 30 lines write a Vue image lazy loading command