阅读 3588

Vue的混入(mixin)知多少?如何用mixin实现一个图片懒加载功能

前言

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立。这时,我们可以通过Vue的mixin功能将相同或者相似的代码提出,这样一来即方便了代码的复用也使得维护更加容易。

这篇文章将为您讲述 Vue mixin的使用方法,会通过实现一个图片懒加载的功能,让你对mixin有更深入的了解。

基础的使用方法

局部组件混入

我们新建一个mixin文件夹用来保存我们想要混入的对象,在需要使用混入对象的组件内进行导入。

src/mixin/demo.js

export default {
    data(){
      return {
        msg:"这是mixin的数据",
        mixinMsg:"这是mixin的数据",
      }
    },
    created(){
      console.log(123)
    },
    methods:{
      onClick(){
        console.log('触发了mixin中的onClick')
      }
    }
}
复制代码

在组件中引入并使用,组件中只使用mixin。

<template>
  <div class='container'>
    <div>{{msg}}</div>
    <div>{{mixinMsg}}</div>
    <div @click="onClick"> 点一下 </div>
  </div>
</template>

<script>
import mixin from '@/mixin/demo.js';
export default {
  mixins:[mixin],
}
</script>
复制代码

结果如下,可以看到mixin对象已经混入组件并起了作用

那么如果组件中定义的内容于mixin对象的内容发生的冲,vue会如何做出选择呢

在组件中加入data,methods和created来看一下结果

<template>
  <div class='container'>
    <div>{{msg}}</div>
    <div>{{mixinMsg}}</div>
    <div @click="onClick"> 点一下 </div>
  </div>
</template>

<script>
import mixin from '@/mixin/demo.js';
export default {
  mixins:[mixin],
  data () {
    return {
      msg: '组件中的数据'
    }
  },
  created(){
    console.log('组件内的created')
  },
  methods: {
    onClick(){
      console.log('触发了组件中的onClick')
    }
  },
}
</script>
复制代码

结果如下:

  1. data中的属性在键值发生冲突的时候,会以组件中的数据优先
  2. 同名钩子函数将被合并为一个数组,会依次调用,混合对象的钩子函数将在组件滋生钩子函数之前调用
  3. 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突的时候,组件对象的键值优先。

全局组件混入

在初始化Vue之前调用Vue.mixin()进行全局混入

可以在main.js中使用

Vue.mixin({
  data(){
    return {
      $_globalMsg:"全局mixin数据"
    }
  },
  created(){
    console.log('触发全局mixin的Created')
  },
  methods:{
    $_globalMixin(){
      console.log('$_globalMixin')
    }
  }
})
复制代码

如果说全局mixin、局部mixin、和组件实例中存在冲突

优先值如下:

组件 > 局部mixin > 全局mixin


思考:通过mixin实现一个类似插件的图片懒加载功能

mixin可以为我们日常的开发做什么。

mixin不仅仅时能够简化组件,还可以将功能相对独立的代码提取出来,进行单独的维护,多处复用。

通过定义局部mixin提取组件中的独立代码。

例如:给组件中的所有图片添加懒加载

src/mixin/demo.js

import logo from '@/assets/logo.png'
export default {
  data() {
    return {
      baseImg: logo,
      $_timer: null
    }
  },
  mounted() {
    // 首屏懒加载一次
    this.$_lazyLoadImage();
    // 监听croll事件
    window.addEventListener('scroll', this.$_handelScroll);
    // 移除croll事件
    this.$on('hook:beforeDestroy', () => {
      window.removeEventListener('scroll', this.$_handelScroll)
    })
  },
  methods: {
    $_handelScroll() {
      clearTimeout(this.$_timer);
      this.$_timer = setTimeout(() => {
        this.$_lazyLoadImage();
      }, 20);
    },
    // 懒加载图片
    $_lazyLoadImage() {
      const imgList = this.$_getNeedLoadingImg();
      if(imgList.length <= 0 ) return;
      // 判断图片是否展示
      imgList.forEach(img => {
        if (this.$_imgInView(img)) {
          this.$_showImg(img)
        }
      })
    },
    // 获取需要加载的图片
    $_getNeedLoadingImg() {
      let images = Array.from(document.querySelectorAll('img[data_src]'));
      images = images.filter(ele => {
        return !ele.getAttribute('isloaded')
      })
      return images
    },
    // 计算图片位置,判断图片是否展示
    $_imgInView(img) {
      return window.innerHeight + document.documentElement.scrollTop >= img.offsetTop
    },
    // 展示图片
    $_showImg(img) {
      const image = new Image();
      const src = img.getAttribute('data_src')
      image.src = src;
      image.onload = () => {
        img.src = src;
        // 标记已加载完成
        img.setAttribute('isloaded', true);
      }
    }
  }
}
复制代码

在需要的组件中使用

<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,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
      ]
    }
  }
}
</script>

<style lang="scss" scoped>
img{
  width: 200px;
  height: 200px;
}
</style>
复制代码

当然你也可以给全局中进行混入,不过最好给需要的组件加上一个条件判断,以避免混入时可能造成的混乱

只需要给需要懒加载的组件加一个自定义属性,然后在全局混入时判断当前组件有没有这个属性即可

具体实现如下

main.js

import logo from '@/assets/logo.png'
Vue.mixin({
  data() {
    return {
      baseImg: logo,
      $_timer: null,
    }
  },
  mounted() {
    // 给具有$needLazyLoad 参数的组件绑定以下事件
    if (this.$options.$needLazyLoad) {
      // 首屏懒加载一次
      this.$_lazyLoadImage();
      // 监听croll事件
      window.addEventListener('scroll', this.$_handelScroll);
      // 移除croll事件
      this.$on('hook:beforeDestroy', () => {
        window.removeEventListener('scroll', this.$_handelScroll)
      })
    }
  },
  methods: {
    $_handelScroll() {
      clearTimeout(this.$_timer);
      this.$_timer = setTimeout(() => {
        this.$_lazyLoadImage();
      }, 20);
    },
    // 懒加载图片
    $_lazyLoadImage() {
      const imgList = this.$_getNeedLoadingImg();
      if (imgList.length <= 0) return;
      // 判断图片是否展示
      imgList.forEach(img => {
        if (this.$_imgInView(img)) {
          this.$_showImg(img)
        }
      })
    },
    // 获取需要加载的图片
    $_getNeedLoadingImg() {
      let images = Array.from(document.querySelectorAll('img[data_src]'));
      images = images.filter(ele => {
        return !ele.getAttribute('isloaded')
      })
      return images
    },
    // 计算图片位置,判断图片是否展示
    $_imgInView(img) {
      return window.innerHeight + document.documentElement.scrollTop >= img.offsetTop
    },
    // 展示图片
    $_showImg(img) {
      const image = new Image();
      const src = img.getAttribute('data_src')
      image.src = src;
      image.onload = () => {
        img.src = src;
        // 标记已加载完成
        img.setAttribute('isloaded', true);
      }
    }
  }
})
复制代码

在组件中使用

<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 {
  // 给需要懒加载的组件添加$needLazyLoad属性
  $needLazyLoad:true,
  data(){
    return {
      imgSrc:[
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
        "https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1091405991,859863778&fm=26&gp=0.jpg",
      ]
    }
  }
}
</script>

<style lang="scss" scoped>
img{
  width: 200px;
  height: 200px;
}
</style>
复制代码

这样一个图片的懒加载的功能就可以实现了,而且就和插件一样随用随引,当然这并不是vue图片懒加载的最佳实践,这里只是拿这个图片懒加载举个例子,能更好的说明mixin的特性。

需要谨慎使用全局混入,因为全局混入会影响每个Vue实例,所有官方更推荐将其作为插件发布,以避免重复应用混入。在使用局部混入的时候也要注意一下定义的变量名不要重复,通常可以使用一些特殊符号做为开头,如文中的$_

除此之外我们还可以自己做一个v-imgLazy指令来实现类似的功能,我会在下一篇文章中进行说明

补:30行写一个Vue图片懒加载指令