Vue項(xiàng)目之ES6裝飾器在項(xiàng)目實(shí)戰(zhàn)中的應(yīng)用
前言
在面向?qū)ο螅∣OP)的設(shè)計(jì)模式中,裝飾器的應(yīng)用非常多,比如在 Java 和 Python 中,都有非常多的應(yīng)用。ES6 也新增了裝飾器的功能,本文會(huì)介紹 ES6 的裝飾器的概念、作用以及在 Vue + ElementUI 的項(xiàng)目實(shí)戰(zhàn)中的應(yīng)用。
裝飾模式(Decorator)
裝飾模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。 這種模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有的類的一個(gè)包裝。 這種模式創(chuàng)建了一個(gè)裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。
優(yōu)點(diǎn):
- 不需要通過創(chuàng)建子類的方式去拓展功能(不需要子類化),這樣可以避免代碼臃腫的問題
- 裝飾類的方法復(fù)用性很高
- 不會(huì)影響到原對(duì)象的代碼結(jié)構(gòu)
ES6 也開始有了裝飾器,寫法與其他語言的寫法保持了統(tǒng)一,就是使用@ + 函數(shù)名的方式
ES6 裝飾器
關(guān)于ES6 裝飾器的用法可以參考阮老師的 ECMAScript 6 入門,這里從中展示一下兩種用法。
- 類的裝飾
裝飾器方法:給對(duì)象添加一個(gè) isTestable 屬性
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}使用的時(shí)候直接用 @ + 函數(shù)名,就可以為對(duì)象添加 isTestable 屬性了
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true- 方法的裝飾
日志裝飾器
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}使用裝飾器
class Math {
@log
add(a, b) {
return a + b;
}
}
const math = new Math();
// Calling add with arguments
math.add(2, 4);從上面兩個(gè)簡單例子可以看出,裝飾器應(yīng)用在類和類的方法上時(shí)非常的方便,有幾個(gè)優(yōu)點(diǎn):
- 語義化,可以非常清晰看出裝飾器為對(duì)象添加了什么功能
- 裝飾器不改變?cè)瓕?duì)象的結(jié)構(gòu),原對(duì)象代碼簡潔、易維護(hù)。
接下來將介紹一下我在 Vue 項(xiàng)目中,利用裝飾器的功能做的代碼優(yōu)化。
裝飾器應(yīng)用
目前我們了解到,裝飾器可以用來注釋或修改類和類方法。而且裝飾器使用起來非常靈活,只需要用@ + 函數(shù)名就可以修改類,可以改善代碼結(jié)構(gòu)。那么在做項(xiàng)目的時(shí)候,編寫代碼時(shí)是否有些功能可以抽象成裝飾器,提高復(fù)用性和改善代碼結(jié)構(gòu)。
下面的例子所用到的技術(shù)棧是 Vue2 + ElementUI + TypeScript + vue-property-decorator
Validate
在很多 UI 組件庫中,都有表單組件,其中表單重要的功能之一就是表單校驗(yàn),以 ElementUI 的 form 舉例,首先校驗(yàn)表單是否通過,如果通過,就將表單數(shù)據(jù)提交給后臺(tái),

完整的代碼如下:
submitForm() {
this.$refs['formName'].validate(async (valid) => {
if (valid) {
try {
// 調(diào)用接口
await this.handleTest();
this.$message.success('Submit Successfully!')
} catch(error) {
console.log(error);
}
} else {
console.log('error submit!!');
return false;
}
});
},這里有幾個(gè)問題:
- 這個(gè)代碼嵌套到第三層才開始進(jìn)入主邏輯代碼,嵌套太多了,萬一在主要業(yè)務(wù)邏輯代碼還有很多嵌套,看起來就十分的難受。
- 記不住,在實(shí)際開發(fā)中,一般不回特意去記觸發(fā)校驗(yàn)的寫法,通常要去找文檔或者找別人的代碼,最后抄過來
- 此功能很常用,每做一個(gè)表單都要寫一遍,重復(fù)寫這份代碼
分析上面代碼,其實(shí)主要的代碼是在 if (valid) 的條件下,而觸發(fā)表單校驗(yàn)的代碼是可以抽象出來的,因?yàn)樗浅3S?,而且這部分代碼是無關(guān)業(yè)務(wù)邏輯的。抽象出去,可以更好地關(guān)注到業(yè)務(wù)邏輯代碼。
export function Validate(refName: string) {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
const fn = target[name]; // 被裝飾的方法
descriptor.value = function (...args: any[]) {
// 將觸發(fā)校驗(yàn)的代碼封裝在此
(this as any).$refs[refName].validate((valid: boolean) => {
if (valid) {
fn.call(this, ...args); // 在這里調(diào)用“被裝飾的方法”
} else {
console.log('error submit!!');
return false;
}
});
};
};
}然后在使用的時(shí)候就非常簡單了,只需要在提交方法上方寫上 @Validate('refName'),傳入表單組件的 ref 名,就可以實(shí)現(xiàn)了觸發(fā)表單校驗(yàn)的功能,這樣不但大大優(yōu)化了代碼結(jié)構(gòu),而且使用起來非常簡單,?? 再也不用擔(dān)心我記不住怎么寫了。
import { Validate } from '@/utils/decorator'
export default class TestForm extends Vue {
@Validate('formName')
async submitForm() {
try {
// 調(diào)用接口
await this.handleTest();
this.$message.success('Submit Successfully!')
} catch(error) {
console.log(error);
}
}
}這樣是不是好多了!特別是在業(yè)務(wù)邏輯非常復(fù)雜的場(chǎng)景,減少嵌套和非業(yè)務(wù)邏輯的代碼,可以讓業(yè)務(wù)邏輯代碼更加清晰。
CatchError
在寫代碼的時(shí)候經(jīng)常用 try catch 去捕獲程序中的錯(cuò)誤,但是 try catch 會(huì)加深了代碼嵌套層級(jí),而且很常用,我們可以將 try catch 的部分抽象出去,作為裝飾器的功能。
比如原來的代碼是這樣的:
export default class TestForm extends Vue {
async submitForm() {
try {
await this.handleTest();
this.$message.success('Submit Successfully!')
} catch(error) {
console.log(error);
}
}
}將 try catch 的功能作為裝飾函數(shù)
export function CatchError() {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
const fn = target[name];
descriptor.value = async function (...args: any[]) {
try {
await fn.call(this, ...args);
} catch (error) {
console.log('error', error);
}
};
return descriptor;
};
}使用起來后,就少了一層 try catch 的嵌套了,而且錯(cuò)誤也被捕獲到了,CatchError 的命名也很好理解,并且你可以統(tǒng)一處理捕獲到的錯(cuò)誤。
import { CatchError } from '@/utils/decorator'
export default class TestForm extends Vue {
@CatchError()
async submitForm() {
await this.handleTest();
this.$message.success('Submit Successfully!')
}
}現(xiàn)在目前有 Validate 和 CatchError 兩種裝飾器,分別是表單校驗(yàn)和錯(cuò)誤捕捉的作用,而表單提交都有用到這兩種功能,裝飾器可以同時(shí)滿足它,因?yàn)橐粋€(gè)方法可以擁有多個(gè)裝飾器。
如果同一個(gè)方法有多個(gè)裝飾器,會(huì)像剝洋蔥一樣,先從外到內(nèi)進(jìn)入,然后由內(nèi)向外執(zhí)行。
那么提交表單的函數(shù)最終可以被裝飾器優(yōu)化成這樣:
import { CatchError, Validate } from '@/utils/decorator'
export default class TestForm extends Vue {
@CatchError()
@Validate('ruleForm')
async submitForm() {
await this.handleTest();
this.$message.success('Submit Successfully!')
}
}發(fā)現(xiàn)了沒有,提交表單的代碼中,完完全全只有業(yè)務(wù)邏輯代碼了!而其他的功能作為裝飾器引入并作用到這個(gè)方法上。而且這些裝飾功能就像是個(gè)語法糖一樣,當(dāng)我下次還需要用到的時(shí)候,只需要引用在我的方法上即可,十分方便。
Confirmation
確認(rèn)消息:提示用戶確認(rèn)其已經(jīng)觸發(fā)的動(dòng)作,并詢問是否進(jìn)行此操作時(shí)會(huì)用到此對(duì)話框。這種場(chǎng)景十分常見,在點(diǎn)擊提交表單確認(rèn)、點(diǎn)擊刪除的時(shí)候,都會(huì)彈出提示框,在用戶點(diǎn)擊確認(rèn)后,再提交。其中最終我們只需要點(diǎn)擊確認(rèn)那一下按鈕提交的功能,其他的功能屬于交互功能。

代碼實(shí)現(xiàn):
<template>
<div>
<el-button type="text" @click="handleDelete"
>點(diǎn)擊打開 Message Box 提示是否刪除</el-button
>
</div>
</template>
<script>
import { Vue, Component } from "vue-property-decorator";
@Component
export default class DecoratorTest extends Vue {
handleDelete() {
this.$confirm("此操作將永久刪除該文件, 是否繼續(xù)?", "提示", {
confirmButtonText: "確定",
cancelButtonText: "取消",
type: "warning",
showCancelButton: true,
beforeClose: (action, instance, done) => {
if (action === "confirm") {
instance.confirmButtonLoading = true;
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 2000);
} else {
done();
}
},
}).then(() => {
this.$message({
type: "success",
message: "刪除成功!",
});
});
}
}
</script>同樣的問題,實(shí)現(xiàn)這樣一個(gè)通用的功能,需要太多與業(yè)務(wù)邏輯無關(guān)的代碼了。代碼嵌套很深,主要業(yè)務(wù)邏輯代碼不夠清晰可見。因此對(duì)于這種通用的功能,也可以抽離出去作為裝飾器。
同樣我們把 confirm 的功能封裝起來,instance.confirmButtonLoading 控制的是按鈕的 loading,done() 是關(guān)閉彈窗的方法,這兩個(gè)功能很好用,因此我們把 instance 和 done 作為參數(shù)傳給被裝飾的方法。
import Vue from "vue";
interface ConfirmationConfig {
title: string;
message: string;
// eslint-disable-next-line @typescript-eslint/ban-types
options?: object;
type?: string;
}
export function Confirmation(config: ConfirmationConfig) {
return function (target: any, name: string, descriptor: PropertyDescriptor) {
const fn = target[name];
let _instance: any = null;
descriptor.value = function (...args: any[]) {
Vue.prototype
.$confirm(
config.message,
config.title,
Object.assign(
{
beforeClose: (action: string, instance: any, done: any) => {
_instance = instance;
if (action === "confirm") {
instance.confirmButtonLoading = true;
fn.call(this, instance, done, ...args);
} else {
done();
}
},
},
config.options || {}
)
)
.then(() => {
_instance.confirmButtonLoading = false;
});
};
return descriptor;
};
}完成封裝 confirm 之后,這么使用即可:
<template>
<div>
<el-button type="text" @click="handleDelete"
>點(diǎn)擊打開 Message Box 提示是否刪除</el-button
>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Confirmation } from "@/utils/decorator";
@Component
export default class DecoratorTest extends Vue {
@Confirmation({
title: "提示",
message: "此操作將永久刪除該文件, 是否繼續(xù)?",
})
handleDelete(instance: any, done: any) {
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
this.$message({
type: "success",
message: "刪除成功!",
});
}, 300);
}, 2000);
}
}
</script>最終這樣減少了很多代碼和嵌套,并且將這個(gè)常用的功能封裝起來了,以后遇到可以直接復(fù)用起來,使用也很方便,只需要引入并傳入 title 和 message 就可以了。
總結(jié)
裝飾器可用于給類和類的方法添加功能,且不會(huì)影響原對(duì)象的結(jié)構(gòu)??捎糜谕卣乖瓕?duì)象的功能。在實(shí)際業(yè)務(wù)項(xiàng)目開發(fā)中,常常會(huì)把功能性代碼和業(yè)務(wù)性代碼耦合在一起,可以將功能性代碼抽象出去,作為裝飾器裝飾業(yè)務(wù)功能代碼,這樣就能專注于業(yè)務(wù)組件的業(yè)務(wù)邏輯代碼,優(yōu)化代碼結(jié)構(gòu),減少代碼嵌套等。
到此這篇關(guān)于Vue項(xiàng)目之ES6裝飾器在項(xiàng)目實(shí)戰(zhàn)中應(yīng)用的文章就介紹到這了,更多相關(guān)Vue ES6裝飾器在實(shí)戰(zhàn)應(yīng)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
代碼示例已經(jīng)發(fā)布到 github 上了代碼地址 ,也可以把項(xiàng)目來下來跑跑看。
參考
相關(guān)文章
vue element 生成無線級(jí)左側(cè)菜單的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue element 生成無線級(jí)左側(cè)菜單的實(shí)現(xiàn)代碼,代碼簡單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
使用vue-cli創(chuàng)建vue2項(xiàng)目的實(shí)戰(zhàn)步驟詳解
相信大部分Vue開發(fā)者都使用過vue-cli來構(gòu)建項(xiàng)目,它的確很方便,但對(duì)于很多初級(jí)開發(fā)者來說,還是要踩不少坑的,下面這篇文章主要給大家介紹了關(guān)于使用vue-cli創(chuàng)建vue2項(xiàng)目的實(shí)戰(zhàn)步驟,需要的朋友可以參考下2023-01-01

