使人眼前一亮的 Vue 實戰技巧

點擊跳轉我的博客查看往期文章javascript

前言

本文主要介紹平常項目開發過程當中的一些技巧,不只能夠幫助提高工做效率,還能提升應用的性能。如下是我總結一些平時工做中的經驗。html

minxin 讓組件複用靈活化

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

拯救繁亂的 template--render 函數

  • template 裏存在一值多判斷
  • 代碼冗親,代碼雜亂

舉一個官方文檔的例子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)

最後咱們就能夠隨時隨地在頁面中使用這些高頻組件,無需再手動一個個引入了。

隱藏的大招--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.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 容許你自定義過濾器,它的用法實際上是很簡單,可是可能有些朋友沒有用過,接下來咱們介紹下:

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()
    };
  }

參考文章與書籍

相關文章
相關標籤/搜索