您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)Vue中如何使用裝飾器,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
什么是裝飾器
裝飾器是ES2016提出來的一個提案,當(dāng)前處于Stage 2階段,關(guān)于裝飾器的體驗,可以點擊 https://github.com/tc39/proposal-decorators查看詳情。裝飾器是一種與類相關(guān)的語法糖,用來包裝或者修改類或者類的方法的行為,其實裝飾器就是設(shè)計模式中裝飾者模式的一種實現(xiàn)方式。不過前面說的這些概念太干了,我們用人話來翻譯一下,舉一個例子。
在日常開發(fā)寫bug過程中,我們經(jīng)常會用到防抖和節(jié)流,比如像下面這樣
class MyClass { follow = debounce(function() { console.log('我是子君,關(guān)注我哦') }, 100) } const myClass = new MyClass() // 多次調(diào)用只會輸出一次 myClass.follow() myClass.follow()
上面是一個防抖的例子,我們通過debounce函數(shù)將另一個函數(shù)包起來,實現(xiàn)了防抖的功能,這時候再有另一個需求,比如希望在調(diào)用follow函數(shù)前后各打印一段日志,這時候我們還可以再開發(fā)一個log函數(shù),然后繼續(xù)將follow包裝起來
/** * 最外層是防抖,否則log會被調(diào)用多次 */ class MyClass { follow = debounce( log(function() { console.log('我是子君,關(guān)注我哦') }), 100 ) }
上面代碼中的debounce和log兩個函數(shù),本質(zhì)上是兩個包裝函數(shù),通過這兩個函數(shù)對原函數(shù)的包裝,使原函數(shù)的行為發(fā)生了變化,而js中的裝飾器的原理就是這樣的,我們使用裝飾器對上面的代碼進行改造
class MyClass { @debounce(100) @log follow() { console.log('我是子君,關(guān)注我哦') } }
裝飾器的形式就是 @ + 函數(shù)名,如果有參數(shù)的話,后面的括號里面可以傳參
在方法上使用裝飾器
裝飾器可以應(yīng)用到class上或者class里面的屬性上面,但一般情況下,應(yīng)用到class屬性上面的場景會比較多一些,比如像上面我們說的log,debounce等等,都一般會應(yīng)用到類屬性上面,接下來我們一起來具體看一下如何實現(xiàn)一個裝飾器,并應(yīng)用到類上面。在實現(xiàn)裝飾器之前,我們需要先了解一下屬性描述符
了解一下屬性描述符
在我們定義一個對象里面的屬性的時候,其實這個屬性上面是有許多屬性描述符的,這些描述符標(biāo)明了這個屬性能不能修改,能不能枚舉,能不能刪除等等,同時ECMAScript將這些屬性描述符分為兩類,分別是數(shù)據(jù)屬性和訪問器屬性,并且數(shù)據(jù)屬性與訪問器屬性是不能共存的。
數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置,在這個位置可以讀取和寫入值。數(shù)據(jù)屬性包含了四個描述符,分別是
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
configurable
表示能不能通過delete刪除屬性,能否修改屬性的其他描述符特性,或者能否將數(shù)據(jù)屬性修改為訪問器屬性。當(dāng)我們通過let obj = {name: ''}聲明一個對象的時候,這個對象里面所有的屬性的configurable描述符的值都是true
2. enumerable
表示能不能通過for in或者Object.keys等方式獲取到屬性,我們一般聲明的對象里面這個描述符的值是true,但是對于class類里面的屬性來說,這個值是false
3. writable
表示能否修改屬性的數(shù)據(jù)值,通過將這個修改為false,可以實現(xiàn)屬性只讀的效果。
4. value
表示當(dāng)前屬性的數(shù)據(jù)值,讀取屬性值的時候,從這里讀??;寫入屬性值的時候,會寫到這個位置。
訪問器屬性
訪問器屬性不包含數(shù)據(jù)值,他們包含了getter與setter兩個函數(shù),同時configurable與enumerable是數(shù)據(jù)屬性與訪問器屬性共有的兩個描述符。
1. getter
在讀取屬性的時候調(diào)用這個函數(shù),默認(rèn)這個函數(shù)為undefined
2. setter
在寫入屬性值的時候調(diào)用這個函數(shù),默認(rèn)這個函數(shù)為undefined
了解了這六個描述符之后,你可能會有幾個疑問: 我如何去定義修改這些屬性描述符?這些屬性描述符與今天的文章主題有什么關(guān)系?接下來是揭曉答案的時候了。
使用Object.defineProperty
了解過vue2.0雙向綁定原理的同學(xué)一定知道,Vue的雙向綁定就是通過使用Object.defineProperty去定義數(shù)據(jù)屬性的getter與setter方法來實現(xiàn)的,比如下面有一個對象
let obj = { name: '子君', officialAccounts: '前端有的玩' }
我希望這個對象里面的用戶名是不能被修改的,用Object.defineProperty該如何定義呢?
Object.defineProperty(obj,'name', { // 設(shè)置writable 是 false, 這個屬性將不能被修改 writable: false }) // 修改obj.name obj.name = "君子" // 打印依然是子君 console.log(obj.name)
通過Object.defineProperty可以去定義或者修改對象屬性的屬性描述符,但是因為數(shù)據(jù)屬性與訪問器屬性是互斥的,所以一次只能修改其中的一類,這一點需要注意。
定義一個防抖裝飾器
裝飾器本質(zhì)上依然是一個函數(shù),不過這個函數(shù)的參數(shù)是固定的,如下是防抖裝飾器的代碼
/** *@param wait 延遲時長 */ function debounce(wait) { return function(target, name, descriptor) { descriptor.value = debounce(descriptor.value, wait) } } // 使用方式 class MyClass { @debounce(100) follow() { console.log('我是子君,我的公眾號是 【前端有的玩】,關(guān)注有驚喜哦') } }
我們逐行去分析一下代碼
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
首先我們定義了一個 debounce函數(shù),同時有一個參數(shù)wait,這個函數(shù)對應(yīng)的就是在下面調(diào)用裝飾器時使用的@debounce(100)
debounce函數(shù)返回了一個新的函數(shù),這個函數(shù)即裝飾器的核心,這個函數(shù)有三個參數(shù),下面逐一分析
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
target: 這個類屬性函數(shù)是在誰上面掛載的,如上例對應(yīng)的是MyClass類
name: 這個類屬性函數(shù)的名稱,對應(yīng)上面的follow
descriptor: 這個就是我們前面說的屬性描述符,通過直接descriptor上面的屬性,即可實現(xiàn)屬性只讀,數(shù)據(jù)重寫等功能
3. 然后第三行 descriptor.value = debounce(descriptor.value, wait), 前面我們已經(jīng)了解到,屬性描述符上面的value對應(yīng)的是這個屬性的值,所以我們通過重寫這個屬性,將其用debounce函數(shù)包裝起來,這樣在函數(shù)調(diào)用follow時實際調(diào)用的是包裝后的函數(shù)
通過上面的三步,我們就實現(xiàn)了類屬性上面可使用的裝飾器,同時將其應(yīng)用到了類屬性上面
在class上使用裝飾器
裝飾器不僅可以應(yīng)用到類屬性上面,還可以直接應(yīng)用到類上面,比如我希望可以實現(xiàn)一個類似Vue混入那樣的功能,給一個類混入一些方法屬性,應(yīng)該如何去做呢?
// 這個是要混入的對象 const methods = { logger() { console.log('記錄日志') } } // 這個是一個登陸登出類 class Login{ login() {} logout() {} }
如何將上面的methods混入到Login中,首先我們先實現(xiàn)一個類裝飾器
function mixins(obj) { return function (target) { Object.assign(target.prototype, obj) } } // 然后通過裝飾器混入 @mixins(methods) class Login{ login() {} logout() {} }
這樣就實現(xiàn)了類裝飾器。對于類裝飾器,只有一個參數(shù),即target,對應(yīng)的就是這個類本身。
了解完裝飾器,我們接下來看一下如何在Vue中使用裝飾器。
在Vue中使用裝飾器
使用ts開發(fā)Vue的同學(xué)一定對vue-property-decorator不會感到陌生,這個插件提供了許多裝飾器,方便大家開發(fā)的時候使用,當(dāng)然本文的中點不是這個插件。其實如果我們的項目沒有使用ts,也是可以使用裝飾器的,怎么用呢?
配置基礎(chǔ)環(huán)境
除了一些老的項目,我們現(xiàn)在一般新建Vue項目的時候,都會選擇使用腳手架vue-cli3/4來新建,這時候新建的項目已經(jīng)默認(rèn)支持了裝飾器,不需要再配置太多額外的東西,如果你的項目使用了eslint,那么需要給eslint配置以下內(nèi)容。
parserOptions: { ecmaFeatures:{ // 支持裝飾器 legacyDecorators: true } }
使用裝飾器
雖然Vue的組件,我們一般書寫的時候export出去的是一個對象,但是這個并不影響我們直接在組件中使用裝飾器,比如就拿上例中的log舉例。
function log() { /** * @param target 對應(yīng) methods 這個對象 * @param name 對應(yīng)屬性方法的名稱 * @param descriptor 對應(yīng)屬性方法的修飾符 */ return function(target, name, descriptor) { console.log(target, name, descriptor) const fn = descriptor.value descriptor.value = function(...rest) { console.log(`這是調(diào)用方法【${name}】前打印的日志`) fn.call(this, ...rest) console.log(`這是調(diào)用方法【${name}】后打印的日志`) } } } export default { created() { this.getData() }, methods: { @log() getData() { console.log('獲取數(shù)據(jù)') } } }
看了上面的代碼,是不是發(fā)現(xiàn)在Vue中使用裝飾器還是很簡單的,和在class的屬性上面使用的方式一模一樣,但有一點需要注意,在methods里面的方法上面使用裝飾器,這時候裝飾器的target對應(yīng)的是methods。
除了在methods上面可以使用裝飾器之外,你也可以在生命周期鉤子函數(shù)上面使用裝飾器,這時候target對應(yīng)的是整個組件對象。
一些常用的裝飾器
下面小編羅列了幾個小編在項目中常用的幾個裝飾器,方便大家使用
1. 函數(shù)節(jié)流與防抖
函數(shù)節(jié)流與防抖應(yīng)用場景是比較廣的,一般使用時候會通過throttle或debounce方法對要調(diào)用的函數(shù)進行包裝,現(xiàn)在就可以使用上文說的內(nèi)容將這兩個函數(shù)封裝成裝飾器, 防抖節(jié)流使用的是lodash提供的方法,大家也可以自行實現(xiàn)節(jié)流防抖函數(shù)哦
import { throttle, debounce } from 'lodash' /** * 函數(shù)節(jié)流裝飾器 * @param {number} wait 節(jié)流的毫秒 * @param {Object} options 節(jié)流選項對象 * [options.leading=true] (boolean): 指定調(diào)用在節(jié)流開始前。 * [options.trailing=true] (boolean): 指定調(diào)用在節(jié)流結(jié)束后。 */ export const throttle = function(wait, options = {}) { return function(target, name, descriptor) { descriptor.value = throttle(descriptor.value, wait, options) } } /** * 函數(shù)防抖裝飾器 * @param {number} wait 需要延遲的毫秒數(shù)。 * @param {Object} options 選項對象 * [options.leading=false] (boolean): 指定在延遲開始前調(diào)用。 * [options.maxWait] (number): 設(shè)置 func 允許被延遲的最大值。 * [options.trailing=true] (boolean): 指定在延遲結(jié)束后調(diào)用。 */ export const debounce = function(wait, options = {}) { return function(target, name, descriptor) { descriptor.value = debounce(descriptor.value, wait, options) } }
封裝完之后,在組件中使用
import {debounce} from '@/decorator' export default { methods:{ @debounce(100) resize(){} } }
2. loading
在加載數(shù)據(jù)的時候,為了個用戶一個友好的提示,同時防止用戶繼續(xù)操作,一般會在請求前顯示一個loading,然后在請求結(jié)束之后關(guān)掉loading,一般寫法如下
export default { methods:{ async getData() { const loading = Toast.loading() try{ const data = await loadData() // 其他操作 }catch(error){ // 異常處理 Toast.fail('加載失敗'); }finally{ loading.clear() } } } }
我們可以把上面的loading的邏輯使用裝飾器重新封裝,如下代碼
import { Toast } from 'vant' /** * loading 裝飾器 * @param {*} message 提示信息 * @param {function} errorFn 異常處理邏輯 */ export const loading = function(message = '加載中...', errorFn = function() {}) { return function(target, name, descriptor) { const fn = descriptor.value descriptor.value = async function(...rest) { const loading = Toast.loading({ message: message, forbidClick: true }) try { return await fn.call(this, ...rest) } catch (error) { // 在調(diào)用失敗,且用戶自定義失敗的回調(diào)函數(shù)時,則執(zhí)行 errorFn && errorFn.call(this, error, ...rest) console.error(error) } finally { loading.clear() } } } }
然后改造上面的組件代碼
export default { methods:{ @loading('加載中') async getData() { try{ const data = await loadData() // 其他操作 }catch(error){ // 異常處理 Toast.fail('加載失敗'); } } } }
3. 確認(rèn)框
當(dāng)你點擊刪除按鈕的時候,一般都需要彈出一個提示框讓用戶確認(rèn)是否刪除,這時候常規(guī)寫法可能是這樣的
import { Dialog } from 'vant' export default { methods: { deleteData() { Dialog.confirm({ title: '提示', message: '確定要刪除數(shù)據(jù),此操作不可回退。' }).then(() => { console.log('在這里做刪除操作') }) } } }
我們可以把上面確認(rèn)的過程提出來做成裝飾器,如下代碼
import { Dialog } from 'vant' /** * 確認(rèn)提示框裝飾器 * @param {*} message 提示信息 * @param {*} title 標(biāo)題 * @param {*} cancelFn 取消回調(diào)函數(shù) */ export function confirm( message = '確定要刪除數(shù)據(jù),此操作不可回退。', title = '提示', cancelFn = function() {} ) { return function(target, name, descriptor) { const originFn = descriptor.value descriptor.value = async function(...rest) { try { await Dialog.confirm({ message, title: title }) originFn.apply(this, rest) } catch (error) { cancelFn && cancelFn(error) } } } }
然后再使用確認(rèn)框的時候,就可以這樣使用了
export default { methods: { // 可以不傳參,使用默認(rèn)參數(shù) @confirm() deleteData() { console.log('在這里做刪除操作') } } }
是不是瞬間簡單多了,當(dāng)然還可以繼續(xù)封裝很多很多的裝飾器,因為文章內(nèi)容有限,暫時提供這三個。
裝飾器組合使用
在上面我們將類屬性上面使用裝飾器的時候,說道裝飾器可以組合使用,在Vue組件上面使用也是一樣的,比如我們希望在確認(rèn)刪除之后,調(diào)用接口時候出現(xiàn)loading,就可以這樣寫(一定要注意順序)
export default { methods: { @confirm() @loading() async deleteData() { await delete() } } }
看完上述內(nèi)容,你們對Vue中如何使用裝飾器有進一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。