使用JavaScript和Vue简化代码的功能

一、前言

现在也算上暂时闲下来了。算算已经好久没写文章了。这篇文章记录下我使用的一些前端小功能吧。如有错误,请及时指出。

二、vue数字滚动小组件

开发中要用到数字上下滚动的组件。在github上找了找这种功能。找到了一个vue-digitroll。周末花了一下午的时间研究了vue-digitroll的源码,很不错。vue-digitroll而且还做了浏览器兼容。最终没有用在项目里,原因有三点:

  1. 项目中暂时不需要考虑这种兼容性。
  2. 项目中也不需要这么多的功能。
  3. vue-digitroll虽然很轻量,但毕竟也要安装。安装了就要多少占点体积。

基于上面三点考虑,我就参考了源码实现,自己写了一个简单的,易于理解的小组件。

大概原理就是数字转为字符串,数字定高,宽度是自己的宽度。循环0到9,超出就往下排。通过overflow:hidden隐藏超出的数字。通过传入的数字找到对应数字的高度位置。translateY实现滚动效果。

下面就贴出来源码:

<template>

  <div class="roll-wrap" :style="{fontSize:`${cellHeight}px` }">

    <ul class="roll-box">

      <li

        class="roll-item"

        v-for="(item, index) in numberArr"

        :key="index"

        :style="{ height: `${cellHeight}px`,lineHeight:`${cellHeight}px`}"

      >

        <!--小数点或其他情况-->

        <div v-if="isNaN(parseFloat(item))">{{ item }}</div>

        <div v-else :style="getStyles(index)">

          <!--数字0到9-->

          <div

            :style="{ height: `${cellHeight}px`,lineHeight:`${cellHeight}px`}"

            v-for="(subItem,subIndex) in oneToNineArr"

            :key="subIndex"

          >{{ subItem }}</div>

        </div>

      </li>

    </ul>

  </div>

</template>

<script>

export default {

props: {

// 高度,默认30

cellHeight: {

typeNumber,

default30

},

// 需要传入的滚动数字

rollNumber: {

type: [StringNumber],

default0

},

// 滚动持续时间,单位ms.默认1.5s

dur: {

typeNumber,

default1500

},

// 缓动函数,默认ease

easeFn: {

typeString,

default'ease'

}

},

data () {

const { rollNumber } = this

return {

// 传入的数字

number: `${rollNumber}`,

// 传入的数字解析为数组

numberArr: [],

// 偏移量

numberOffsetArr: [],

// 0到9数组

oneToNineArr: [0123456789]

}

},

created () {

this.numberArr = this.number.split('')

this.resetState(this.numberArr.length)

},

watch: {

rollNumber (value, oldVal) {

this.number = `${value}`

this.numberArr = `${value}`.split('')

this.resetState(this.numberArr.length)

}

},

methods: {

resetState (len) {

const newArr = new Array(len).join(',').split(',')

this.numberOffsetArr = newArr.map(() => 0)

// 延迟执行动画

setTimeout(() => {

// 设置传入的数字下标对应偏移量,重新赋值

this.numberArr.forEach((num, i) => {

this.$set(this.numberOffsetArr, i, num * this.cellHeight)

})

}, 30)

},

getStyles (index) {

const style = { transition`${this.easeFn} ${this.dur}ms`transform`translate(0%, -${this.numberOffsetArr[index]}px)` }

return style

}

}

}

</script>

<style lang="stylus" scoped>

.roll-wrap

ul.roll-box

display flex

padding 0

margin 0

text-align center

overflow hidden

li

overflow hidden

</style>

使用方式也很简单,如下:

 <number-roll :roll-number="9999" />

三、前端JS计算丢失精度问题

具体参考 JavaScript 浮点数陷阱及解法和number-precision 这篇文章和number-precision开源库。

我也看了看源码,进行了一些测试,摘出来了一些,下面就贴一下我摘出来的源码:

/**

 * 解决浮点运算问题,避免小数点后产生多位数和计算精度损失。

 */

export default {

  /**

* 返回数字长度

* @param {*number} num Input number

*/

digitLength (num) {

const len = (num.toString().split('.')[1] || '').length

return len > 0 ? len : 0

},

/**

* 把小数转成整数,如果是小数则放大成整数

* @param {*number} num 输入数

*/

float2Fixed (num) {

return Number(num.toString().replace('.'''))

},

/**

* 精确加法

* plus(0.1, 0.2) // = 0.3, not 0.30000000000000004

*/

plus (num1, num2) {

const baseNum = Math.pow(10Math.max(this.digitLength(num1), this.digitLength(num2)))

return (num1 * baseNum + num2 * baseNum) / baseNum

},

/**

* 精确减法

* minus(1.0, 0.9) // = 0.1, not 0.09999999999999998

*/

minus (num1, num2) {

const baseNum = Math.pow(10Math.max(this.digitLength(num1), this.digitLength(num2)))

return (num1 * baseNum - num2 * baseNum) / baseNum

},

  /**

* 精确乘法

* times(3, 0.3) // = 0.9, not 0.8999999999999999

*/

times (num1, num2) {

const num1Changed = this.float2Fixed(num1)

const num2Changed = this.float2Fixed(num2)

const baseNum = this.digitLength(num1) + this.digitLength(num2)

const leftValue = num1Changed * num2Changed

return leftValue / Math.pow(10, baseNum)

},

  /**

* 精确除法

* divide(1.21, 1.1) // = 1.1, not 1.0999999999999999

*/

divide (num1, num2) {

const num1Changed = this.float2Fixed(num1)

const num2Changed = this.float2Fixed(num2)

return (num1Changed / num2Changed) * Math.pow(10this.digitLength(num2) - this.digitLength(num1))

},

/**

* 四舍五入

* round(0.105, 2); // = 0.11, not 0.1

*/

round (num, ratio) {

const base = Math.pow(10, ratio)

return this.divide(Math.round(this.times(num, base)), base)

}

}

四、async await 简化代码

因为项目里使用axios进行了全局异常处理的提示,不需特殊处理的情况下,没有必要进行try{}catch{}代码块包装了。因为大多数按钮提交的时候要增加loading,就可以使用fianlly以下方式简化代码。

this.submitLoading = true

if(this.submitLoading) return

const res = await submitForm({name:'zhangsan',age:'20'}).finally(() => { this.submitLoading = false })

五、使用element的scroll-bar组件

很多开源库中都使用了element<el-scrollbar/>组件。这个组件真的好用,如果你有定高但是需要显示滚动条实现滚动的需求。就可以很简单的实现好看的滚动条。比如如下面的代码:

 <el-scrollbar style="height: 300px;">

  <el-tree

    :data="data"

  />

</el-scrollbar>

六、封装一些简单的搜索小组件

列表的搜索功能是必备的。在使用库的时候避免大量引入组件的标签,封装一些不那么复杂的搜索小组件,使用起来很方便。比如下面的代码:

<template>

  <el-row type="flex" align="middle">

    <el-col :span="24">

      <el-form @keyup.enter.native="querySearch()" @submit.native.prevent class="flex-center" :inline="inline">

        <el-form-item v-for="(item,index) in formArr" :key="index" :label="item.label">

          <el-select v-if="item.tagName === 'select'" v-model="item.value" placeholder="请选择">

            <el-option v-for="item in item.options || []" :key="item.value" :value="item.value" :label="item.label" />

          </el-select>

          <el-input v-else v-model="item.value" :placeholder="item.placeholder"></el-input>

        </el-form-item>

        <el-form-item>

          <el-button icon="el-icon-search" type="warning" @click="querySearch()">查询</el-button>

        </el-form-item>

      </el-form>

    </el-col>

  </el-row>

</template>

<script>

import { deepCopy } from 'utils/utils'

export default {

  props: {

    searchColumns: {

      typeArray,

      default () {

        return []

      }

    },

    inline: {

      typeBoolean,

      defaulttrue

    }

  },

  data () {

    return {

      formArr: []

    }

  },

  methods: {

    querySearch () {

      const obj = {}

      this.formArr.forEach(el => {

        obj[el.prop] = el.value

      })

      this.$emit('query-search', obj)

    }

  },

  watch: {

    searchColumns: {

      handler (val) {

        this.formArr = deepCopy(this.searchColumns)

      },

      deeptrue,

      immediatetrue

    }

  }

}

</script>

使用起来也很简单:

<simple-search :searchColumns="searchColumns" @query-search="querySearch" />

export default {

  data () {

    return {

      // 搜索条件

      condition: {}

      // 搜索列

      searchColumns: [

        {

          label: '名称',

          prop: 'name',

          value: '',

          placeholder: '请输入名称'

        }

    }

  },

  methods: {

    querySearch (queryForm) {

      this.condition = queryForm

      this.getList()

    }

  }

}

七、使用Vue的mixins简化代码

mixins真是个好东西,善于使用mixins可以简化不少代码。加快Vue项目的开发速度这篇文章挺不错。但是mixins不能滥用,不要在全局中使用。

因为大部分后台列表页面都要请求列表,都要分页,加载loading等,我们没有必要在每个Vue组件下面都写这些属性。下面是我用mixins的实现了一些简化这些代码的功能(基于ElementUI)。如果每次切换路由的时候,需要记住当前用户离开这个列表页面之前的页码,可以使用localStorage来存储页码。

/*

 * 分页mixins

 */

export default {

  data () {

    return {

      // 分页

      pagination: {

        // 当前页

        page: 1,

        // 页长

        size: 10,

        // 总个数

        total: 0,

        // 分页布局

        layout: 'prev,pager,next,total,jumper'

      },

      // 增,删,改按钮loading

      load: {

        addLoading: false,

        deleteLoading: false,

        editLoading: false

      },

      // 列表loading

      listLoading: true

    }

  },

  created () {

    if (!(Object.prototype.toString.call(this.getList) === '[object Function]')) {

      throw new Error('请在组件内定义getList方法加载数据!')

    }

  },

  methods: {

    // 改变页码handle

    pageChange (val) {

      this.pagination.page = val

      this.getList()

    },

    // 移除一条数据后重新获取列表数据

    getListForDelSingle (list = [], index = 0) {

      list.splice(index, 1)

      // 如果当前页无数据

      if (list.length <= 0) {

        this.pagination.page--

        if (this.pagination.page <= 0) {

          this.pagination.page = 1

        }

      }

      this.getList()

    },

    // 移除多条数据后重新获取列表数据

    getListForDeltMany (delLen, listLen) {

      if (!delLen || !listLen) return

      if (delLen >= listLen) {

        this.pagination.page--

        if (this.pagination.page <= 0) {

          this.pagination.page = 1

        }

      }

      this.getList()

    }

  }

}

八、一些简单的工具方法

如果说项目中没有还安装lodash的话,都可以加以下的,很轻量,很好用。能够节省很多时间。还有就是好多开源库的工具方法都非常棒,比如说Element,iview,ant-design,vant等都可以参考学习或者在项目中直接拿来用。

  • 一些form表单对象有很多时候需要初始化,如果手写代码一行一行的修改的话,代码会非常冗余。如果说form表单不是很复杂的话,就可以用下面这种方式实现表单初始化效果:

this.userForm.name = ''

this.userForm.pwd = ''

function initForm (form = {}, init = { num: 0, str: '' }{

  const newForm = {}

  const toString = Object.prototype.toString

  for (const [key, value] of Object.entries(form)) {

    if (toString.call(value) === '[object String]') {

      newForm[key] = init.str

    } else if (toString.call(value) === '[object Number]') {

      newForm[key] = init.num

    } else if (toString.call(value) === '[object Array]') {

      newForm[key] = []

    } else if (toString.call(value) === '[object Object]') {

      newForm[key] = {}

    }

  }

  return newForm

}

  • 树结构转为一维数组

function getFlattenDeepList (nodes = []) {

  let list = []

  nodes.forEach(item => {

    list.push(item)

    if (item.children && item.children.length) {

      const tempList = getFlattenDeepList(item.children)

      list = [...list, ...tempList]

    }

  })

  return list

}

  • 根据最子项ID获取所有对应的树级父级ID

function getParentIdListByLeafId (leafId, nodes = [], newNodes = []{

  if (!leafId) return []

  for (let i = 0, len = nodes.length; i < len; i++) {

    const tempList = newNodes.concat()

    tempList.push(nodes[i].id)

    // 找到匹配返回结果

    if (leafId === nodes[i].id) {

      return tempList

    }

    if (nodes[i].children && nodes[i].children.length) {

      const result = getParentIdListByLeafId(leafId, nodes[i].children, tempList)

      if (result) return result

    }

  }

}

  • 一维数组转树状结构

let arr = [

  { id1pid''name'1AA' },

  { id2pid''name'2AA' },

  { id3pid''name'3AA' },

  { id4pid1name'4AA' },

  { id5pid2name'5AA' },

  { id6pid3name'6AA' },

  { id7pid4name'7AA' },

  { id8pid1name'8AA' },

  { id9pid5name'9AA' }

]

const newArr = []

arr.forEach(el => {

  el.children = []

  if (!el.pid) {

    newArr.push(el)

  } else {

    const parent = arr.find(_ => _.id === el.pid)

    parent.children.push(el)

  }

})

九、关于正则表达式

正则表达式本身是很复杂的(其实我也不是很懂…),关于需要正则表达式来验证的功能。如果项目时间比较紧,拿一些比较严谨的开源库里的正则直接用是可以的。推荐铁皮铁皮饭盒老师的正则大全这个库,几千个star。应该是经过很严谨的验证的,不要在通过网上随便搜出来的正则拿来直接用,我始终感觉不是正确的。不过正则确实是应该抽出一大段时间好好学的。

十、使用Vue语法糖

使用好Vue的语法糖(v-model,v-on,v-bind)也可以简化代码的编写。比如说要封装<el-select/>组件修改样式或进行特殊定制。就可以像下面代码这样(参考自Vue-Element-Admin):

<template>

  <el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs"  v-on="$listeners" multiple">

    <slot />

  </el-select>

</template>

<script>

import Sortable from 'sortablejs'

export default {

name'DragSelect',

props: {

value: {

typeArray,

requiredtrue

}

},

computed: {

selectVal: {

get() {

return [...this.value]

},

set(val) {

this.$emit('input', [...val])

}

}

}

}

</script>

<el-drag-select v-model="value" style="width:500px;" multiple placeholder="请选择">

  <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />

</el-drag-select>

<script>

import ElDragSelect from '@/components/DragSelect' // base on element-ui

export default {

name'DragSelectDemo',

components: { ElDragSelect },

data() {

return {

value: ['Apple'],

options: [{

value'Apple',

label'Apple'

}]

}

}

}

</script>

十一、关于Vue生命周期

每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新DOM等。其实理解了初始化顺序,就可以知道在钩子函数里该做什么事情了。这里参考Vue生命周期。

十二、关于文章排版

文章排版使用MD2All。使用起来很简单,把在掘金上写的文字和代码复制到左侧黑色区域,然后点击一键排版,然后点保存,复制带样式的文字和代码后,在粘贴回来就可以了。注意以下,最好自己原来的文章留个备份,不然生成的很多样式代码无法理解。

有一个坑需要注意以下当使用``来解析代码的时候,注意后面不要加html或者js了。不然MD2All`解析不友好。

以上是 使用JavaScript和Vue简化代码的功能 的全部内容, 来源链接: utcz.com/a/21928.html

回到顶部