點擊跳轉我的博客查看往期文章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() }; }