总结一下使用JavaScript和Vue一些简化代码的功能

2,518 阅读4分钟

一、前言

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

二、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: {
      type: Number,
      default: 30
    },
    // 需要传入的滚动数字
    rollNumber: {
      type: [String, Number],
      default: 0
    },
    // 滚动持续时间,单位ms.默认1.5s
    dur: {
      type: Number,
      default: 1500
    },
    // 缓动函数,默认ease
    easeFn: {
      type: String,
      default: 'ease'
    }
  },
  data () {
    const { rollNumber } = this
    return {
      // 传入的数字
      number: `${rollNumber}`,
      // 传入的数字解析为数组
      numberArr: [],
      // 偏移量
      numberOffsetArr: [],
      // 0到9数组
      oneToNineArr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
  },
  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" />

三、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: {
      type: Array,
      default () {
        return []
      }
    },
    inline: {
      type: Boolean,
      default: true
    }
  },
  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)
      },
      deep: true,
      immediate: true
    }
  }
}
</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 = [
  { id: 1, pid: '', name: '1AA' },
  { id: 2, pid: '', name: '2AA' },
  { id: 3, pid: '', name: '3AA' },
  { id: 4, pid: 1, name: '4AA' },
  { id: 5, pid: 2, name: '5AA' },
  { id: 6, pid: 3, name: '6AA' },
  { id: 7, pid: 4, name: '7AA' },
  { id: 8, pid: 1, name: '8AA' },
  { id: 9, pid: 5, name: '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: {
      type: Array,
      required: true
    }
  },
  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生命周期

本文使用 mdnice 排版