Vue項目之ES6裝飾器在項目實戰(zhàn)中的應(yīng)用
前言
在面向?qū)ο螅∣OP)的設(shè)計模式中,裝飾器的應(yīng)用非常多,比如在 Java 和 Python 中,都有非常多的應(yīng)用。ES6 也新增了裝飾器的功能,本文會介紹 ES6 的裝飾器的概念、作用以及在 Vue + ElementUI 的項目實戰(zhàn)中的應(yīng)用。
裝飾模式(Decorator)
裝飾模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)。 這種模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有的類的一個包裝。 這種模式創(chuàng)建了一個裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。
優(yōu)點:
- 不需要通過創(chuàng)建子類的方式去拓展功能(不需要子類化),這樣可以避免代碼臃腫的問題
- 裝飾類的方法復(fù)用性很高
- 不會影響到原對象的代碼結(jié)構(gòu)
ES6 也開始有了裝飾器,寫法與其他語言的寫法保持了統(tǒng)一,就是使用@ + 函數(shù)名的方式
ES6 裝飾器
關(guān)于ES6 裝飾器的用法可以參考阮老師的 ECMAScript 6 入門,這里從中展示一下兩種用法。
- 類的裝飾
裝飾器方法:給對象添加一個 isTestable
屬性
function testable(isTestable) { return function(target) { target.isTestable = isTestable; } }
使用的時候直接用 @ + 函數(shù)名,就可以為對象添加 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);
從上面兩個簡單例子可以看出,裝飾器應(yīng)用在類和類的方法上時非常的方便,有幾個優(yōu)點:
- 語義化,可以非常清晰看出裝飾器為對象添加了什么功能
- 裝飾器不改變原對象的結(jié)構(gòu),原對象代碼簡潔、易維護。
接下來將介紹一下我在 Vue 項目中,利用裝飾器的功能做的代碼優(yōu)化。
裝飾器應(yīng)用
目前我們了解到,裝飾器可以用來注釋或修改類和類方法。而且裝飾器使用起來非常靈活,只需要用@ + 函數(shù)名就可以修改類,可以改善代碼結(jié)構(gòu)。那么在做項目的時候,編寫代碼時是否有些功能可以抽象成裝飾器,提高復(fù)用性和改善代碼結(jié)構(gòu)。
下面的例子所用到的技術(shù)棧是 Vue2 + ElementUI + TypeScript + vue-property-decorator
Validate
在很多 UI 組件庫中,都有表單組件,其中表單重要的功能之一就是表單校驗,以 ElementUI 的 form 舉例,首先校驗表單是否通過,如果通過,就將表單數(shù)據(jù)提交給后臺,
完整的代碼如下:
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; } }); },
這里有幾個問題:
- 這個代碼嵌套到第三層才開始進入主邏輯代碼,嵌套太多了,萬一在主要業(yè)務(wù)邏輯代碼還有很多嵌套,看起來就十分的難受。
- 記不住,在實際開發(fā)中,一般不回特意去記觸發(fā)校驗的寫法,通常要去找文檔或者找別人的代碼,最后抄過來
- 此功能很常用,每做一個表單都要寫一遍,重復(fù)寫這份代碼
分析上面代碼,其實主要的代碼是在 if (valid)
的條件下,而觸發(fā)表單校驗的代碼是可以抽象出來的,因為它非常常用,而且這部分代碼是無關(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ā)校驗的代碼封裝在此 (this as any).$refs[refName].validate((valid: boolean) => { if (valid) { fn.call(this, ...args); // 在這里調(diào)用“被裝飾的方法” } else { console.log('error submit!!'); return false; } }); }; }; }
然后在使用的時候就非常簡單了,只需要在提交方法上方寫上 @Validate('refName')
,傳入表單組件的 ref 名,就可以實現(xiàn)了觸發(fā)表單校驗的功能,這樣不但大大優(yōu)化了代碼結(jié)構(gòu),而且使用起來非常簡單,?? 再也不用擔心我記不住怎么寫了。
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ù)雜的場景,減少嵌套和非業(yè)務(wù)邏輯的代碼,可以讓業(yè)務(wù)邏輯代碼更加清晰。
CatchError
在寫代碼的時候經(jīng)常用 try catch
去捕獲程序中的錯誤,但是 try catch
會加深了代碼嵌套層級,而且很常用,我們可以將 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
的嵌套了,而且錯誤也被捕獲到了,CatchError
的命名也很好理解,并且你可以統(tǒng)一處理捕獲到的錯誤。
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 兩種裝飾器,分別是表單校驗和錯誤捕捉的作用,而表單提交都有用到這兩種功能,裝飾器可以同時滿足它,因為一個方法可以擁有多個裝飾器。
如果同一個方法有多個裝飾器,會像剝洋蔥一樣,先從外到內(nèi)進入,然后由內(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ù)邏輯代碼了!而其他的功能作為裝飾器引入并作用到這個方法上。而且這些裝飾功能就像是個語法糖一樣,當我下次還需要用到的時候,只需要引用在我的方法上即可,十分方便。
Confirmation
確認消息:提示用戶確認其已經(jīng)觸發(fā)的動作,并詢問是否進行此操作時會用到此對話框。這種場景十分常見,在點擊提交表單確認、點擊刪除的時候,都會彈出提示框,在用戶點擊確認后,再提交。其中最終我們只需要點擊確認那一下按鈕提交的功能,其他的功能屬于交互功能。
代碼實現(xiàn):
<template> <div> <el-button type="text" @click="handleDelete" >點擊打開 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>
同樣的問題,實現(xiàn)這樣一個通用的功能,需要太多與業(yè)務(wù)邏輯無關(guān)的代碼了。代碼嵌套很深,主要業(yè)務(wù)邏輯代碼不夠清晰可見。因此對于這種通用的功能,也可以抽離出去作為裝飾器。
同樣我們把 confirm 的功能封裝起來,instance.confirmButtonLoading
控制的是按鈕的 loading,done()
是關(guān)閉彈窗的方法,這兩個功能很好用,因此我們把 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" >點擊打開 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>
最終這樣減少了很多代碼和嵌套,并且將這個常用的功能封裝起來了,以后遇到可以直接復(fù)用起來,使用也很方便,只需要引入并傳入 title 和 message 就可以了。
總結(jié)
裝飾器可用于給類和類的方法添加功能,且不會影響原對象的結(jié)構(gòu)。可用于拓展原對象的功能。在實際業(yè)務(wù)項目開發(fā)中,常常會把功能性代碼和業(yè)務(wù)性代碼耦合在一起,可以將功能性代碼抽象出去,作為裝飾器裝飾業(yè)務(wù)功能代碼,這樣就能專注于業(yè)務(wù)組件的業(yè)務(wù)邏輯代碼,優(yōu)化代碼結(jié)構(gòu),減少代碼嵌套等。
到此這篇關(guān)于Vue項目之ES6裝飾器在項目實戰(zhàn)中應(yīng)用的文章就介紹到這了,更多相關(guān)Vue ES6裝飾器在實戰(zhàn)應(yīng)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
代碼示例已經(jīng)發(fā)布到 github 上了代碼地址 ,也可以把項目來下來跑跑看。
參考
相關(guān)文章
Vue使用echarts散點圖在區(qū)域內(nèi)標點
這篇文章主要為大家詳細介紹了Vue使用echarts散點圖在區(qū)域內(nèi)標點,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03