前方高能,這是最新的一波Vue實戰技巧,不用則已,一用驚人

葡萄美酒夜光杯,欲飲琵琶產品催。 客戶現場君莫笑,古來埋坑幾人回?vue

最近一直在開發後臺管理系統,日復一日的重複着表單表格表格表單,標準的CV仔,感受好無聊,如何能在這種無聊的開發過程當中去提高本身,小編今天又整理了一波新的Vue實戰技巧,這些技巧,不用則已,一用驚人。同時你也能夠點擊下面的連接閱讀近期小編的文章。面試

實戰技巧,Vue原來還能夠這樣寫 獲贊 1700+ajax

絕對乾貨~!學會這些Vue小技巧,能夠早點下班和女神約會了 獲贊 970+vuex

看到賺到!重讀vue2.0風格指南,我整理了這些關鍵規則 獲贊 120+element-ui

插槽,我要鑽到你的懷裏

插槽,相信每一位Vue都有使用過,可是如何更好的去理解插槽,如何去自定義插槽,今天小編爲你帶來更形象的說明。後端

默認插槽

大學畢業剛上班,窮鬼一個,想着每月租房還要掏房租,因此小編決定買一個一居室,東拼西湊借了一堆債,終於湊夠了首付,買了一個小小的毛坯房。咱們能夠把這個一居室的毛坯房想一想成一個組件,這個房子的戶型,面積,樓層都是固定的,可是室內如何裝修,擺什麼傢俱,這個倒是由你來決定的,房間內部就能夠理解爲插槽,容許用戶去自定義內容。api

1. 開發商終於將一居室開發完交房了

<template>
 <!--這是一個一居室-->  <div class="one-bedroom">  <!--添加一個默認插槽,用戶能夠在外部隨意定義這個一居室的內容-->  <slot></slot>  </div> </template> 複製代碼

2. 小編要開始裝修了

<template>
 <!--這裏一居室-->  <one-bedroom>  <!--將傢俱放到房間裏面,組件內部就是上面提供的默認插槽的空間-->  <span>先放一個小牀,反正沒有女友</span>  <span>再放一個電腦桌,在家還要加班寫bug</span>  </one-bedroom> </template> <script> import OneBedroom from '../components/one-bedroom' export default {  components: {  OneBedroom  } } </script>  複製代碼

具名插槽

過了幾年,小編有了女友,準備結婚了,一居室房間確定不行啊,丈母孃嫌小不一樣意,沒辦法,只能又湊錢買大房子,買了一個兩居室(窮逼一個),由於是兩居室,因此有了主臥和次臥之分,裝修是否也不能把主臥和次臥裝修的如出一轍,因此就須要進行區分。將房子想一想成組件,那麼組件就有兩個插槽,而且須要起名字進行區分。數組

1. 開發商終於開發完交房了

<template>
 <div class="two-bedroom">  <!--這是主臥-->  <div class="master-bedroom">  <!---主臥使用默認插槽-->  <slot></slot>  </div>  <!--這是次臥-->  <div class="secondary-bedroom">  <!--次臥使用具名插槽-->  <slot name="secondard"></slot>  </div>  </div> </template>  複製代碼

2. 小編要賣血攢錢裝修了

<template>
 <two-bedroom>  <!--主臥使用默認插槽-->  <div>  <span>放一個大牀,要結婚了,嘿嘿嘿</span>  <span>放一個衣櫃,老婆的衣服太多了</span>  <span>算了,仍是放一個電腦桌吧,還要寫bug</span>  </div>  <!--次臥,經過v-slot:secondard 能夠指定使用哪個具名插槽, v-slot:secondard 也能夠簡寫爲 #secondard-->  <template v-slot:secondard>  <div>  <span>父母要住,放一個硬一點的牀,軟牀對腰很差</span>  <span>放一個衣櫃</span>  </div>  </template>  </two-bedroom> </template> <script> import TwoBedroom from '../components/slot/two-bedroom' export default {  components: {  TwoBedroom  } } </script>  複製代碼

做用域插槽

裝修的時候,裝修師傅問我洗衣機是要放到衛生間仍是陽臺,通常狀況下開發商會預留放洗衣機的位置。而這個位置能夠理解爲插槽傳的參數,這個就是做用域插槽。瀏覽器

1. 看一下衛生間插槽傳了什麼參數

<template>
 <div class="two-bedroom">  <!--其餘內容省略-->  <div class="toilet">  <!--經過v-bind 能夠向外傳遞參數, 告訴外面衛生間能夠放洗衣機-->  <slot name="toilet" v-bind="{ washer: true }"></slot>  </div>  </div> </template>  複製代碼

2. 把洗衣機放到衛生間

<template>
 <two-bedroom>  <!--其餘省略-->  <!--衛生間插槽,經過v-slot="scope"能夠獲取組件內部經過v-bind傳的值-->  <template v-slot:toilet="scope">  <!--判斷是否能夠放洗衣機-->  <span v-if="scope.washer">這裏放洗衣機</span>  </template>  </two-bedroom> </template> 複製代碼

插槽默認值

小編的同事不想等期房,因此就買了二手房,二手房前業主都裝修好了,能夠直接入住。固然也能夠從新裝修,下面是同事買的二手房。markdown

1. 這是裝修好的二手房

<template>
 <div class="second-hand-house">  <div class="master-bedroom">  <!--插槽能夠指定默認值,若是外部調用組件時沒有修改插槽內容,則使用默認插槽-->  <slot>  <span>這裏有一張水牀,玩的夠嗨</span>  <span>還有一個衣櫃,有點舊了</span>  </slot>  </div>  <!--這是次臥-->  <div class="secondary-bedroom">  <!--次臥使用具名插槽-->  <slot name="secondard">  <span>這裏有一張嬰兒牀</span>  </slot>  </div>  </div> </template>  複製代碼

2. 同事決定先把主臥裝修了,之後結婚用

<second-hand-house>
 <!--主臥使用默認插槽,只裝修主臥-->  <div>  <span>放一個大牀,要結婚了,嘿嘿嘿</span>  <span>放一個衣櫃,老婆的衣服太多了</span>  <span>算了,仍是放一個電腦桌吧,還要寫bug</span>  </div>  </second-hand-house> 複製代碼

瞭解選項合併策略,自定義生命週期鉤子函數

當你使用Vuemixins的時候,是否有發現,若是混入的methods裏面的方法與組件的方法同名,則會被組件方法覆蓋,可是生命週期函數若是重名,混入的與組件自身的都會被執行,且執行順序是先混入和自身,這是怎麼作到的呢?

1. 瞭解Vue合併策略

Vue中,不一樣的選項有不一樣的合併策略,好比 data,props,methods是同名屬性覆蓋合併,其餘直接合並,而生命週期鉤子函數則是將同名的函數放到一個數組中,在調用的時候依次調用,具體可參考小編前面的一篇文章絕對乾貨~!學會這些Vue小技巧,能夠早點下班和女神約會了

Vue中,提供了一個api, Vue.config.optionMergeStrategies,能夠經過這個api去自定義選項的合併策略。

在代碼中打印

console.log(Vue.config.optionMergeStrategies)
複製代碼
控制檯打印內容
控制檯打印內容

經過上圖能夠看到Vue全部選項的合併策略函數,咱們能夠經過覆蓋上面的方法,來自定義合併策略函數,不過通常用不到。

2. 經過合併策略自定義生命週期函數

背景

最近客戶給領導反饋,咱們的系統用一段時間,瀏覽器就變得有點卡,不知道爲何。問題出來了,原本想甩鍋到後端,可是瀏覽器問題,無法甩鍋啊,那就排查吧。

後來發現頁面有許多定時器,ajax輪詢還有動畫,打開一個瀏覽器頁籤無法問題,打開多了,瀏覽器就變得卡了,這時候我就想若是能在用戶切換頁籤時候將這些都停掉,不久解決了。百度裏面上下檢索,找到了一個事件visibilitychange,能夠用來判斷瀏覽器頁籤是否顯示。

有方法了,就寫唄

export default {
 created() {  window.addEventListener('visibilitychange', this.$_hanldeVisiblityChange)  // 此處用了hookEvent,能夠參考小編前一篇文章  this.$on('hook:beforeDestroy', () => {  window.removeEventListener(  'visibilitychange',  this.$_hanldeVisiblityChange  )  })  },  methods: {  $_hanldeVisiblityChange() {  if (document.visibilityState === 'hidden') {  // 停掉那一堆東西  }  if (document.visibilityState === 'visible') {  // 開啓那一堆東西  }  }  } } 複製代碼

經過上面的代碼,能夠看到在每個須要監聽處理的文件都要寫一堆事件監聽,判斷頁面是否顯示的代碼,一處兩處還能夠,文件多了就頭疼了,這時候小編突發奇想,定義一個頁面顯示隱藏的生命週期鉤子,把這些判斷都封裝起來,哪裏須要點哪裏,so easy(點讀機記得廣告費)。

自定義生命週期鉤子函數

定義生命週期函數 pageHiddenpageVisible

import Vue from 'vue'
 // 通知全部組件頁面狀態發生了變化 const notifyVisibilityChange = (lifeCycleName, vm) => {  // 生命週期函數會存在$options中,經過$options[lifeCycleName]獲取生命週期  const lifeCycles = vm.$options[lifeCycleName]  // 由於使用了created的合併策略,因此是一個數組  if (lifeCycles && lifeCycles.length) {  // 遍歷 lifeCycleName對應的生命週期函數列表,依次執行  lifeCycles.forEach(lifecycle => {  lifecycle.call(vm)  })  }  // 遍歷全部的子組件,而後依次遞歸執行  if (vm.$children && vm.$children.length) {  vm.$children.forEach(child => {  notifyVisibilityChange(lifeCycleName, child)  })  } }  /**  * 添加生命週期鉤子函數  * @param {*} rootVm vue 根實例,在頁面顯示隱藏時候,經過root向下通知  */ export function init() {  const optionMergeStrategies = Vue.config.optionMergeStrategies  /*  定義了兩個生命週期函數 pageVisible, pageHidden  爲何要賦值爲 optionMergeStrategies.created呢  這個至關於指定 pageVisible, pageHidden 的合併策略與 created的相同(其餘生命週期函數都同樣)  */  optionMergeStrategies.pageVisible = optionMergeStrategies.beforeCreate  optionMergeStrategies.pageHidden = optionMergeStrategies.created }  /**  * 將事件變化綁定到根節點上面  * @param {*} rootVm  */ export function bind(rootVm) {  window.addEventListener('visibilitychange', () => {  // 判斷調用哪一個生命週期函數  let lifeCycleName = undefined  if (document.visibilityState === 'hidden') {  lifeCycleName = 'pageHidden'  } else if (document.visibilityState === 'visible') {  lifeCycleName = 'pageVisible'  }  if (lifeCycleName) {  // 經過全部組件生命週期發生變化了  notifyVisibilityChange(lifeCycleName, rootVm)  }  }) }  複製代碼

應用

  1. main.js主入口文件引入
import { init, bind } from './utils/custom-life-cycle'
 // 初始化生命週期函數, 必須在Vue實例化以前肯定合併策略 init()  const vm = new Vue({  router,  render: h => h(App) }).$mount('#app')  // 將rootVm 綁定到生命週期函數監聽裏面 bind(vm)  複製代碼
  1. 在須要的地方監聽生命週期函數
export default {
 pageVisible() {  console.log('頁面顯示出來了')  },  pageHidden() {  console.log('頁面隱藏了')  } } 複製代碼

provideinject,不止父子傳值,祖宗傳值也能夠

Vue相關的面試常常會被面試官問道,Vue父子之間傳值的方式有哪些,一般咱們會回答,props傳值,$emit事件傳值,vuex傳值,還有eventbus傳值等等,今天再加一種provideinject傳值,離offer又近了一步。(對了,下一節還有一種)

使用過React的同窗都知道,在React中有一個上下文Context,組件能夠經過Context向任意後代傳值,而Vueprovideinject的做用於Context的做用基本同樣

先舉一個例子

使用過elemment-ui的同窗必定對下面的代碼感到熟悉

<template>
 <el-form :model="formData" size="small">  <el-form-item label="姓名" prop="name">  <el-input v-model="formData.name" />  </el-form-item>  <el-form-item label="年齡" prop="age">  <el-input-number v-model="formData.age" />  </el-form-item>  <el-button>提交</el-button>  </el-form> </template> <script> export default {  data() {  return {  formData: {  name: '',  age: 0  }  }  } } </script>  複製代碼

看了上面的代碼,貌似沒啥特殊的,每天寫啊。在el-form上面咱們指定了一個屬性size="small",而後有沒有發現表單裏面的全部表單元素以及按鈕的 size都變成了small,這個是怎麼作到的?接下來咱們本身手寫一個表單模擬一下

本身手寫一個表單

咱們如今模仿element-ui的表單,本身自定義一個,文件目錄以下

自定義表單custom-form.vue

<template>
 <form class="custom-form">  <slot></slot>  </form> </template> <script> export default {  props: {  // 控制表單元素的大小  size: {  type: String,  default: 'default',  // size 只能是下面的四個值  validator(value) {  return ['default', 'large', 'small', 'mini'].includes(value)  }  },  // 控制表單元素的禁用狀態  disabled: {  type: Boolean,  default: false  }  },  // 經過provide將當前表單實例傳遞到全部後代組件中  provide() {  return {  customForm: this  }  } } </script>  複製代碼

在上面代碼中,咱們經過provide將當前組件的實例傳遞到後代組件中,provide是一個函數,函數返回的是一個對象

自定義表單項custom-form-item.vue

沒有什麼特殊的,只是加了一個label,element-ui更復雜一些

<template>
 <div class="custom-form-item">  <label class="custom-form-item__label">{{ label }}</label>  <div class="custom-form-item__content">  <slot></slot>  </div>  </div> </template> <script> export default {  props: {  label: {  type: String,  default: ''  }  } } </script>  複製代碼

自定義輸入框 custom-input.vue

<template>
 <div  class="custom-input"  :class="[  `custom-input--${getSize}`,  getDisabled && `custom-input--disabled`  ]"  >  <input class="custom-input__input" :value="value" @input="$_handleChange" />  </div> </template> <script> /* eslint-disable vue/require-default-prop */ export default {  props: {  // 這裏用了自定義v-model  value: {  type: String,  default: ''  },  size: {  type: String  },  disabled: {  type: Boolean  }  },  // 經過inject 將form組件注入的實例添加進來  inject: ['customForm'],  computed: {  // 經過計算組件獲取組件的size, 若是當前組件傳入,則使用當前組件的,不然是否form組件的  getSize() {  return this.size || this.customForm.size  },  // 組件是否禁用  getDisabled() {  const { disabled } = this  if (disabled !== undefined) {  return disabled  }  return this.customForm.disabled  }  },  methods: {  // 自定義v-model  $_handleChange(e) {  this.$emit('input', e.target.value)  }  } } </script>  複製代碼

form中,咱們經過provide返回了一個對象,在input中,咱們能夠經過inject獲取form中返回對象中的項,如上代碼inject:['customForm']所示,而後就能夠在組件內經過this.customForm調用form實例上面的屬性與方法了

**在上面代碼中咱們使用了自定義v-model,關於自定義v-model能夠閱讀小編前面的文章絕對乾貨~!學會這些Vue小技巧,能夠早點下班和女神約會了 **

在項目中使用

<template>
 <custom-form size="small">  <custom-form-item label="姓名">  <custom-input v-model="formData.name" />  </custom-form-item>  </custom-form> </template> <script> import CustomForm from '../components/custom-form' import CustomFormItem from '../components/custom-form-item' import CustomInput from '../components/custom-input' export default {  components: {  CustomForm,  CustomFormItem,  CustomInput  },  data() {  return {  formData: {  name: '',  age: 0  }  }  } } </script>  複製代碼

執行上面代碼,運行結果爲:

<form class="custom-form">
 <div class="custom-form-item">  <label class="custom-form-item__label">姓名</label>  <div class="custom-form-item__content">  <!--size=small已經添加到指定的位置了-->  <div class="custom-input custom-input--small">  <input class="custom-input__input">  </div>  </div>  </div> </form> 複製代碼

經過上面的代碼能夠看到,input組件已經設置組件樣式爲custom-input--small

inject格式說明

除了上面代碼中所使用的inject:['customForm']寫法以外,inject還能夠是一個對象。且能夠指定默認值

修改上例,若是custom-input外部沒有custom-form,則不會注入customForm,此時爲customForm指定默認值

{
 inject: {  customForm: {  // 對於非原始值,和props同樣,須要提供一個工廠方法  default: () => ({  size: 'default'  })  }  } } 複製代碼

若是咱們但願inject進來的屬性的名字不叫customForm,而是叫parentForm,以下代碼

inject: {
 // 注入的屬性名稱  parentForm: {  // 經過 from 指定從哪一個屬性注入  from: 'customForm',  default: () => ({  size: 'default'  })  }  },  computed: {  // 經過計算組件獲取組件的size, 若是當前組件傳入,則使用當前組件的,不然是否form組件的  getSize() {  return this.size || this.parentForm.size  }  } 複製代碼

使用限制

  1. provideinject的綁定不是可響應式的。可是,若是你傳入的是一個可監聽的對象,如上面的customForm: this,那麼其對象的屬性仍是可響應的。

  2. Vue官網建議provideinject 主要在開發高階插件/組件庫時使用。不推薦用於普通應用程序代碼中。由於provideinject在代碼中是不可追溯的(ctrl + f能夠搜),建議可使用Vuex代替。 可是,也不是說不能用,在局部功能有時候用了做用仍是比較大的。

dispatch 和 broadcast ,這是一種有歷史的組件通訊方式

$dispatch$broadcast是一種有歷史的組件通訊方式,爲何是有歷史的,由於他們是Vue1.0提供的一種方式,在Vue2.0中廢棄了。可是廢棄了不表明咱們不能本身手動實現,像許多UI庫內部都有實現。本文以element-ui實現爲基礎進行介紹。同時看完本節,你會對組件的$parent,$children,$options有所瞭解。

方法介紹

$dispatch: $dispatch會向上觸發一個事件,同時傳遞要觸發的祖先組件的名稱與參數,當事件向上傳遞到對應的組件上時會觸發組件上的事件偵聽器,同時傳播會中止。

$broadcast: $broadcast會向全部的後代組件傳播一個事件,同時傳遞要觸發的後代組件的名稱與參數,當事件傳遞到對應的後代組件時,會觸發組件上的事件偵聽器,同時傳播會中止(由於向下傳遞是樹形的,因此只會中止其中一個葉子分支的傳遞)。

$dispatch實現與應用

1. 代碼實現

/**  * 向上傳播事件  * @param {*} eventName 事件名稱  * @param {*} componentName 接收事件的組件名稱  * @param {...any} params 傳遞的參數,能夠有多個  */ function dispatch(eventName, componentName, ...params) {  // 若是沒有$parent, 則取$root  let parent = this.$parent || this.$root  while (parent) {  // 組件的name存儲在組件的$options.componentName 上面  const name = parent.$options.name  // 若是接收事件的組件是當前組件  if (name === componentName) {  // 經過當前組件上面的$emit觸發事件,同事傳遞事件名稱與參數  parent.$emit.apply(parent, [eventName, ...params])  break  } else {  // 不然繼續向上判斷  parent = parent.$parent  }  } }  // 導出一個對象,而後在須要用到的地方經過混入添加 export default {  methods: {  $dispatch: dispatch  } }  複製代碼

2. 代碼應用

在子組件中經過$dispatch向上觸發事件

import emitter from '../mixins/emitter'
export default {  name: 'Chart',  // 經過混入將$dispatch加入進來  mixins: [emitter],  mounted() {  // 在組件渲染完以後,將組件經過$dispatch將本身註冊到Board組件上  this.$dispatch('register', 'Board', this)  } } 複製代碼

Board組件上經過$on監聽要註冊的事件

export default {
 name: 'Board',  created() {  this.$on('register',(component) => {  // 處理註冊邏輯  })  } } 複製代碼

$broadcast實現與應用

1. 代碼實現

/**  * 向下傳播事件  * @param {*} eventName 事件名稱  * @param {*} componentName 要觸發組件的名稱  * @param {...any} params 傳遞的參數  */ function broadcast(eventName, componentName, ...params) {  this.$children.forEach(child => {  const name = child.$options.name  if (name === componentName) {  child.$emit.apply(child, [eventName, ...params])  } else {  broadcast.apply(child, [eventName, componentName, ...params])  }  }) }  // 導出一個對象,而後在須要用到的地方經過混入添加 export default {  methods: {  $broadcast: broadcast  } }  複製代碼

2. 代碼應用

在父組件中經過$broadcast向下觸發事件

import emitter from '../mixins/emitter'
export default {
  name: 'Board',
  // 經過混入將$dispatch加入進來
  mixins: [emitter],
  methods:{
  	//在須要的時候,刷新組件
  	$_refreshChildren(params) {
  		this.$broadcast('refresh', 'Chart', params)
  	}
  }
}
複製代碼

在後代組件中經過$on監聽刷新事件

export default {
 name: 'Chart',  created() {  this.$on('refresh',(params) => {  // 刷新事件  })  } } 複製代碼

總結

經過上面的例子,同窗們應該都能對$dispatch$broadcast有所瞭解,可是爲何Vue2.0要放棄這兩個方法呢?官方給出的解釋是:」由於基於組件樹結構的事件流方式實在是讓人難以理解,而且在組件結構擴展的過程當中會變得愈來愈脆弱。這種事件方式確實不太好,咱們也不但願在之後讓開發者們太痛苦。而且 $dispatch$broadcast 也沒有解決兄弟組件間的通訊問題。「

確實如官網所說,這種事件流的方式確實不容易讓人理解,並且後期維護成本比較高。可是在小編看來,無論黑貓白貓,能抓老鼠的都是好貓,在許多特定的業務場景中,由於業務的複雜性,頗有可能使用到這樣的通訊方式。可是使用歸使用,可是不能濫用,小編一直就在項目中有使用。

結語

不要吹滅你的靈感和你的想象力; 不要成爲你的模型的奴隸。 ——文森特・梵高

本文使用 mdnice 排版

相關文章
相關標籤/搜索