点击跳转我的博客查看往期文章javascript
本文主要介绍平常项目开发过程当中的一些技巧,不只能够帮助提高工做效率,还能提升应用的性能。如下是我总结一些平时工做中的经验。html
Vue 提供了 minxin 这种在组件内插入组件属性的方法,我的建议这货能少用就少用,可是有个场景则很是建议使用 minxin:当某段代码重复出如今多个组件中,而且这个重复的代码块很大的时候,将其做为一个 minxin 经常能给后期的维护带来很大的方便。vue
这是项目中封装一个列表功能,有下拉刷新,加载自动请求数据,上拉加载下一页数据等等,它是这样的java
看不懂不要紧我只是开发中举了一个例子node
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('列表请求完成'); }, }, }
不少人看到着应该很好奇为何不封装成一个组件,可是因为不少列表样式不尽相同,因此封装成一个组件可扩展性不高。
如今咱们能够这样使用。react
<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>
咱们只要定义请求参数和请求的地址,还有列表的样式,就能实现一个不错的列表功能。webpack
举一个官方文档的例子git
<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 函数重写上面的例子:web
<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.js import index from './components/global.js' Vue.use(index)
最后咱们就能够随时随地在页面中使用这些高频组件,无需再手动一个个引入了。
开发过程当中咱们有时候要建立一个定时器,在组件被销毁以前,这个定时器也要销毁。代码以下:
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>
咱们在项目开发时,可能会遇到这样问题:当页面切换到同一个路由但不一样参数地址时,好比/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 组件是一个新节点,从而先销毁组件,而后再从新建立新组件,这样组件内的生命周期会从新触发。
咱们一般给一个元素添加 v-if / v-show,来判断该用户是否有权限,但若是判断条件繁琐且多个地方须要判断,这种方式的代码不只不优雅并且冗余。针对这种状况,咱们能够封装了一个指令权限,能简单快速的实现按钮级别的权限判断。
咱们先在新建个 array.js 文件,用于存放与权限相关的全局函数
// array.js export function checkArray(key) { let arr = ['admin', 'editor'] let index = arr.indexOf(key) if (index > -1) { return true // 有权限 } else { return false // 无权限 } }
而后在将 array 文件挂载到全局中
// main.js import { checkArray } from './common/array' Vue.config.productionTip = false Vue.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 容许你自定义过滤器,它的用法实际上是很简单,可是可能有些朋友没有用过,接下来咱们介绍下:
能够在一个组件的选项中定义本地的过滤器:
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) })
使用方法也简单,即在双花括号中使用管道符(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() }; }