說來慚愧,用 Vue 這么長時間了倒得,今天第一次用指令立叛。
是出于什么契機呢壤躲?
主要是今天我們需要優(yōu)化一下 loading 的效果城菊,之前項目中用的都是 element-ui 的v-loading,現(xiàn)在我們網(wǎng)站的審美提上來了碉克,過去的效果已經(jīng)跟不上我們“羅網(wǎng)”的氣質(zhì)了凌唬。修改默認 loading 效果的方法千千萬,我為啥要自定義指令來寫一個 loading 呢漏麦,因為我沒用過客税,我就想用一下,我愿意撕贞,而且以后再做修改的話更耻,這個方案的可擴展性更好,想怎么改就怎改捏膨,不用摳摳搜搜地去改樣式秧均。
提示:本文只針對有 Vue 開發(fā)經(jīng)驗的同學(xué)『叛模看完這篇文章目胡,大家應(yīng)該能夠?qū)懸恍〇|西了,如果看完還不會链快,很正常誉己,每個人都有自己的天賦,對于不是自己天賦方面的東西多看幾遍就好了久又,大家都是智商正常的人類巫延,沒有什么東西是他能學(xué)會,你不能學(xué)會的地消,所以不用著急炉峰,跟著節(jié)奏慢慢來就可以。分享一個我奶奶留下來的家訓(xùn)“活到老脉执,學(xué)到老疼阔,還有一招學(xué)不到”,所以大家要對自己有信心。
今天排插炸了婆廊,還好沒炸的很大迅细,只是小小的炸了一下起了點火星子冒了點小煙,沒炸到臉淘邻。我可是沒開空調(diào)茵典、沒坐在電熱毯里,穿個睡衣坐在書桌前寫的文啊宾舅。還好有我們王剛王老師推薦的“每日堅果”補充能量统阿,加班寫文必備,代碼的好伴侶筹我,大家快去買扶平。
本文大綱
- 什么是指令?
- 常見的默認指令
- 什么是自定義指令蔬蕊?我會帶著大家過一下 Vue 官網(wǎng)文檔上的demo
- 結(jié)合具體的業(yè)務(wù)場景寫一個自定義的loading指定 结澄,暫定 v-cloading
- 總結(jié) 指令比較適合哪些應(yīng)用場景
- 參考文檔
什么是指令?
- 指令是帶有
v-
前綴的特殊屬性 - 當表達式的值改變時岸夯,將其產(chǎn)生的 連帶影響麻献,響應(yīng)式地作用于 DOM
第一句換很好理解,第二句我們在接下來的demo中會讓你直觀的感受到這句話的意思囱修。
常見的默認指令
這里我們就列舉三個常見的指令赎瑰,想看更多指令可以看看 Vue 官方文檔王悍,或是 Vue指令基本使用大全這篇博文破镰,這篇文章列舉出了挺多的。
v-model
作用:在表單元素上創(chuàng)建雙向的數(shù)據(jù)綁定
說明:監(jiān)聽用戶的輸入事件以更新數(shù)據(jù)
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
v-on
- 作用:綁定事件
- 語法:
v-on:click="say"
orv-on:click="say('參數(shù)', $event)"
- 簡寫:
@click="say"
- 說明:綁定的事件從
methods
中獲取
<!-- 完整語法 -->
<a v-on:click="doSomething"></a>
<!-- 縮寫 -->
<a @click="doSomething"></a>
<!-- 方法傳參 -->
<a @click="doSomething(“123”)"></a>
<script>
// 2 創(chuàng)建 Vue 的實例對象
var vm = new Vue({
el: '#app',
// methods屬性用來給vue實例提供方法(事件)
methods: {
doSomething: function(str) {
//接受參數(shù)压储,并輸出
console.log(str);
}
}
})
</script>
v-bind
- 作用:當表達式的值改變時鲜漩,將其產(chǎn)生的連帶影響,響應(yīng)式地作用于 DOM
- 語法:
v-bind:title="msg"
- 簡寫:
:title="msg"
<!-- 完整語法 -->
<a v-bind:href="url"></a>
<!-- 縮寫 -->
<a :href="url"></a>
<script>
// 2 創(chuàng)建 Vue 的實例對象
var vm = new Vue({
// el 用來指定vue掛載到頁面中的元素集惋,值是:選擇器
// 理解:用來指定vue管理的HTML區(qū)域
el: '#app',
// 數(shù)據(jù)對象孕似,用來給視圖中提供數(shù)據(jù)的
data: {
url: 'http://www.baidu.com'
}
})
</script>
什么是自定義指令
這里我會把 官方文檔上的 demo 和一些必要的說明搬過來。為什么要這么做刮刑?為了從來沒接觸過自定義指令的同學(xué)不用自己查找切換看文檔喉祭。只需要看完這一篇,了解一些基本的概念雷绢,就可以循序漸進的帶你寫一個 v-cloading 自定義loading的指令泛烙。如果有想了解源碼的同學(xué),可以暫時移步翘紊,網(wǎng)上有很多介紹 v-loading 帶你解讀源碼的文章蔽氨。這里我們是第一次使用,所以只會淺顯地介紹應(yīng)用。
Vue 官方文檔上都是以全局注冊為例子鹉究,在這里我們就都以局部注冊為例子宇立。
demo1:當頁面加載時,input 自動獲取焦點
我們先上代碼
<template>
<div class="test-page">
<!-- 我們的指令 v-fo我們的指令 -->
<input type="text" v-focus>
</div>
</template>
<script>
export default {
// directives 不用多做解釋自赔,就是放指令的地方
directives: {
// focus 我們的指令名稱 這里我們寫focus就可以了妈嘹,Vue會默認給我們加上 v-的
focus: { // 這個對象相當于我們?nèi)绾稳ッ枋龊投x這個 focus 指令
// 當被綁定的元素插入到 dom 中時
inserted: (el) => {
// el 綁定指令的元素,聚焦
el.focus()
}
}
}
}
</script>
<style lang="scss" scoped>
.test-page {
padding: 20px;
input {
width: 200px;
height: 40px;
}
}
</style>
在這里我們不用過多糾結(jié)每一行代碼是什么绍妨,比如 inserted
是什么蟋滴,inserted 是指令的一個鉤子函數(shù),不用著急痘绎,后面我們會具體介紹一下 指令的鉤子函數(shù)的津函。這個demo只是讓我們先感受下指令是什么,能做什么孤页,是不是很簡單尔苦,感覺自己隨隨便也能寫一個 demo 了。
結(jié)果:可以看到確實是頁面一加載就 input 就獲取到了焦點
demo2:帶你感受指令的鉤子函數(shù)
雖然代碼應(yīng)該是很客觀很理性的東西行施,但是學(xué)習(xí)的過程中允坚,感受也很重要,感受會影響你想不想學(xué)蛾号,如果只是冰冷的定義我覺得會很難理解一個東西稠项。你感受到了,也自然就學(xué)會了鲜结。
我先列一下官網(wǎng)對于鉤子函數(shù)的定義和描述展运,然后我會結(jié)合具體的例子,讓大家感受一下鉤子函數(shù)是怎樣發(fā)揮作用的精刷,觸發(fā)的時機是什么拗胜。
一個指令定義對象可以提供如下幾個鉤子函數(shù)(均為可選):
-
bind
:只調(diào)用一次,指令第一次綁定到元素時調(diào)用怒允。在這里可以進行一次性的初始化設(shè)置埂软。 -
inserted
: 被綁定元素插入父節(jié)點時調(diào)用(僅保證父節(jié)點存在,但不一定已被插入文檔中) -
update
:所在組件的 VNode 更新時調(diào)用纫事,但是可能發(fā)生在其子 VNode 更新之前勘畔。指令的值可能發(fā)生了改變,也可能沒有丽惶。但是你可以通過比較更新前后的值來忽略不必要的模板更新炫七。 -
componentUpdated
:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用 -
unbind
:只調(diào)用一次,指令與元素解綁時調(diào)用蚊夫。
關(guān)于鉤子函數(shù)其實我理解的也不是和深刻诉字,就以 demo 去理解,如果有同學(xué)更加理解鉤子函數(shù),可以告訴我壤圃,大家一起交流一下陵霉。這個 demo 也是看別人寫的。
<template>
<div class="test-page">
<h1 v-color="color" v-if="show">{{title}}</h1>
<button @click="show=false">測試解綁 v-color</button>
<button @click="title='更換title'">更換 title</button>
<button @click="color='blue'">更換 color</button>
</div>
</template>
<script>
export default {
data () {
return {
color: 'red',
title: '自定義指令',
show: true
}
},
directives: {
color: {
bind: () => {
console.log('bind')
},
inserted: (el, binding) => {
console.log('inserted')
el.style.color = binding.value
},
update: (el, binding) => {
console.log('update value: ', binding.value)
console.log('update oldValue: ', binding.oldValue)
if (binding.value !== binding.oldValue) {
el.style.color = binding.value
}
},
componentUpdated: (el, binding) => {
console.log('componentUpdated')
},
unbind: () => {
console.log('v-color 指令解綁')
}
}
}
}
</script>
當我們刷新頁面時伍绳,指令顯示被綁定到了 dom 上踊挠,然后被插入到了父節(jié)點中。
當我們點擊 “更換title” 按鈕時冲杀,其實指定綁定的元素肯定是會更新的效床,但是指令的 value 值是還沒有更新的,仍然是 red权谁。
當我們點擊 “更換 color” 按鈕時剩檀,指令的值就發(fā)生變化而,由 red 變成了 blue旺芽。
當我們點擊 “測試解綁 v-color”沪猴,我們其實就是銷毀了指令所綁定的組件,指令就解綁了采章。
demo3:鉤子函數(shù)參數(shù)
在上述 demo 中我們看到鉤子函數(shù)的參數(shù)有 el运嗜、binding 等,可能不是很理解悯舟,這一個 demo 就帶大家了解一下鉤子函數(shù)的參數(shù)担租。
照例我們先看看官網(wǎng)是怎么說明鉤子函數(shù)參數(shù)的。
el
:指令所綁定的元素抵怎,可以用來直接操作 DOM-
binding
:一個對象奋救,包含一下屬性:name
:指令名,不包括v-
前綴value
:指令的綁定值便贵,例如:v-my-directive="1 + 1"
中菠镇,綁定值為 2。oldValue
:指令綁定的前一個值承璃,僅在update
和componentUpdated
鉤子中可用。無論值是否改變都可用蚌本。expression
:字符串形式的指令表達式盔粹。例如v-my-directive="1 + 1"
中,表達式為"1 + 1"
程癌。arg
:傳給指令的參數(shù)舷嗡,可選。例如v-my-directive:foo
中嵌莉,參數(shù)為"foo"
进萄。modifiers
:一個包含修飾符的對象。例如:v-my-directive.foo.bar
中,修飾符對象為{ foo: true, bar: true }
中鼠。
vnode
:Vue 編譯生成的虛擬節(jié)點可婶。移步 VNode API 來了解更多詳情。oldVnode
:上一個虛擬節(jié)點援雇,僅在update
和componentUpdated
鉤子中可用矛渴。
Warning:
除了 el
之外,其它參數(shù)都應(yīng)該是只讀的惫搏,切勿進行修改具温。如果需要在鉤子之間共享數(shù)據(jù),建議通過元素的 dataset
來進行筐赔。
<template>
<div class="test-page">
<div v-demo:foo.a.b="message"></div>
</div>
</template>
<script>
export default {
data () {
return {
message: 'hello!'
}
},
directives: {
demo: {
bind: (el, binding, vnode) => {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifier: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
}
}
}
</script>
我們來看下結(jié)果:是不是很簡單參數(shù)就那些
文檔中關(guān)于動態(tài)指令參數(shù)扬虚、字面量我就不接說了桂敛,大家自己看下文檔就可以了。
帶大家實現(xiàn)一個仿 element-UI 的 v-loading
上面的基本知識介紹了那么多,我們終于可以綜合運用來寫一個實用的例子了岭洲。
首先我們看下 element 的 v-loading 有哪些屬性
但是這里我們不會實現(xiàn)上面那么多屬性,因為我懶逻翁,demo并非本人原創(chuàng)龙填,我只是做了修改。
我們今天的 demo 實現(xiàn):fullscreen text spinner background 等屬性尸曼,我們的自定義指令 我們就叫它 v-cloading
吧们何。需要loading的時候我們就創(chuàng)建一個實例,把它掛到父級元素上去控轿。
首先我們先準備一個 loading 的模板 mask.vue:
這個沒什么好講的冤竹,就是定義了一下 loading 的效果長什么樣
<template>
<transition name="cv-loading-fade">
<div
v-show="visible"
class="cv-loading-mask"
:style="{ backgroundColor: background || ''}"
:class="[customClass, { 'is-fullscreen': fullscreen }]">
<div class="cv-loading-spinner">
<div class="loading-animation-box">
<span class="circle purple-circle"></span>
<span class="circle green-circle"></span>
</div>
<span class="cv-loading-text">{{text}}</span>
</div>
</div>
</transition>
</template>
<script>
export default {
data () {
return {
text: null,
background: null,
fullscreen: true,
visible: false,
customClass: ''
}
},
mounted () {
if (this.fullscreen) {
document.body.style.overflow = 'hidden'
}
},
methods: {
setText (text) {
this.text = text
}
},
destroyed () {
document.body.style.overflowX = 'hidden'
}
}
</script>
<style lang="scss" scoped>
@import "assets/styles/variables.scss";
.cv-loading-fade-enter,
.cv-loading-fade-leave-active {
opacity: 0;
}
.cv-loading-mask {
position: absolute;
z-index: 2000;
background-color: rgba(255, 255, 255, 0.9);
margin: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: opacity 0.3s;
&.is-fullscreen {
position: fixed;
.cv-loading-spinner {
margin-top: -25px;
}
}
}
.cv-loading-spinner {
top: 50%;
margin-top: -21px;
width: 100%;
text-align: center;
position: absolute;
.loading-animation-box {
position: relative;
width: 64px;
height: 64px;
margin: 0 auto;
.circle {
position: absolute;
display: inline-block;
width: 14px;
height: 14px;
border-radius: 50%;
top: 25px;
transition: all;
&.purple-circle {
background-color: $primary_color;
left: 10px;
animation: leftAnimation 1.5s ease-in-out infinite;
}
&.green-circle {
background-color: $sub_color;
right: 10px;
animation: rightAnimation 1.5s ease-in-out infinite;
}
}
}
.cv-loading-text {
margin: 3px 0;
font-size: 14px;
color: #5D37EC;
}
}
@keyframes leftAnimation {
0% {
transform: scale(1) translateX(0px);
z-index: 1;
}
25% {
transform: scale(1.5) translateX(15px);
z-index: 5;
}
50% {
transform: scale(1) translateX(30px);
z-index: 5;
}
75% {
transform: scale(0.5) translateX(15px);
z-index: 5;
}
100% {
transform: scale(1) translateX(0px);
z-index: 1;
}
}
@keyframes rightAnimation {
0% {
transform: scale(1) translateX(0px);
z-index: 1;
}
25% {
transform: scale(0.5) translateX(-15px);
z-index: 1;
}
50% {
transform: scale(1) translateX(-30px);
z-index: 1;
}
75% {
transform: scale(1.5) translateX(-15px);
z-index: 5;
}
100% {
transform: scale(1) translate(0px);
z-index: 1;
}
}
</style>
接下來我們看下最關(guān)鍵的部分,指令的實現(xiàn)部分茬射,其實這部分的代碼和 element v-loading 本身的實現(xiàn)比較相似鹦蠕。雖然這塊代碼還不算特別完善,但是基本的實現(xiàn)是可以的在抛,這塊我會重點講一下钟病。等我認真閱讀完 element-UI v-loading 的源碼后,會再進行完善的刚梭。
import Vue from 'vue'
import maskLoading from './mask.vue'
// 我們通過模板 構(gòu)造一個 Mask
const Mask = Vue.extend(maskLoading)
// Mask 是否需要更新肠阱,也就是 loading 展示效果是否需要更新
const toggleLoading = (el, binding) => {
// 如果指令傳入的值為 true 或是有值,就顯示這個模板朴读,掛到父級元素上去或是body上
if (binding.value) {
Vue.nextTick(() => {
if (binding.modifiers.fullscreen) {
// 全屏的話就掛載到 body 上
document.body.appendChild(el.mask)
} else {
// 非全屏就掛到當前組件上去
let height = el.clientHeight
let width = el.clientWidth
let offsetTop = el.offsetTop
el.mask.style.top = offsetTop + 'px'
el.mask.style.height = height + 'px'
el.mask.style.width = width + 'px'
el.appendChild(el.mask)
}
})
} else {
// 如果傳入的值是 false屹徘,或是沒有值,就銷毀 Mask
el.mask && el.mask.parentNode && el.mask.parentNode.removeChild(el.mask)
el.instance && el.instance.$destroy()
}
}
Vue.directive('cloading', {
bind (el, binding) {
// 指令第一次綁定到元素上時衅金,初始化一些屬性噪伊,這些屬性可以通過字面量的形式傳簿煌,也可以通過 dataset或是其他方式,我還沒想好鉴吹。
let background = binding.value.background
let text = binding.value.text
let iconSrc = binding.value.iconSrc
let iconWidth = binding.value.iconWidth
let iconHeight = binding.value.iconHeight
let color = binding.value.color
let fontSize = binding.value.fontSize
console.log('binding.value: ', binding.value)
// 構(gòu)造了一個 Mask 實例
const mask = new Mask({
el: document.createElement('div'),
data: {
fullscreen: !!binding.modifiers.fullscreen,
background: background || '255, 255, 255, 0.9',
text: text || '加載中...',
iconSrc: iconSrc || require('../../assets/images/icn_loading.png'),
iconWidth: iconWidth || null,
iconHeight: iconHeight || null,
color: color || null,
fontSize: fontSize || null
}
})
el.instance = mask
el.mask = mask.$el
// 更新 Mask的展示
toggleLoading(el, binding)
},
// 所在組件的 VNode 更新時調(diào)用
update (el, binding) {
if (binding.oldValue !== binding.value) {
toggleLoading(el, binding)
}
},
unbind (el) {
el.mask && el.mask.parentNode && el.mask.parentNode.removeChild(el.mask)
el.instance && el.instance.$destroy()
}
})
<div
:style="{height: '1000px', width: '100%'}"
v-cloading.fullscreen="true"
>
</div>
這樣寫下來姨伟,發(fā)現(xiàn)也沒什么可講的,一切都很自然拙寡。
鐺鐺~授滓,我們來看下效果:
還蠻好看的。是不是很簡單肆糕。