Vue框架TypeScript裝飾器使用指南

CharlesYu01發表於2019-02-17

關鍵詞 裝飾器 Decorator 超程式設計

前言

裝飾器是一種特殊型別的宣告,它能夠被附加到類宣告,方法, 訪問符,屬性或引數上。 裝飾器使用 @expression這種形式,expression求值後必須為一個函式,它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入。

本篇先從專案的巨集觀角度來總結一下Decorator如何組織。

我會持續分享一些知識整理,如果文章對您有幫助記得點贊鼓勵一下哦?~,也可以通過郵件方式聯絡我

文章列表: https://juejin.im/user/5bc8b9bf6fb9a05d1658bbbf/posts

郵箱地址: 595506910@qq.com

目錄

  • 主要的Decorator依賴
    • vue-class-component
    • vuex-class
    • vue-property-decorator
    • core-decorators
  • 自定義Decorator示例
  • 哪些功能適合用Decorator實現
  • Decorator實現小Tips
  • See also

主要的Decorator依賴

vue-cli3 預設支援Decorator, 年初重寫了一個design庫主要依賴官方和社群提供的Decorator來實現的元件。 Decorator可以非侵入的裝飾類、方法、屬性,解耦業務邏輯和輔助功能邏輯。以下是主要的三方Decorator元件,有了這些元件常用的Vue特性就可以全部轉成Decorator風格了。

vue-class-component

  • @Component 如果您在宣告元件時更喜歡基於類的 API,則可以使用官方維護的 vue-class-component 裝飾器
  • 實時計算computed屬性, get computedMsg () {return 'computed ' + this.msg}
  • 生命週期鉤子 mounted () {this.greet()}

vuex-class

讓Vuex和Vue之間的繫結更清晰和可擴充

  • @State
  • @Getter
  • @Action
  • @Mutation

vue-property-decorator

這個元件完全依賴於vue-class-component.並且參考vuex-class元件,它具備以下幾個屬性:

  • @Component (完全繼承於vue-class-component)
  • @Prop:父子元件之間值的傳遞
  • @Emit:子元件向父元件傳遞
  • @Model:雙向繫結
  • @Watch:觀察的表示式變動
  • @Provide:在元件巢狀層級過深時。父元件不便於向子元件傳遞資料。就把資料通過Provide傳遞下去。
  • @Inject:然後子元件通過Inject來獲取
  • Mixins (在vue-class-component中定義);

建議專案中只引用vue-property-decorator就可以了,避免@Component從vue-class-component和vue-property-decorator兩個中隨意引用。

core-decorators

  • @readonly
  • @autobind : TSX 回撥函式中的 this,類的方法預設是不會繫結 this 的,可以使用autobind裝飾器
  • @override

總結一下主要就分成這三類:

  • 修飾類的:@Component、@autobind;
  • 修飾方法的:@Emit、@Watch、@readonly、@override;
  • 修飾屬性的:@Prop、@readonly;

以上引用方法等詳系內容可檢視官方文件。要想完整的發揮Decorator的價值就需要根據需要自定義一些裝飾器。下面自定義部分就來實現一個記錄日誌功能的裝飾器。

約定優於配置

props: {
    name: {
        type: string,
        defalut: ''
    }
}

vs

@Prop name:string
複製程式碼

傳統的寫法就是配置式的宣告,下面這個優雅多了,型別和作用一目瞭然。

自定義Decorator示例

  • @Logger,1.Logger日誌裝飾器通常是修飾方法,Decorater則是在執行時就被觸發了,日誌記錄是在方法被呼叫時觸發,示例中通過自動釋出事件實現呼叫時觸發。2.為增加日誌記錄的靈活性,需要通過暴露鉤子函式的方式來改變日誌記錄的內容。

期望的日誌格式

{
    "logId":"", // 事件Id
    "input":"", // 方法輸入的內容
    "output":"", // 方法輸出的內容
    "custom":"" // 自定義的日誌內容
}
複製程式碼

實現

export function Logger(logId?: string, hander?: Function) {
    const loggerInfo =Object.seal({logId:logId, input:'',output:'', custom: ''});
    const channelName = '__logger';
    const msgChannel = postal.channel(channelName);
    msgChannel.subscribe(logId, logData => {
        // 根據業務邏輯來處理日誌
        console.log(logData);
    });

    return function (target: any,
        key: string,
        descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
            const oldValue = descriptor.value
            descriptor.value = function () {
                const args: Array<any> = [];
                for (let index in arguments) {
                args.push(arguments[index]);
                }
                loggerInfo.input = `${key}(${args.join(',')})`;
                // 執行原方法
                const value = oldValue.apply(this, arguments);
                loggerInfo.output = value;
                hander && (loggerInfo.custom = hander(loggerInfo.input, loggerInfo.output) || '');
                // 被呼叫時,會自動發出一個事件
                msgChannel.publish(logId, loggerInfo);
            }
            return descriptor
    }
}
複製程式碼

使用

// 直接使用非常簡潔
@Logger('event_get_detial1')
getDetial(id?: string, category?: string) {
    return "詳細內容";
}
// 或者使用自定義,讓日誌和業務邏輯分離
@Logger('event_get_detial2', (input, output) => {
        return '我是自定義內容';
})
getDetial2(id?: string, category?: string) {
    return "詳細內容";
}
...
<button @click="getDetial2('1000', 'a')">獲取詳情</button>
複製程式碼

效果: {logId: "event_get_detial2", input: "getDetial2(1000,a)", output: "詳細內容", custom: "我是自定義內容"}, 每次點選按鈕都會觸發一次。

TODO: 這裡還需要對輸入引數和輸出引數中的引用資料型別做處理。

同時還需要掌握:裝飾器工廠、裝飾器組合、裝飾器求值、引數裝飾器、後設資料

哪些功能適合用Decorator實現

  • 官網和社群提供的這些Decorator, 可以作為自己框架的底層設計。
  • 日誌功能全域性都得用,呼叫方法基本一致,是最適合使用裝飾器來實現,並且每個專案的日誌記錄各有差異,最適合自定義這部分。

Decorator實現小Tips

  • 考慮下各類Decorator疊加和共存的問題,可以參考官閘道器於裝飾器組合描述

  • Decorator 的目標是在原有功能基礎上,新增功能,切忌覆蓋原有功能

  • 類裝飾器不能用在宣告檔案中( .d.ts),也不能用在任何外部上下文中(比如declare的類)

  • 裝飾器只能用於類和類的方法,不能用於函式,因為存在函式提升。類是不會提升的,所以就沒有這方面的問題。

  • 注意遷移速度、避免一口吃成胖子的做法

  • 不要另起爐灶對主流庫建立Decorator庫,主流庫維護成本很高還是得有官方來維護,為保證質量不使用個人編寫的Decorator庫。自己在建立Decorator庫時也要有這個意識,僅做一些有必要自定義的。

  • Decorator 不是管道模式,decorator之間不存在互動,所以必須注意保持decorator獨立性、透明性

  • Decorator 更適用於非業務功能需求

  • 確定 decorator 的用途後,切記執行判斷引數型別

  • decorator 針對每個裝飾目標,僅執行一次

See also

  • ts官網裝飾器說明【深入閱讀】 https://www.tslang.cn/docs/handbook/decorators.html
  • Decorator作用在不同的宣告型別target、key說明 https://segmentfault.com/a/1190000010019412
  • JS 裝飾器(Decorator)場景實戰 https://zhuanlan.zhihu.com/p/30487077
  • Decorators in ES7 https://zhuanlan.zhihu.com/p/20139834
  • vue元件 風格指南 https://cn.vuejs.org/v2/style-guide/index.html
  • 使用Typescript封裝一款裝飾器風格的Web框架 https://zhuanlan.zhihu.com/p/33766385
  • Axios 裝飾器實現 1.https://github.com/glangzh/retrofit-cjs 2.https://www.npmjs.com/package/axios-decorator
  • 宣告計算屬性用的。 https://my.oschina.net/lpcysz/blog/2980469
  • autobind https://blog.csdn.net/liubiggun/article/details/82147796
  • vue-class-component原始碼閱讀 https://www.jianshu.com/p/cfed56d630a4
  • 函式和方法的區別 https://www.imooc.com/article/19709?block_id=tuijian_wz
  • 很多人整理過使用方法 https://www.cnblogs.com/navysummer/p/9689851.html 舉個例子 join(separator?: string): string;
  • vue原始碼解析系列-compute實現機制 https://segmentfault.com/a/1190000009862528

相關文章