Swiper在Vue中的使用

Swiper官方组件仅支持Vue3,如果需要在Vue2中使用Swiper,可以使用vue-awesome-swiper

vue-awesome-swiper也需要官方Swiper插件的支持,不过支持最好的版本是Swiper5,Swiper4和Swiper6都会出现一些bug

安装方法

npm安装

npm install vue-awesome-swiper --save
npm install swiper@5.2.0 --save

使用方法

vue-awesome-swiper相当于是对swiper进行了包装,swiper的API同样适用于vue-awesome-swiper

这个网站有相应的源码

https://github.surmon.me/vue-awesome-swiper/

在Vue组件中可以这样使用

<template>
  <swiper class="swiper" :options="swiperOption">
    <swiper-slide class="swiper-slide" v-for="(item, index) in banners" :key="index">
      <a :href="item.link" >
        <img :src="item.image" alt="">
      </a>
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
  </swiper>
</template>

<script>
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

//Swiper配置
const swiperOption = {
  initialSlide :1,
  spaceBetween: 30,
  centeredSlides: true,
  loop: true,
  pagination: {
    el: '.swiper-pagination',
    clickable: true
  },
  autoplay: {
    delay: 2500,
    disableOnInteraction: false
  }
}

export default {
  name: 'MainSwiper',
  components: {
    Swiper,
    SwiperSlide
  },
  props: {
      banners: {
      type: Array,
      default() {
        return []
      }
    }
  },
  data() {
    return {
      swiperOption
    }
  }
}
</script>

<style scoped>
  .swiper-slide {
    height: 200px;
    width: 100%;
  }
  .swiper-slide img {
    height: 100%;
    width: 100%;
  }
  .swiper {
    --swiper-pagination-color: var(--color-high-text); /* 两种都可以 */
  }
</style>
Vue实现选中当前按钮

Vue中的v-bind绑定class可以很方便的呃实现选中当前按钮点击,我们可以给每一个按钮绑定一个样式,例如:

:class="{tabControlItemActive: currentIndex === index}

初始值令currentIndex为0,并且绑定一个方法

@click="showIndex(index)

该方法就收一个参数index,也就是当前点击的按钮的index

showIndex(index) {
      this.currentIndex = index;
    }

在接收到index之后,将index的值赋给currentIndex,这样一来,currentIndex的值必定为每次点击的按钮的index值

在两者相同时,tabControlItemActive样式的值为true,便会显示出来

<template>
  <div class="tab-control">
    <div v-for="(item, index) in tabItems" :key="index"
         class="tab-control-item" :class="{tabControlItemActive: currentIndex === index}"
         @click="showIndex(index)">
      <span>{{item}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "TabControl",
  props: {
    tabItems: {
      type: Array,
      default() {
        return []
      }
    }
  },
  data() {
    return {
      currentIndex: 0
    }
  },
  methods: {
    showIndex(index) {
      this.currentIndex = index;
    }
  }
}
</script>

<style scoped>
  .tab-control {
    display: flex;
    height: 44px;
    line-height: 44px;
  }
  .tab-control-item {
    flex: 1;
    text-align: center;
  }
  .tabControlItemActive {
    color: var(--color-high-text);
  }
  .tabControlItemActive span {
    padding: 4px;
    border-bottom: 2px solid var(--color-high-text);
  }
</style>
将一个数组赋值给另一个数组

可以使用ES6的数组解构,将数组解构之后push进新数组

this.goods[type].list.push(...data.data.list);
关于create

在编写代码的时候踩的一个坑,原本想实验在create中调用两次toGetShopCityGoods()方法接收到的数据会因为页码变化而变化。

但是出现的结果是两次都是同一个结果,通过在chrome中断点调试发现,Vue是在执行完两条语句之后才开始创建Vue实例。

由此猜测,之所以两次得到同样的结果是因为Vue实例还未创建,所以得到的page都是同一个,导致最后得到相同的结果

组件绑定方法

在组件上直接绑定方法使用常规的v-on并不可行,需要加上native属性

例如:

<back-top class="back-top" @click.native="backTop"></back-top>
ref

Vue可以使用ref绑定一个组件,例如:

<div class="wrapper" ref="wrapper">

这样就在当前的组件中绑定了另外一个ref组件,可以在任何地方调用这个组件

相比较通过类名或者id查询,Vue更推荐使用此方法绑定组件

同时,也可以在任何地方通过ref属性调用这个组件的方法

这样一来,在封装的第三方插件中,如果想在其他地方调用这个插件对象极其方法时,就可以使用ref属性

<b-scroll class="shop-city-scroll" ref="BScroll">

例如,在better-scroll中,想做一个返回顶部的组件,需要调用better-scroll的方法,我们便有两种可行的方法

其一:直接调用better-scroll对象,调用其scrollTo方法

其二:在封装better-scroll的组件中,封装一个核心为scrollTo的方法,这样就可以不用调用scroll对象而是调用组件方法

better-scroll

在移动端如果使用原生的js滑动效果往往差强人意,滑动不够顺畅,没有回弹效果

better-scroll是一个基于iscroll,使用原生js包装的屏幕滑动插件,目前已经支持Vue

但是在Vue2中似乎并不能很好的支持better-scroll2.0,会出现各种各样的bug,于是在该项目中最终使用的是better-scroll1.0

安装方法

npm install better-scroll@1.13.0

使用方法

better-scroll的基本原理是一个固定大小的外层wrapper盒子,里面包含的是content盒子,content必须要比wrapper盒子更大,这样才能产生滚动效果。

<div class="wrapper" ref="wrapper">
    <div class="content">
      <slot></slot>
    </div>
</div>

better-scroll最好在mounted()生命周期函数中进行初始化,因为此时DOM元素已经创建完毕,为了保证,可以将初始化放进$nextTick()回调函数中

  mounted() {
    this.$nextTick(() => {
          this.bScroll = new BScroll(this.$refs.wrapper, {
            click: true,
          });
        }
    )
  },

一个简单的better-scroll滑动组件就创建好了,该插件还支持很多API,例如下拉刷新等

事件总线

Vue中组件通信可以通过$emit发送方法,但是当组件之间的关系比较复杂的时候,就不那么方便

因此,可以使用事件总线

使用方法

首先在main.js里面,给Vue实例新建一个属性,该属性的值为一个新的Vue实例

Vue.prototype.$bus = new Vue()

如果在任何一个组件中需要通过emit传递方法,则可以

this.$bus.$emit('show', 'show me')

在另一个组件中通过on来加载方法

this.$bus.$on('show', message => {
      console.log(message)
})

事件总线的一个注意事项

当在生命周期钩子里使用事件总线的过程中,发现事件总线会触发多次,调试无果,遂去百度,得到以下结论:

如果事件注册在外部总线上,则需要在beforeDestroy或中将其拆除destroyed

如果这变得重复,您可以编写一个 mixin 来自动执行此操作。

也就是说,在我退出界面时,并没有将事件销毁,那么在下次创建组件时在生命周期中又会再次触发事件。

防抖动函数

在有些时候,比如输入框输入关键字进行查询时,如果使用onchange每次改变都向后台请求数据的话,这样消耗的资源是很大的

如果有这样一个需求,在进行关键字输入时,如果间隔的时间很小,那么就不需要请求多次只需要请求一次

这就是防抖动,我们可以设置一个定时器,在间隔的时间很短的情况下,取消前面一次发送请求,而是在时间间隔比较大的时候发送请求

export function debounce(func, delay) {
    let timer = null;
    console.log(timer);
    return function () {
        clearTimeout(timer);
        timer = setTimeout(() => {
            func()
        }, delay)
    }
}

这里其实是使用了闭包,总结各个书籍和博文,闭包其实就是一个函数,假设这个函数为fun2,这个函数通常在另一个函数fun1的作用域中,因此,fun2能够访问到fun1的变量。

那么在fun1函数作用域外调用fun2的话,就可以通过fun2来访问fun1的变量。同时,因为fun2在fun1的作用域中,所以fun2的存在依赖于fun1,所以fun1会一直在内存中,同时fun1里面的变量也会得以保存。

function debounce(func, delay) {
            let timer = null;
            console.log(timer);
            return function () {
                console.log('time show');
                clearTimeout(timer);
                timer = setTimeout(() => {
                        func()
                    }, delay)
            }
        }

        const show = debounce(() => {
                console.log('hello world');
            },500);

        for (let index = 0; index < 30; index++) { 
            show()
        }

例如这个例子,这里其实是使用了两个闭包,第一层闭包是debounce以及它的返回值,第二层是返回值与setTimeout定时器。show保存的是第一层闭包,因此timer不会重复定义,整个debounce会被内存保存。

回到for循环中,show函数被执行,其执行的是debounce的返回值,也就是匿名函数,由于debounce还在内存中,所以show函数能够接收到参数func和delay。

此时进入第二个闭包,也就是show与setTimeout,setTimeout的定义是这样说的:

内置的工具函数setTimeout(...)持有对一个参数的引用,这个参数也许叫fn或者func,或者其他类似的名字,引擎会调用这个函数,在例子中就是内部的timer函数,而词法作用域在这个过程中保持完整

也就是说setTimeout函数里面传入的函数会被引擎自动执行,也就满足了闭包外部调用函数的条件,因此show的作用域也得以保存,所以timer的值不会被清空,在500ms的延迟内,下个show函数执行时,如果timer定时器存在,那么就删除定时器timer并重新定义

其实这个debounce函数,应该关注的是它返回值而不是它本身,因为这个函数它只会执行一次,被多次执行的是它的返回值,它的返回值才是真正应该注意的闭包,外层这个debounce闭包,其实就是为了保存第一次的timer,因为如果不这样将timer定义在函数内,那么就只有作为一个全局变量了,这时非常危险的

同时可以去chrome里面进行调试,可以发现,重复执行的其实是debounce的返回值,其本身只初始化了一次

这篇文章讲得很明白:

https://zhuanlan.zhihu.com/p/110733457

Data存储的值

data中一般存储的是后台的数据或者个别插件的配置

在data中存储后台传递过来的值的时候,一般是先在data里面定义一个空值,然后create函数中进行axios请求,将请求到的值赋予data里面的属性值

这里要注意,切不要在data中直接存储一个大的对象,这样在进行props传参数的时候,只能通过对象属性的方式传递参数,由于子组件不知道父组件传递过来的是什么数据类型,因此会报错

margin遇到的坑

当margin应用于行内元素时,会出现margin-top和margin-bottom失效的问题,具体什么原因不得而知

当使用margin时,需要将行内元素转换为块级元素或者行内块元素

同时,使用margin时,还需要注意外边距合并的问题

时间戳格式化

时间戳格式化需要先将时间戳转换为标准时间格式:

注意时间戳在JavaScript中要乘1000

const time = new Date(time * 1000);
//const time = new Date(1535697272 * 1000);

接着有两种方式可以转换

通过JavaScript内置方法

例如:

options = {
            year: 'numeric', month: 'numeric', day: 'numeric',
            hour: 'numeric', minute: 'numeric', second: 'numeric',
            hour12: false
        };
console.log(time.toLocaleString('zh-CN', options));

JavaScript内置了许多的格式化方法,还有DateTimeFormat等等,这样做的优点是代码简洁,但是同时也会有转换不够灵活,部分浏览器不支持等问题

通过自定义函数(正则表达式)

自定义一个formatDate函数:

function formatDate(date, fmt) {
            if (/(y+)/.test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
            }
            let o = {
                'M+': date.getMonth() + 1,
                'd+': date.getDate(),
                'h+': date.getHours(),
                'm+': date.getMinutes(),
                's+': date.getSeconds()
            };
            for (let k in o) {
                if (new RegExp(`(${k})`).test(fmt)) {
                    let str = o[k] + '';
                    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
                }
            }
            return fmt;
        };

function padLeftZero(str) {
    return ('00' + str).substr(str.length);
}

const formatTime = formatDate(time, 'yyyy/MM/dd hh:mm');
console.log(formatTime);

formatDate接收两个参数,第一个参数为实例化Date对象,第二个参数为指定显示的时间格式

Vue插件

lazyload

安装方式

npm install vue-lazyload --save

使用方式

一、导入lazyload

import VueLazyLoad from "vue-lazyload";

二、使用lazyload

const errorImage = require('./assets/img/common/errorImg.png');
const loadImage = require('./assets/img/common/lazyLoadImg.gif');

Vue.use(VueLazyLoad, {
  preLoad: 1.3,
  error: errorImage,
  loading: loadImage,
  attempt: 1
});

三、替换img标签的src属性

<img v-lazy="goodsItem.show.img" alt="">

fastclick

安装方式

npm install fastclick --save

使用方式

一、导入fastclick

import FastClick from 'fastclick';

二、使用fastclick

FastClick.attach(document.body);

postcss-pxtorem

安装方式

npm install postcss postcss-pxtorem --save-dev

注意:该插件依赖postcss,如果版本不正确便会报错

这里使用的版本为

"postcss": "^8.3.6",
"postcss-pxtorem": "^5.1.1",

使用方式

一、配置postcss.config

module.exports = {
    plugins: {
        // 兼容浏览器,添加前缀
        'autoprefixer':{
            overrideBrowserslist: [
                'last 10 versions', // 所有主流浏览器最近10版本用
            ],
            grid: true
        },
        'postcss-pxtorem': {
            rootValue: 16,  //结果为:设计稿元素尺寸/16,比如元素宽320px,最终页面会换算成 20rem
            propList: ['*']
        }
    }
}

这里是将全部的px都转换为了rem,使用了通配符

二、配置rem.js

export default function getFontSize () {
    const initFontSize = 16
    const iPhone6Width = 375
    const clientWidth = window.document.documentElement.clientWidth || iPhone6Width
    const newFontSize = initFontSize * (clientWidth / iPhone6Width)
    document.documentElement.style.fontSize = newFontSize + 'px'
}

这里是通过js获取到当前屏幕大小,并根据屏幕大小转换相应的root-font-size

三、导入rem.js并执行

import getFontSize from "@/rem";

(getFontSize)()

在主函数中导入该函数并立即执行