点击跳转集体博客查看往期文章

前言

本文次要介绍日常我的项目开发过程中的一些技巧,不仅能够帮忙晋升工作效率,还能进步利用的性能。以下是我总结一些平时工作中的教训。

minxin 让组件复用灵活化

Vue 提供了 minxin 这种在组件内插入组件属性的办法,集体倡议这货能少用就少用,然而有个场景则十分倡议应用 minxin:当某段代码反复呈现在多个组件中,并且这个反复的代码块很大的时候,将其作为一个 minxin 经常能给前期的保护带来很大的不便。

这是我的项目中封装一个列表性能,有下拉刷新,加载主动申请数据,上拉加载下一页数据等等,它是这样的

看不懂没关系我只是开发中举了一个例子

export default {  data() {    return {      page: 1,      limit: 10,      busy: false, // 申请拦挡,避免屡次加载      finish: false, // 是否申请实现,用于页面展现成果      pageList: [], // 页面数据      reqParams: {}, // 页面申请参数,可被扭转的      defaultParams: {}, // 页面申请参数,下拉刷新不会被重置的扭转      routeName: '', // 非凡状况,页面须要复用他人的list的时候      autoReq: true, // onload是否本人去申请      lodingText: '', // 申请中底部显示的文案      noDataText: '暂无数据', // 自定义无数据文案      lastText: '- 我是有底线的 -',      noData: false, // 页面无数据      reqName: '',    }  },  created() {    this.autoReq && this.initPage(false, true)  },  onPullDownRefresh() {    this.pullDownRefreshFn()  },  onReachBottom() {    this.reachBottomFn()  },  methods: {    // 重置初始化数据    initPage(saveParams = true, refresh = false) {      // 初始化所有变量      this.page = 1      this.busy = false      this.finish = false      this.noData = false      this.lodingText = '数据加载中'      if (saveParams) {        const { page, limit } = this.reqParams        page ? (this.page = page) : ''        limit ? (this.limit = limit) : ''      } else {        this.reqParams = {}      }      this.getCommonList(refresh)    },    // 下拉刷新函数    pullDownRefreshFn() {      this.initData()      this.initPage(false, true)    },    // 上啦加载函数    reachBottomFn() {      this.getCommonList()    },    // 重置数据,不便调用(个别在里面自定义清空一些数据)    initData() {      // 重置data外面的变量,不便里面援用这个mixin的时候,下拉刷新重置变量    },    // 列表获取数据接口    async getCommonList(refresh) {      if (!this.reqName) return      if (this.busy) return      this.busy = true      this.finish = false      const httpFn = this.$http || getApp().globalData.$http // 兼容nvue      try {        const query = {          ...this.defaultParams,          ...this.reqParams,          page: this.page,          limit: this.limit,        }        const { data } = await httpFn(this.reqName, query)        if (this.page === 1) this.pageList = []        /**         * [Node.JS中用concat和push连贯两个或多个数组的性能比拟](http://ourjs.com/detail/5cb3fe1c44b4031138b4a1e2)         * 那么两者在node.js的性能如何? 咱们做了一组测试数据,两种别离测试100万次。         * push比concat办法speed 3倍左右。因为push只是在原数组的根底上进行批改,所以会快一点。         * push返回的是数组的长度,所以没从新定义变量再判断了         * [Array.prototype.push.apply(arr1, arr2)无奈主动触发DOM更新](https://www.imooc.com/wenda/detail/494323)         * 因为 this.pageList.push !== Array.prototype.push,,this.pageList.push指向的是vue重写过的办法         */        this.finish = true        const resLen = data.list ? data.list.length : 0        if (resLen === 0) {          this.resSuccess(data, refresh)          return        }        const listLen = this.pageList.push.apply(this.pageList, data.list)        if (listLen < data.count && this.limit <= resLen) {          // 阐明还有数据          this.busy = false          this.page = Math.ceil(listLen / this.limit) + 1        }        this.resSuccess(data, refresh)      } catch (e) {        // 避免接口报错锁死        this.busy = false        this.finish = true      }    },    resSuccess(data, refresh) {      if (this.finish && this.busy) {        if (this.pageList.length > 0) {          this.$nextTick(() => {            setTimeout(() => {              this.lodingText = this.lastText            }, 100)          })        } else {          this.lodingText = this.noDataText          this.noData = true        }      }      refresh && uni.stopPullDownRefresh()      this.finishInit(data)    },    // 申请实现做点什么(不便里面导入的文件本人援用)    finishInit(data) {      // 申请实现做点什么      // console.log('列表申请实现');    },  },}

很多人看到着应该很好奇为什么不封装成一个组件,然而因为很多列表款式不尽相同,所以封装成一个组件可扩展性不高。
当初咱们能够这样应用。

<template>  <view class="c-recommend-goods">    <!-- 列表款式 -->    <view class="" v-for="item in pageList" :key="item.id">{{item}}</view>    <!-- 空状态&& 加载中等小提示 -->    <c-no-data v-if="lodingText" :show-img="noData" :text="lodingText"></c-no-data>  </view></template><script>import listMixins from '@/common/mixins/list.js'export default {  mixins: [listMixins],  data() {    return {      autoReq: false, // 进入页面主动申请数据      reqParams: {}, // 申请参数      reqName: 'userCompanyList' // 申请地址    }  }}</script><style></style>

咱们只有定义申请参数和申请的地址,还有列表的款式,就能实现一个不错的列表性能。

援救繁乱的 template--render 函数

  • template 里存在一值多判断
  • 代码冗亲,代码芜杂

举一个官网文档的例子

<template>  <div>    <h1 v-if="level === 1">      <slot></slot>    </h1>    <h2 v-else-if="level === 2">      <slot></slot>    </h2>    <h3 v-else-if="level === 3">      <slot></slot>    </h3>    <h4 v-else-if="level === 4">      <slot></slot>    </h4>    <h5 v-else-if="level === 5">      <slot></slot>    </h5>    <h6 v-else-if="level === 6">      <slot></slot>    </h6>  </div></template><script>export default {  data() {    return {}  },  props: {    level: {      type: Number,      required: true,    },  },}</script>

当初应用 render 函数重写下面的例子:

<script>  export default {    props: {      level: {        require: true,        type: Number,      }    },    render(createElement) {      return createElement('h' + this.level, this.$slots.default);    }  };</script>

一劳永逸的组件注册

组件应用前,须要引入后再注册:

import BaseButton from './baseButton'import BaseIcon from './baseIcon'import BaseInput from './baseInput'export default {  components: {    BaseButton,    BaseIcon,    BaseInput  }}

当初 BaseButton、 BaseIcon 和 BaseInput 都能够在模板中应用了:

<BaseInput  v-model="searchText"  @keydown.enter="search"/><BaseButton @click="search">  <BaseIcon name="search"/></BaseButton>

但如果组件多了后,每次都要先导入每个你想应用的组件,而后再注册组件,便会新增很多代码量!咱们应该如何优化呢?

这时,咱们须要借助一下 webpack 的 require.context() 办法来创立本人的(模块)上下文,从而实现主动动静 require 组件。这个办法须要 3 个参数:要搜寻的文件夹目录,是否还应该搜寻它的子目录,以及一个匹配文件的正则表达式。

咱们先在 components 文件夹(这外面都是些高频组件)增加一个叫 global.js 的文件,在这个文件里应用 require.context 动静将须要的高频组件通通打包进来。而后在 main.js 文件中引入 global.js 的文件。

//  global.js文件import Vue from 'vue'function changeStr (str) {  return str.charAt(0).toUpperCase() + str.slice(1)}const requireComponent = require.context('./', false, /\.vue$/)// 查找同级目录下以vue结尾的组件const install = () => {  requireComponent.keys().forEach(fileName => {    let config = requireComponent(fileName)    console.log(config) // ./child1.vue 而后用正则拿到child1    let componentName = changeStr(      fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')    )    Vue.component(componentName, config.default || config)  })}export default {  install // 对外裸露install办法}
// main.jsimport index from './components/global.js'Vue.use(index)

最初咱们就能够随时随地在页面中应用这些高频组件,无需再手动一个个引入了。

暗藏的大招--hook

开发过程中咱们有时候要创立一个定时器,在组件被销毁之前,这个定时器也要销毁。代码如下:

mounted() {  // 创立一个定时器    this.timer = setInterval(() => {      // ......    }, 500);  },  // 销毁这个定时器。  beforeDestroy() {    if (this.timer) {      clearInterval(this.timer);      this.timer = null;    }  }

这种写法有个很显著的弊病:定时器 timer 的创立和清理并不是在一个中央,这样很容易导致遗记去清理!

咱们能够借助 hook 对代码整合,这样代码也更容易保护了:

mounted() {    let timer = setInterval(() => {      // ......    }, 500);    this.$once("hook:beforeDestroy", function() {      if (timer) {        clearInterval(timer);        timer = null;      }    });  }

在 Vue 组件中,能够用过$on,$once 去监听所有的生命周期钩子函数,如监听组件的 updated 钩子函数能够写成 this.$on('hook:updated', () => {})。

hook 除了下面的使用外,还能够内部监听组件的生命周期函数。在某些状况下,咱们须要在父组件中理解一个子组件何时被创立、挂载或更新。

比方,如果你要在第三方组件 CustomSelect 渲染时监听其 updated 钩子,能够通过@hook:updated来实现:

<template>  <!--通过@hook:updated监听组件的updated生命钩子函数-->  <!--组件的所有生命周期钩子都能够通过@hook:钩子函数名 来监听触发-->  <custom-select @hook:updated="doSomething" /></template><script>import CustomSelect from "../components/custom-select";export default {  components: {    CustomSelect  },  methods: {    doSomething() {      console.log("custom-select组件的updated钩子函数被触发");    }  }};</script>

简略暴力的 router key

咱们在我的项目开发时,可能会遇到这样问题:当页面切换到同一个路由但不同参数地址时,比方/detail/1,跳转到/detail/2,页面跳转后数据居然没更新?路由配置如下:

 {     path: "/detail/:id",     name:"detail",     component: Detail }

这是因为 vue-router 会辨认出两个路由应用的是同一个组件从而进行复用,并不会从新创立组件,而且组件的生命周期钩子天然也不会被触发,导致跳转后数据没有更新。那咱们如何解决这个问题呢?
咱们能够为 router-view 组件增加属性 key,例子如下:

<router-view :key="$route.fullpath"></router-view>

这种方法次要是利用虚构 DOM 在渲染时候通过 key 来比照两个节点是否雷同,如果 key 不雷同,就会断定 router-view 组件是一个新节点,从而先销毁组件,而后再从新创立新组件,这样组件内的生命周期会从新触发。

高精度权限管制--自定义指令 directive

咱们通常给一个元素增加 v-if / v-show,来判断该用户是否有权限,但如果判断条件繁琐且多个中央须要判断,这种形式的代码不仅不优雅而且冗余。针对这种状况,咱们能够封装了一个指令权限,能简略疾速的实现按钮级别的权限判断。
咱们先在新建个 array.js 文件,用于寄存与权限相干的全局函数

// array.jsexport function checkArray(key) {  let arr = ['admin', 'editor']  let index = arr.indexOf(key)  if (index > -1) {    return true // 有权限  } else {    return false // 无权限  }}

而后在将 array 文件挂载到全局中

// main.jsimport { checkArray } from './common/array'Vue.config.productionTip = falseVue.directive('permission', {  inserted(el, binding) {    let permission = binding.value // 获取到 v-permission的值    if (permission) {      let hasPermission = checkArray(permission)      if (!hasPermission) {        // 没有权限 移除Dom元素        el.parentNode && el.parentNode.removeChild(el)      }    }  },})

最初咱们在页面中就能够通过自定义指令 v-permission 来判断:

<div class="btns">  <button v-permission="'admin'">权限按钮1</button> // 会显示  <button v-permission="'visitor'">权限按钮2</button> //无显示  <button v-permission="'editor'">权限按钮3</button> // 会显示</div>

动静指令参数

Vue 2.6 的最酷性能之一是能够将指令参数动静传递给组件。咱们能够用方括号括起来的 JavaScript 表达式作为一个指令的参数:

<a v-bind:[attributeName]="url"> 这是个链接 </a>

这里的 attributeName 会被作为一个 JavaScript 表达式进行动静求值,求得的值将会作为最终的参数来应用。
同样地,你能够应用动静参数为一个动静的事件名绑定处理函数:

<a v-on:[eventName]="doSomething"> 这是个链接 </a>

接下来咱们看个例子:假如你有一个按钮,在某些状况下想监听单击事件,在某些状况下想监听双击事件。这时动静指令参数派上用场:

<template>  <div>    <aButton @[someEvent]="handleSomeEvent()" />  </div></template><script>export default {  data () {    return {      someEvent: someCondition ? "click" : "dbclick"    }  },  methods: {    handleSomeEvent () {      // handle some event    }  }}</script>

过滤器让数据处理更便当

Vue.js 容许你自定义过滤器,它的用法其实是很简略,然而可能有些敌人没有用过,接下来咱们介绍下:

1.了解过滤器

  • 性能:对要显示的数据进行特定格式化后再显示
  • 留神:过滤器并没有扭转本来的数据,须要对展示的数据进行包装
  • 应用场景:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始反对)。

2.定义过滤器

能够在一个组件的选项中定义本地的过滤器:

filters: {  capitalize: function (value) {    if (!value) return ''    value = value.toString()    return value.charAt(0).toUpperCase() + value.slice(1)  }}

也能够在创立 Vue 实例之前全局定义过滤器:

Vue.filter('capitalize', function (value) {  if (!value) return ''  value = value.toString()  return value.charAt(0).toUpperCase() + value.slice(1)})

3.应用过滤器

应用办法也简略,即在双花括号中应用管道符(pipeline) |隔开

<!-- 在双花括号中 --><div>{{ myData| filterName}}</div><div>{{ myData| filterName(arg)}}</div><!-- 在 v-bind 中 --><div v-bind:id="rawId | formatId"></div>

过滤器能够串联:

{{ message | filterA | filterB }}

在这个例子中,filterA 被定义为接管单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB,将 filterA 的后果传递到 filterB 中。
接下来咱们看个如何应用过滤器格式化日期的例子:

 <div>    <h2>显示格式化的日期工夫</h2>    <p>{{ date }}</p>    <p>{{ date | filterDate }}</p>    <p>年月日: {{ date | filterDate("YYYY-MM-DD") }}</p> </div> ......  filters: {    filterDate(value, format = "YYYY-MM-DD HH:mm:ss") {      console.log(this)//undefined 过滤器没有this指向的      return moment(value).format(format);    }  },  data() {    return {      date: new Date()    };  }

参考文章与书籍

  • Vue 官网文档
  • Vue2.6 查漏补缺
  • Vue.js 最佳实际(五招让你成为 Vue.js 巨匠)
  • 实战技巧,Vue 原来还能够这样写
  • 应用 Vue.observable()进行状态治理
  • Vue 我的项目构建与开发入门
  • 2020 年的 12 个 Vue.js 开发技巧和诀窍