兩隻黃鸝鳴翠柳,一堆bug上西天。
天天上班寫着重複的代碼,當一個cv仔,忙到八九點,工做效率低,感受本身沒有任何提高。如何能更快的完成手頭的工做,今天小編整理了一些新的Vue
使用技巧。大家先加班,我先下班陪女神去逛街了。javascript
今天產品經理又給我甩過來一個需求,須要開發一個圖表,拿到需求,瞄了一眼,而後我就去echarts
官網複製示例代碼了,複製完改了改差很少了,改完代碼長這樣html
<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時候,技術大佬說了,這樣有問題。前端
大佬:這樣寫不是很好,應該將監聽resize事件與銷燬resize事件放到一塊兒,如今兩段代碼分開並且相隔幾百行代碼,可讀性比較差 我:那我把兩個生命週期鉤子函數位置換一下,放到一塊兒? 大佬:hook聽過沒? 我:Vue3.0纔有啊,咋,咱要升級Vue?
而後技術大佬就不理我了,並向我扔過來一段代碼vue
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`還能夠這樣監聽生命週期函數。java
_在`Vue`組件中,能夠用過`$on\`,\`$once`去監聽全部的生命週期鉤子函數,如監聽組件的`updated`鉤子函數能夠寫成 `this.$on('hook:updated', () => {})`_程序員
今天同事在公司羣裏問,想在外部監聽組件的生命週期函數,有沒有辦法啊?element-ui
爲何會有這樣的需求呢,原來同事用了一個第三方組件,須要監聽第三方組件數據的變化,可是組件又沒有提供change
事件,同事也沒辦法了,纔想出來要去在外部監聽組件的updated
鉤子函數。查看了一番資料,發現Vue
支持在外部監聽組件的生命週期鉤子函數。app
<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
echarts
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
。dom
同窗們在使用element-ui
的loading
時,在代碼中可能會這樣寫
// 顯示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>
但這樣使用並不能知足咱們的需求
loading
能夠將整個頁面所有遮罩起來Vue.extend
將組件轉換爲全局組件loading
組件,將組件的props
改成data
export default { data() { return { text: '', visible: false } } }
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
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
組件,開發完以後,其餘開發在使用的時候又提出來了兩個需求
loading
掛載到某一個元素上面,如今只能是全屏使用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>
loading
效果v-permission
watch
與watch
當即觸發回調,我能夠監聽到你的一舉一動在開發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()
便可取消
什麼是函數式組件?函數式組件就是函數是組件,感受在玩文字遊戲。使用過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
,小編將在後續文章中會出詳細的使用教程。
this
,this
經過render
函數的第二個參數來代替context.listeners.click
的方式調用外部傳入的事件ref
去引用組件時,實際引用的是HTMLElement
props
能夠不用顯示聲明,因此沒有在props
裏面聲明的屬性都會被自動隱式解析爲prop
,而普通組件全部未聲明的屬性都被解析到$attrs
裏面,並自動掛載到組件根元素上面(能夠經過inheritAttrs
屬性禁止)JSX
,能用函數式組件嗎?在Vue2.5
以前,使用函數式組件只能經過JSX
的方式,在以後,能夠經過模板語法來生命函數式組件
<!--在template 上面添加 functional屬性--> <template functional> <img :src="props.avatar ? props.avatar : 'default-avatar.png'" /> </template> <!--根據上一節第六條,能夠省略聲明props-->
不要吹滅你的靈感和你的想象力; 不要成爲你的模型的奴隸。 ——文森特・梵高