實戰技巧,Vue原來還能夠這樣寫

兩隻黃鸝鳴翠柳,一堆bug上西天。javascript

天天上班寫着重複的代碼,當一個cv仔,忙到八九點,工做效率低,感受本身沒有任何提高。如何能更快的完成手頭的工做,提升本身的開發效率,在上一篇《絕對乾貨~!學會這些Vue小技巧,能夠早點下班和女神約會了》,小編整理了一些Vue開發技巧,今天小編又整理了一些新的Vue使用技巧。大家先加班,我先下班陪女神去逛街了。前端

新文推送《前方高能,這是最新的一波Vue實戰技巧,不用則已,一用驚人》vue

PS: 爲了方便你們閱讀,小編在每一節前面加了分割線java


hookEvent,原來能夠這樣監聽組件生命週期

1. 內部監聽生命週期函數

今天產品經理又給我甩過來一個需求,須要開發一個圖表,拿到需求,瞄了一眼,而後我就去echarts官網複製示例代碼了,複製完改了改差很少了,改完代碼長這樣程序員

<template>
 <div class="echarts"></div> </template> <script> export default {  mounted() {  this.chart = echarts.init(this.$el)  // 請求數據,賦值數據 等等一系列操做...  // 監聽窗口發生變化,resize組件  window.addEventListener('resize', this.$_handleResizeChart)  },  updated() {  // 幹了一堆活  },  created() {  // 幹了一堆活  },  beforeDestroy() {  // 組件銷燬時,銷燬監聽事件  window.removeEventListener('resize', this.$_handleResizeChart)  },  methods: {  $_handleResizeChart() {  this.chart.resize()  },  // 其餘一堆方法  } } </script> 複製代碼

功能寫完開開心心的提測了,測試沒啥問題,產品經理表示作的很棒。然而code review時候,技術大佬說了,這樣有問題。web

大佬:這樣寫不是很好,應該將監聽`resize`事件與銷燬`resize`事件放到一塊兒,如今兩段代碼分開並且相隔幾百行代碼,可讀性比較差
  我:那我把兩個生命週期鉤子函數位置換一下,放到一塊兒?   大佬: `hook`聽過沒?   我:`Vue3.0`纔有啊,咋,咱要升級`Vue`? 複製代碼

而後技術大佬就不理我了,並向我扔過來一段代碼element-ui

export default {
 mounted() {  this.chart = echarts.init(this.$el)  // 請求數據,賦值數據 等等一系列操做...   // 監聽窗口發生變化,resize組件  window.addEventListener('resize', this.$_handleResizeChart)  // 經過hook監聽組件銷燬鉤子函數,並取消監聽事件  this.$once('hook:beforeDestroy', () => {  window.removeEventListener('resize', this.$_handleResizeChart)  })  },  updated() {},  created() {},  methods: {  $_handleResizeChart() {  // this.chart.resize()  }  } } 複製代碼

看完代碼,恍然大悟,大佬不愧是大佬,原來Vue還能夠這樣監聽生命週期函數。微信

Vue組件中,能夠用過$on,$once去監聽全部的生命週期鉤子函數,如監聽組件的updated鉤子函數能夠寫成 this.$on('hook:updated', () => {})app

2. 外部監聽生命週期函數

今天同事在公司羣裏問,想在外部監聽組件的生命週期函數,有沒有辦法啊?echarts

爲何會有這樣的需求呢,原來同事用了一個第三方組件,須要監聽第三方組件數據的變化,可是組件又沒有提供change事件,同事也沒辦法了,纔想出來要去在外部監聽組件的updated鉤子函數。查看了一番資料,發現Vue支持在外部監聽組件的生命週期鉤子函數。

<template>
 <!--經過@hook:updated監聽組件的updated生命鉤子函數-->  <!--組件的全部生命週期鉤子均可以經過@hook:鉤子函數名 來監聽觸發-->  <custom-select @hook:updated="$_handleSelectUpdated" /> </template> <script> import CustomSelect from '../components/custom-select' export default {  components: {  CustomSelect  },  methods: {  $_handleSelectUpdated() {  console.log('custom-select組件的updated鉤子函數被觸發')  }  } } </script>  複製代碼

小項目還用Vuex?用Vue.observable手寫一個狀態管理吧

在前端項目中,有許多數據須要在各個組件之間進行傳遞共享,這時候就須要有一個狀態管理工具,通常狀況下,咱們都會使用Vuex,但對於小型項目來講,就像Vuex官網所說:「若是您不打算開發大型單頁應用,使用 Vuex 多是繁瑣冗餘的。確實是如此——若是您的應用夠簡單,您最好不要使用 Vuex」。這時候咱們就可使用Vue2.6提供的新API Vue.observable手動打造一個Vuex

建立 store

import Vue from 'vue'
 // 經過Vue.observable建立一個可響應的對象 export const store = Vue.observable({  userInfo: {},  roleIds: [] })  // 定義 mutations, 修改屬性 export const mutations = {  setUserInfo(userInfo) {  store.userInfo = userInfo  },  setRoleIds(roleIds) {  store.roleIds = roleIds  } }  複製代碼

在組件中引用

<template>
 <div>  {{ userInfo.name }}  </div> </template> <script> import { store, mutations } from '../store' export default {  computed: {  userInfo() {  return store.userInfo  }  },  created() {  mutations.setUserInfo({  name: '子君'  })  } } </script> 複製代碼

開發全局組件,你可能須要瞭解一下Vue.extend

Vue.extend是一個全局Api,平時咱們在開發業務的時候不多會用到它,但有時候咱們但願能夠開發一些全局組件好比Loading,Notify,Message等組件時,這時候就可使用Vue.extend

同窗們在使用element-uiloading時,在代碼中可能會這樣寫

// 顯示loading
const loading = this.$loading() // 關閉loading loading.close() 複製代碼

這樣寫可能沒什麼特別的,可是若是你這樣寫

const loading = this.$loading()
const loading1 = this.$loading() setTimeout(() => {  loading.close() }, 1000 * 3) 複製代碼

這時候你會發現,我調用了兩次loading,可是隻出現了一個,並且我只關閉了loading,可是loading1也被關閉了。這是怎麼實現的呢?咱們如今就是用Vue.extend + 單例模式去實現一個loading

開發loading組件

<template>
 <transition name="custom-loading-fade">  <!--loading蒙版-->  <div v-show="visible" class="custom-loading-mask">  <!--loading中間的圖標-->  <div class="custom-loading-spinner">  <i class="custom-spinner-icon"></i>  <!--loading上面顯示的文字-->  <p class="custom-loading-text">{{ text }}</p>  </div>  </div>  </transition> </template> <script> export default {  props: {  // 是否顯示loading  visible: {  type: Boolean,  default: false  },  // loading上面的顯示文字  text: {  type: String,  default: ''  }  } } </script> 複製代碼

開發出來loading組件以後,若是須要直接使用,就要這樣去用

<template>
 <div class="component-code">  <!--其餘一堆代碼-->  <custom-loading :visible="visible" text="加載中" />  </div> </template> <script> export default {  data() {  return {  visible: false  }  } } </script> 複製代碼

但這樣使用並不能知足咱們的需求

  1. 能夠經過js直接調用方法來顯示關閉
  2. loading能夠將整個頁面所有遮罩起來

經過Vue.extend將組件轉換爲全局組件

1. 改造loading組件,將組件的props改成data
export default {
 data() {  return {  text: '',  visible: false  }  } } 複製代碼
2. 經過Vue.extend改造組件
// loading/index.js
import Vue from 'vue' import LoadingComponent from './loading.vue'  // 經過Vue.extend將組件包裝成一個子類 const LoadingConstructor = Vue.extend(LoadingComponent)  let loading = undefined  LoadingConstructor.prototype.close = function() {  // 若是loading 有引用,則去掉引用  if (loading) {  loading = undefined  }  // 先將組件隱藏  this.visible = false  // 延遲300毫秒,等待loading關閉動畫執行完以後銷燬組件  setTimeout(() => {  // 移除掛載的dom元素  if (this.$el && this.$el.parentNode) {  this.$el.parentNode.removeChild(this.$el)  }  // 調用組件的$destroy方法進行組件銷燬  this.$destroy()  }, 300) }  const Loading = (options = {}) => {  // 若是組件已渲染,則返回便可  if (loading) {  return loading  }  // 要掛載的元素  const parent = document.body  // 組件屬性  const opts = {  text: '',  ...options  }  // 經過構造函數初始化組件 至關於 new Vue()  const instance = new LoadingConstructor({  el: document.createElement('div'),  data: opts  })  // 將loading元素掛在到parent上面  parent.appendChild(instance.$el)  // 顯示loading  Vue.nextTick(() => {  instance.visible = true  })  // 將組件實例賦值給loading  loading = instance  return instance }  export default Loading 複製代碼
3. 在頁面使用loading
import Loading from './loading/index.js'
export default {  created() {  const loading = Loading({ text: '正在加載。。。' })  // 三秒鐘後關閉  setTimeout(() => {  loading.close()  }, 3000)  } } 複製代碼

經過上面的改造,loading已經能夠在全局使用了,若是須要像element-ui同樣掛載到Vue.prototype上面,經過this.$loading調用,還須要改造一下

將組件掛載到Vue.prototype上面

Vue.prototype.$loading = Loading
// 在export以前將Loading方法進行綁定 export default Loading  // 在組件內使用 this.$loading() 複製代碼

自定義指令,從底層解決問題

什麼是指令?指令就是你女友指着你說,「那邊搓衣板,跪下,這是命令!」。開玩笑啦,程序員哪裏會有女友。

經過上一節咱們開發了一個loading組件,開發完以後,其餘開發在使用的時候又提出來了兩個需求

  1. 能夠將 loading掛載到某一個元素上面,如今只能是全屏使用
  2. 可使用指令在指定的元素上面掛載 loading

有需求,咱就作,沒話說

開發v-loading指令

import Vue from 'vue'
import LoadingComponent from './loading' // 使用 Vue.extend構造組件子類 const LoadingContructor = Vue.extend(LoadingComponent)  // 定義一個名爲loading的指令 Vue.directive('loading', {  /**  * 只調用一次,在指令第一次綁定到元素時調用,能夠在這裏作一些初始化的設置  * @param {*} el 指令要綁定的元素  * @param {*} binding 指令傳入的信息,包括 {name:'指令名稱', value: '指令綁定的值',arg: '指令參數 v-bind:text 對應 text'}  */  bind(el, binding) {  const instance = new LoadingContructor({  el: document.createElement('div'),  data: {}  })  el.appendChild(instance.$el)  el.instance = instance  Vue.nextTick(() => {  el.instance.visible = binding.value  })  },  /**  * 所在組件的 VNode 更新時調用  * @param {*} el  * @param {*} binding  */  update(el, binding) {  // 經過對比值的變化判斷loading是否顯示  if (binding.oldValue !== binding.value) {  el.instance.visible = binding.value  }  },  /**  * 只調用一次,在 指令與元素解綁時調用  * @param {*} el  */  unbind(el) {  const mask = el.instance.$el  if (mask.parentNode) {  mask.parentNode.removeChild(mask)  }  el.instance.$destroy()  el.instance = undefined  } }) 複製代碼

在元素上面使用指令

<template>
 <div v-loading="visible"></div> </template> <script> export default {  data() {  return {  visible: false  }  },  created() {  this.visible = true  fetch().then(() => {  this.visible = false  })  } } </script>  複製代碼

項目中哪些場景能夠自定義指令

  1. 爲組件添加 loading效果
  2. 按鈕級別權限控制 v-permission
  3. 代碼埋點,根據操做類型定義指令
  4. input輸入框自動獲取焦點
  5. 其餘等等。。。

深度watchwatch當即觸發回調,我能夠監聽到你的一舉一動

在開發Vue項目時,咱們會常常性的使用到watch去監聽數據的變化,而後在變化以後作一系列操做。

基礎用法

好比一個列表頁,咱們但願用戶在搜索框輸入搜索關鍵字的時候,能夠自動觸發搜索,此時除了監聽搜索框的change事件以外,咱們也能夠經過watch監聽搜索關鍵字的變化

<template>
 <!--此處示例使用了element-ui-->  <div>  <div>  <span>搜索</span>  <input v-model="searchValue" />  </div>  <!--列表,代碼省略-->  </div> </template> <script> export default {  data() {  return {  searchValue: ''  }  },  watch: {  // 在值發生變化以後,從新加載數據  searchValue(newValue, oldValue) {  // 判斷搜索  if (newValue !== oldValue) {  this.$_loadData()  }  }  },  methods: {  $_loadData() {  // 從新加載數據,此處須要經過函數防抖  }  } } </script> 複製代碼

當即觸發

經過上面的代碼,如今已經能夠在值發生變化的時候觸發加載數據了,可是若是要在頁面初始化時候加載數據,咱們還須要在created或者mounted生命週期鉤子裏面再次調用$_loadData方法。不過,如今能夠不用這樣寫了,經過配置watch的當即觸發屬性,就能夠知足需求了

// 改造watch
export default {  watch: {  // 在值發生變化以後,從新加載數據  searchValue: {  // 經過handler來監聽屬性變化, 初次調用 newValue爲""空字符串, oldValue爲 undefined  handler(newValue, oldValue) {  if (newValue !== oldValue) {  this.$_loadData()  }  },  // 配置當即執行屬性  immediate: true  }  } } 複製代碼

深度監聽(我能夠看到你心裏的一舉一動)

一個表單頁面,需求但願用戶在修改表單的任意一項以後,表單頁面就須要變動爲被修改狀態。若是按照上例中watch的寫法,那麼咱們就須要去監聽表單每個屬性,太麻煩了,這時候就須要用到watch的深度監聽deep

export default {
 data() {  return {  formData: {  name: '',  sex: '',  age: 0,  deptId: ''  }  }  },  watch: {  // 在值發生變化以後,從新加載數據  formData: {  // 須要注意,由於對象引用的緣由, newValue和oldValue的值一直相等  handler(newValue, oldValue) {  // 在這裏標記頁面編輯狀態  },  // 經過指定deep屬性爲true, watch會監聽對象裏面每個值的變化  deep: true  }  } } 複製代碼

隨時監聽,隨時取消,瞭解一下$watch

有這樣一個需求,有一個表單,在編輯的時候須要監聽表單的變化,若是發生變化則保存按鈕啓用,不然保存按鈕禁用。這時候對於新增表單來講,能夠直接經過watch去監聽表單數據(假設是formData),如上例所述,但對於編輯表單來講,表單須要回填數據,這時候會修改formData的值,會觸發watch,沒法準確的判斷是否啓用保存按鈕。如今你就須要瞭解一下$watch

export default {
 data() {  return {  formData: {  name: '',  age: 0  }  }  },  created() {  this.$_loadData()  },  methods: {  // 模擬異步請求數據  $_loadData() {  setTimeout(() => {  // 先賦值  this.formData = {  name: '子君',  age: 18  }  // 等表單數據回填以後,監聽數據是否發生變化  const unwatch = this.$watch(  'formData',  () => {  console.log('數據發生了變化')  },  {  deep: true  }  )  // 模擬數據發生了變化  setTimeout(() => {  this.formData.name = '張三'  }, 1000)  }, 1000)  }  } } 複製代碼

根據上例能夠看到,咱們能夠在須要的時候經過this.$watch來監聽數據變化。那麼如何取消監聽呢,上例中this.$watch返回了一個值unwatch,是一個函數,在須要取消的時候,執行 unwatch()便可取消

本文使用 mdnice 排版


函數式組件,函數是組件?

什麼是函數式組件?函數式組件就是函數是組件,感受在玩文字遊戲。使用過React的同窗,應該不會對函數式組件感到陌生。函數式組件,咱們能夠理解爲沒有內部狀態,沒有生命週期鉤子函數,沒有this(不須要實例化的組件)。

在平常寫bug的過程當中,常常會開發一些純展現性的業務組件,好比一些詳情頁面,列表界面等,它們有一個共同的特色是隻須要將外部傳入的數據進行展示,不須要有內部狀態,不須要在生命週期鉤子函數裏面作處理,這時候你就能夠考慮使用函數式組件。

先來一個函數式組件的代碼

export default {
 // 經過配置functional屬性指定組件爲函數式組件  functional: true,  // 組件接收的外部屬性  props: {  avatar: {  type: String  }  },  /**  * 渲染函數  * @param {*} h  * @param {*} context 函數式組件沒有this, props, slots等都在context上面掛着  */  render(h, context) {  const { props } = context  if (props.avatar) {  return <img src={props.avatar}></img>  }  return <img src="default-avatar.png"></img>  } } 複製代碼

在上例中,咱們定義了一個頭像組件,若是外部傳入頭像,則顯示傳入的頭像,不然顯示默認頭像。上面的代碼中你們看到有一個render函數,這個是Vue使用JSX的寫法,關於JSX,小編將在後續文章中會出詳細的使用教程。

爲何使用函數式組件

  1. 最主要最關鍵的緣由是函數式組件不須要實例化,無狀態,沒有生命週期,因此渲染性能要好於普通組件
  2. 函數式組件結構比較簡單,代碼結構更清晰
函數式組件與普通組件的區別
  1. 函數式組件須要在聲明組件是指定functional
  2. 函數式組件不須要實例化,因此沒有 this, this經過 render函數的第二個參數來代替
  3. 函數式組件沒有生命週期鉤子函數,不能使用計算屬性,watch等等
  4. 函數式組件不能經過$emit對外暴露事件,調用事件只能經過 context.listeners.click的方式調用外部傳入的事件
  5. 由於函數式組件是沒有實例化的,因此在外部經過 ref去引用組件時,實際引用的是 HTMLElement
  6. 函數式組件的 props能夠不用顯示聲明,因此沒有在 props裏面聲明的屬性都會被自動隱式解析爲 prop,而普通組件全部未聲明的屬性都被解析到 $attrs裏面,並自動掛載到組件根元素上面(能夠經過 inheritAttrs屬性禁止)

我不想用JSX,能用函數式組件嗎?

Vue2.5以前,使用函數式組件只能經過JSX的方式,在以後,能夠經過模板語法來生命函數式組件

<!--在template 上面添加 functional屬性-->
<template functional>  <img :src="props.avatar ? props.avatar : 'default-avatar.png'" /> </template> <!--根據上一節第六條,能夠省略聲明props--> 複製代碼

最後我想說:

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

若是喜歡小編,關注小編,每週一給您帶來不同的乾貨。同時您也能夠關注下面的微信羣與公衆號。

PS: 小手一讚,豔遇不斷,謝謝

tLOX6O.png
tLOX6O.png

本文使用 mdnice 排版

相關文章
相關標籤/搜索