令人眼前一亮的 Vue 实战技巧

开发 前端
本文主要介绍日常项目开发过程中的一些技巧,不仅可以帮助提升工作效率,还能提高应用的性能。以下是我总结一些平时工作中的技巧。

[[418041]]

 前言

本文主要介绍日常项目开发过程中的一些技巧,不仅可以帮助提升工作效率,还能提高应用的性能。以下是我总结一些平时工作中的技巧。

minxin 让组件复用灵活化

Vue提供了minxin这种在组件内插入组件属性的方法,个人建议这货能少用就少用,但是有个场景则非常建议使用minxin:当某段代码重复出现在多个组件中,并且这个重复的代码块很大的时候,将其作为一个minxin常常能给后期的维护带来很大的方便。

比如我们在项目中封装一个列表功能,有下拉刷新,加载自动请求数据,上拉加载下一页数据等等,代码如下: 

  1. export default {  
  2.     data() {  
  3.         return {  
  4.             page: 1,  
  5.             limit: 10,  
  6.             busy: false, // 请求拦截,防止多次加载  
  7.             finish: false, // 是否请求完成,用于页面展示效果  
  8.             pageList: [], // 页面数据  
  9.             reqParams: {}, // 页面请求参数,可被改变的  
  10.             defaultParams: {}, // 页面请求参数,下拉刷新不会被重置的改变  
  11.             routeName: '', // 特殊情况,页面需要复用别人的list的时候  
  12.             autoReq: true, // onload是否自己去请求  
  13.             lodingText: '', // 请求中底部显示的文案  
  14.             noDataText: '暂无数据', // 自定义无数据文案  
  15.             lastText: '- 我是有底线的 -',  
  16.             noData: false, // 页面无数据  
  17.             reqName: ''  
  18.         }  
  19.     },  
  20.     created() {  
  21.         this.autoReq && this.initPage(false, true) 
  22.     },  
  23.     onPullDownRefresh() {  
  24.         this.pullDownRefreshFn()  
  25.     },  
  26.     onReachBottom() {  
  27.         this.reachBottomFn()  
  28.     },  
  29.     methods: {  
  30.         // 重置初始化数据  
  31.         initPage(saveParams = truerefresh = false) {  
  32.             // 初始化所有变量  
  33.             this.page = 1  
  34.             this.busy = false  
  35.             this.finish = false  
  36.             this.noData = false  
  37.             this.lodingText = '数据加载中'  
  38.             if (saveParams) {  
  39.                 const { page, limit } = this.reqParams  
  40.                 page ? this.page = page : ''  
  41.                 limit ? this.limit = limit : ''  
  42.             } else { 
  43.                 this.reqParams = {}  
  44.             }  
  45.             this.getCommonList(refresh)  
  46.         },  
  47.         // 下拉刷新函数  
  48.         pullDownRefreshFn() {  
  49.             this.initData()  
  50.             this.initPage(false, true)  
  51.         },  
  52.         // 上啦加载函数  
  53.         reachBottomFn() {  
  54.             this.getCommonList()  
  55.         },  
  56.         // 重置数据,方便调用(一般在外面自定义清空一些数据)  
  57.         initData() { // 重置data里面的变量,方便外面引用这个mixin的时候,下拉刷新重置变量  
  58.         },  
  59.         // 列表获取数据接口  
  60.         async getCommonList(refresh) {  
  61.             if (!this.reqName) return  
  62.             if (this.busy) return  
  63.             this.busy = true  
  64.             this.finish = false  
  65.             const httpFn = this.$http || getApp().globalData.$http// 兼容nvue  
  66.             try {  
  67.                 const query = {  
  68.                     ...this.defaultParams,  
  69.                     ...this.reqParams,  
  70.                     page: this.page,  
  71.                     limit: this.limit  
  72.                 }  
  73.                 const { data } = await httpFn(this.reqName, query)  
  74.                 if (this.page === 1) this.pageList = []  
  75.                 /**  
  76.                  * [Node.JS中用concat和push连接两个或多个数组的性能比较](http://ourjs.com/detail/5cb3fe1c44b4031138b4a1e2)  
  77.                  * 那么两者在node.js的性能如何? 我们做了一组测试数据,两种分别测试100万次。  
  78.                  * push比concat方法快3倍左右。因为push只是在原数组的基础上进行修改,所以会快一点。  
  79.                  * push返回的是数组的长度,所以没重新定义变量再判断了  
  80.                  * [Array.prototype.push.apply(arr1, arr2)无法自动触发DOM更新](https://www.imooc.com/wenda/detail/494323)  
  81.                  * 因为 this.pageList.push !== Array.prototype.push,,this.pageList.push指向的是vue重写过的方法  
  82.                  */  
  83.                 this.finish = true  
  84.                 const resLen = data.list ? data.list.length : 0  
  85.                 if (resLen === 0) {  
  86.                     this.resSuccess(data, refresh)  
  87.                     return  
  88.                 }  
  89.                 const listLen = this.pageList.push.apply(this.pageList, data.list)  
  90.                 if (listLen < data.count && this.limit <= resLen) { // 说明还有数据  
  91.                     this.busy = false  
  92.                     this.page = Math.ceil(listLen / this.limit) + 1  
  93.                 }  
  94.                 this.resSuccess(data, refresh)  
  95.             } catch (e) {  
  96.                 // 防止接口报错锁死  
  97.                 this.busy = false  
  98.                 this.finish = true  
  99.             }  
  100.         },  
  101.         resSuccess(data, refresh) {  
  102.             if (this.finish && this.busy) {  
  103.                 if (this.pageList.length > 0) {  
  104.                     this.$nextTick(() => {  
  105.                         setTimeout(() => {  
  106.                             thisthis.lodingText = this.lastText  
  107.                         }, 100)  
  108.                     })  
  109.                 } else {  
  110.                     thisthis.lodingText = this.noDataText  
  111.                     this.noData = true  
  112.                 }  
  113.             }  
  114.             refresh && uni.stopPullDownRefresh()  
  115.             this.finishInit(data)  
  116.         },  
  117.         // 请求完成做点什么(方便外面导入的文件自己引用)  
  118.         finishInit(data) { // 请求完成做点什么  
  119.             // console.log('列表请求完成');  
  120.         }  
  121.     } 

很多人看到这样场景,应该会好奇为什么不封装成一个组件?由于很多列表样式不尽相同,所以封装成一个组件可扩展性不高。但我们可以通过minxin来简化代码: 

  1. <template>  
  2.   <view class="c-recommend-goods">  
  3.     <!-- 列表样式 -->  
  4.     <view class="" v-for="item in pageList" :key="item.id">{{item}}</view>  
  5.     <!-- 空状态&& 加载中等小提示 -->  
  6.     <c-no-data v-if="lodingText" :show-img="noData" :text="lodingText"></c-no-data>  
  7.   </view>  
  8. </template>  
  9. <script>  
  10. import listMixins from '@/common/mixins/list.js'  
  11. export default {  
  12.   mixins: [listMixins],  
  13.   data() {  
  14.     return { 
  15.       autoReq: false, // 进入页面自动请求数据  
  16.       reqParams: {}, // 请求参数  
  17.       reqName: 'userCompanyList' // 请求地址  
  18.     }  
  19.   }  
  20.  
  21. </script>  
  22. <style></style> 

我们只要定义请求参数和请求的地址,还有列表的样式,就能实现一个不错的列表功能。

拯救繁乱的template--render函数

有时候项目中template里存在一值多判断,如果按照下方代码书写业务逻辑,代码冗余且杂乱。 

  1. <template>  
  2.   <div>  
  3.     <h1 v-if="level === 1">  
  4.       <slot></slot>  
  5.     </h1>  
  6.     <h2 v-else-if="level === 2">  
  7.       <slot></slot>  
  8.     </h2>  
  9.     <h3 v-else-if="level === 3">  
  10.       <slot></slot>  
  11.     </h3>  
  12.     <h4 v-else-if="level === 4">  
  13.       <slot></slot>  
  14.     </h4>  
  15.     <h5 v-else-if="level === 5">  
  16.       <slot></slot>  
  17.     </h5>  
  18.     <h6 v-else-if="level === 6">  
  19.       <slot></slot>  
  20.     </h6>  
  21.   </div>  
  22. </template>  
  23. <script>  
  24. export default {  
  25.   data() {  
  26.     return {}  
  27.   },  
  28.   props: {  
  29.     level: {  
  30.       type: Number,  
  31.       required: true,  
  32.     },  
  33.   },  
  34.  
  35. </script> 

现在使用 render 函数重写上面的例子: 

  1. <script>  
  2.   export default {  
  3.     props: {  
  4.       level: {  
  5.         require: true,  
  6.         type: Number,  
  7.       }  
  8.     },  
  9.     render(createElement) {  
  10.       return createElement('h' + this.level, this.$slots.default);  
  11.     }  
  12.   };  
  13. </script> 

一劳永逸的组件注册

组件使用前,需要引入后再注册: 

  1. import BaseButton from './baseButton'  
  2. import BaseIcon from './baseIcon'  
  3. import BaseInput from './baseInput'  
  4. export default {  
  5.   components: {  
  6.     BaseButton,  
  7.     BaseIcon,  
  8.     BaseInput  
  9.   }  

现在 BaseButton、 BaseIcon和BaseInput都可以在模板中使用了: 

  1. <BaseInput  
  2.   v-model="searchText"  
  3.   @keydown.enter="search"  
  4. />  
  5. <BaseButton @click="search">  
  6.   <BaseIcon name="search"/>  
  7. </BaseButton> 

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

这时,我们需要借助一下webpack的require.context() 方法来创建自己的(模块)上下文,从而实现自动动态require组件。这个方法需要3个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。

我们先在components文件夹(这里面都是些高频组件)添加一个叫global.js的文件,在这个文件里使用require.context 动态将需要的高频组件统统打包进来。然后在main.js文件中引入global.js的文件。 

  1. //  global.js文件  
  2. import Vue from 'vue'  
  3. function changeStr (str) {  
  4.   return str.charAt(0).toUpperCase() + str.slice(1)  
  5.  
  6. const requirerequireComponent = require.context('./', false, /\.vue$/)  
  7. // 查找同级目录下以vue结尾的组件  
  8. const install = () => {  
  9.   requireComponent.keys().forEach(fileName => {  
  10.     let config = requireComponent(fileName)  
  11.     console.log(config) // ./child1.vue 然后用正则拿到child1  
  12.     let componentName = changeStr 
  13.       fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')  
  14.     ) 
  15.     Vue.component(componentName, config.default || config)  
  16.   }) 
  17.  
  18. export default {  
  19.   install // 对外暴露install方法 
  20.  
  21. // main.js  
  22. import index from './components/global.js'  
  23. Vue.use(index) 

最后我们就可以随时随地在页面中使用这些高频组件,无需再手动一个个引入了。

隐藏的大招--hook

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

  1. mounted() {  
  2.   // 创建一个定时器  
  3.     this.timer = setInterval(() => {  
  4.       // ......  
  5.     }, 500);  
  6.   },  
  7.   // 销毁这个定时器。  
  8.   beforeDestroy() {  
  9.     if (this.timer) {  
  10.       clearInterval(this.timer);  
  11.       this.timer = null 
  12.     }  
  13.   } 

这种写法有个很明显的弊端:定时器timer的创建和清理并不是在一个地方,这样很容易导致忘记去清理!

我们可以借助hook对代码整合,这样代码也更容易维护了: 

  1. mounted() {  
  2.     let timer = setInterval(() => {  
  3.       // ......  
  4.     }, 500); 
  5.     this.$once("hook:beforeDestroy", function() {  
  6.       if (timer) {  
  7.         clearInterval(timer);  
  8.         timer = null 
  9.       }  
  10.     });  
  11.   } 

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

hook除了上面的运用外,还可以外部监听组件的生命周期函数。在某些情况下,我们需要在父组件中了解一个子组件何时被创建、挂载或更新。

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

  1. <template>  
  2.   <!--通过@hook:updated监听组件的updated生命钩子函数-->  
  3.   <!--组件的所有生命周期钩子都可以通过@hook:钩子函数名 来监听触发-->  
  4.   <custom-select @hook:updated="doSomething" />  
  5. </template>  
  6. <script>  
  7. import CustomSelect from "../components/custom-select";  
  8. export default {  
  9.   components: {  
  10.     CustomSelect  
  11.   },  
  12.   methods: {  
  13.     doSomething() {  
  14.       console.log("custom-select组件的updated钩子函数被触发");  
  15.     }  
  16.   }  
  17. }; 
  18. </script> 

简单暴力的router key

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

  1.  
  2.     path: "/detail/:id",  
  3.     name:"detail",  
  4.     component: Detail 
  5.  

这是因为vue-router会识别出两个路由使用的是同一个组件从而进行复用,并不会重新创建组件,而且组件的生命周期钩子自然也不会被触发,导致跳转后数据没有更新。那我们如何解决这个问题呢?我们可以为router-view组件添加属性key,例子如下: 

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

这种办法主要是利用虚拟DOM在渲染时候通过key来对比两个节点是否相同,如果key不相同,就会判定router-view组件是一个新节点,从而先销毁组件,然后再重新创建新组件,这样组件内的生命周期会重新触发。

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

我们通常给一个元素添加v-if / v-show,来判断该用户是否有权限,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以封装了一个指令权限,能简单快速的实现按钮级别的权限判断。我们先在新建个array.js文件,用于存放与权限相关的全局函数 

  1. // array.js  
  2. export function checkArray (key) {  
  3.   let arr = ['admin', 'editor']  
  4.   let index = arr.indexOf(key)  
  5.   if (index > -1) {  
  6.     return true // 有权限  
  7.   } else {  
  8.     return false // 无权限  
  9.   }  

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

  1. // main.js  
  2. import { checkArray } from "./common/array";  
  3. Vue.config.productionTip = false 
  4. Vue.directive("permission", {  
  5.   inserted (el, binding) {  
  6.     let permission = binding.value; // 获取到 v-permission的值  
  7.     if (permission) {  
  8.       let hasPermission = checkArray(permission);  
  9.       if (!hasPermission) { // 没有权限 移除Dom元素  
  10.         el.parentNode && el.parentNode.removeChild(el);  
  11.       }  
  12.     }  
  13.   }  
  14. }); 

最后我们在页面中就可以通过自定义指令 v-permission来判断: 

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

图片

动态指令参数

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

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

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

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

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

  1. <template>  
  2.   <div>  
  3.     <aButton @[someEvent]="handleSomeEvent()" />  
  4.   </div>  
  5. </template>  
  6. <script>  
  7. export default {  
  8.   data () {  
  9.     return {  
  10.       someEvent: someCondition ? "click" : "dbclick"  
  11.     }  
  12.   },  
  13.   methods: {  
  14.     handleSomeEvent () {  
  15.       // handle some event  
  16.     }  
  17.   }  
  18.  
  19. </script> 

过滤器让数据处理更便利

Vue.js 允许你自定义过滤器,它的用法其实是很简单,但是可能有些朋友没有用过,接下来我们介绍下:

1.理解过滤器

  •  功能:对要显示的数据进行特定格式化后再显示。
  •  注意:过滤器并没有改变原本的数据,需要对展现的数据进行包装。
  •  使用场景:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。

2.定义过滤器

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

  1. filters: {  
  2.   capitalize: function (value) {  
  3.     if (!value) return ''  
  4.     valuevalue = value.toString()  
  5.     return value.charAt(0).toUpperCase() + value.slice(1)  
  6.   } 

也可以在创建 Vue 实例之前全局定义过滤器: 

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

3.使用过滤器

使用方法也简单,即在双花括号中使用管道符(pipeline) |隔开 

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

过滤器可以串联: 

  1. {{ message | filterA | filterB }} 

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

  1. <div>  
  2.    <h2>显示格式化的日期时间</h2>  
  3.    <p>{{ date }}</p>  
  4.    <p>{{ date | filterDate }}</p>  
  5.    <p>年月日: {{ date | filterDate("YYYY-MM-DD") }}</p>  
  6. </div>  
  7. ......  
  8.  filters: {  
  9.    filterDate(value, format = "YYYY-MM-DD HH:mm:ss") {  
  10.      console.log(this)//undefined 过滤器没有this指向的  
  11.      return moment(value).format(format);  
  12.    }  
  13.  },  
  14.  data() {  
  15.    return {  
  16.      date: new Date()  
  17.    };  
  18.  }  

 

责任编辑:庞桂玉 来源: 前端大全
相关推荐

2017-03-06 18:35:22

VRAR应用

2023-07-16 22:37:46

JavaScript代码任务

2024-03-14 17:41:25

AIGC人工智能应用

2022-07-28 15:46:08

Linux工具

2022-12-19 08:23:24

2022-12-09 09:39:20

Vue3Vue2

2021-06-30 09:56:24

MySQL数据库索引

2023-08-10 08:16:41

Hash技术哈希表

2022-02-28 23:37:16

iOS苹果系统

2022-04-02 21:36:23

iOS苹果风格

2009-08-06 09:37:09

Silverlight

2021-07-14 23:57:26

Vue高级技巧

2024-01-03 15:59:56

Linux发行版

2013-10-14 17:38:56

2017-09-11 08:35:15

PythonGUIIT

2020-06-17 09:15:57

MySQLSQL数据库

2016-01-21 10:55:04

2020-11-23 10:02:04

开发者
点赞
收藏

51CTO技术栈公众号