寫一個Angular裝飾器(Decorators)- 學于ng-zorro源碼

寫一個Angular裝飾器(Decorators)- 學于ng-zorro源碼

本文將圍繞:

1. what is it?
2. why use it?
3. how to use it?

業(yè)務需求場景提供(參考):

  1. 格式化 @Input

  2. 動態(tài)校驗 @Input 傳入的值類型

  3. ...

代碼完整示例:

本文示例代碼

1. What is it?

先看看TypeScript官方對Decorators 解釋:

裝飾器是一種特殊類型的聲明洼专,它能夠被附加到類聲明踱葛,方法荒典, 訪問符栓撞,屬性或參數(shù)上澈歉。 裝飾器使用 @expression這種形式顺献,expression求值后必須為一個函數(shù)只祠,它會在運行時被調(diào)用,被裝飾的聲明信息做為參數(shù)傳入唐片。

例如Angular中的 @Input

  @Input 
  value: string;

這樣看是不是清晰了點,實際上裝飾器的作用是對設計模式中的裝飾者模式的實現(xiàn)涨颜。

裝飾者模式:

指在不必 改變原類文件 或者 使用繼承 的情況下费韭,動態(tài)擴展對象的功能

裝飾器分為:

類裝飾器: (target) => {}

方法裝飾器:(target, methodName: string, descriptor: PropertyDescriptor) => {}

參數(shù)裝飾器:(target, methodName: string, paramIndex: number) => {}

屬性裝飾器:(target, propertyName: string) => {}

2. Why use it?

正如前面提供的兩個業(yè)務場景,我們?nèi)〉谝粋€使用裝飾器解決下該問題庭瑰。

思路: 我們需要控制一個組件loading的狀態(tài)星持,那么設isLoading為true或者false,來控制開關。

OK弹灭,我們開始寫代碼:

Child Comonent:

// html
  <div *ngIf="isLoading">isLoading 為 true</div>
  <div *ngIf="!isLoading">isLoading 為 false</div>

//ts
  @Input() isLoading = false;

Parent Component

  <app-child [isLoading]="'false'"></app-child>

Result

格式化前

我傳了'false'但是為什么顯示的是true督暂,細心的人可以發(fā)現(xiàn)是因為傳了一個不規(guī)范的字段,正如我寫的'false'穷吮,傳的是一個string逻翁,進行轉譯!!'false',我們可以看到它是一個true捡鱼。

除了誤傳'false'八回,還會有開發(fā)者傳入一個value,有值或無值做一個開關控制驾诈。

作為組件的提供者缠诅,希望避免這樣的傳值,我們可以對傳入的值進行格式化乍迄,可如下操作:

 @Input()
  get isLoading() {
    return this._isLoading;
  }
  set isLoading(v) {
    this._isLoading = coerceBooleanProperty(v);
  }
  _isLoading = false;

coerceBooleanProperty:是@angular/cdk/coercion中的一個方法管引,主要用途將傳入的值進行強制轉化為boolean.

我們再看一下頁面顯示:

利用setter 格式化后

可以發(fā)現(xiàn)這個成功解決我們的問題,但是如果每個地方都要寫一個setter 和getter 闯两,并且創(chuàng)建一個新的變量_isLoading褥伴,那也太麻煩了,這時候裝飾器就派上用場了生蚁。

3. How to use it?

首先我們先看一下簡單的裝飾器:

//類裝飾器
function Man(target: Function):void{
    target.prototype.__isMan = true;
}

//使用類裝飾器
@Man
class Person{
    constructor(){}
}

//測試裝飾后的結果
let person = new Person();
console.log(person.__isMan);//true

這是一個類裝飾器噩翠,還有其他幾種,前面可以看到邦投,具體寫法就舉例伤锚。但是如果我們在寫一個項目,還會遇到方法,參數(shù)屯援,屬性修飾器猛们,所以我們需要統(tǒng)一一下方法。

在這之前我們先看看ts官方給的一個提示:

如果我們要定制一個修飾器如何應用到一個聲明上狞洋,我們得寫一個裝飾器工廠函數(shù)弯淘。 裝飾器工廠就是一個簡單的函數(shù),它返回一個表達式吉懊,以供裝飾器在運行時調(diào)用庐橙。

代碼來源于ng-zorro源碼:

因此我們需要寫一個通用的工廠函數(shù)。

工廠函數(shù)拆解:

一個通用裝飾器工廠函數(shù)的外殼如下(詳細在文章后面):



// 因為我們需要一個通用的裝飾器工廠函數(shù)借嗽,所以我們傳入兩個參數(shù)name和fallback
// name: 裝飾器名字(如:InputBoolean)
// fallback:調(diào)用需要執(zhí)行的函數(shù)(如:toBoolean)

// 其次因為裝飾器分為5種态鳖,但是我們主要針對屬性和方法裝飾器,因此
// originalDescriptor可為空(屬性裝飾器不包括該值恶导,可看前面裝飾器的介紹)

function propDecoratorFactory(name:string,fallback:function){
 
  return propDecorator(
    target: any, 
    propName: string, 
    originalDescriptor?: TypedPropertyDescriptor<any>
  ){
      ... 
  }
}

通用裝飾器工廠函數(shù)完整版(個人的理解在代碼注釋中):

function propDecoratorFactory<T, D>(name: string, fallback: (v: T) => D): (target: any, propName: string) => void {
  function propDecorator(target: any, propName: string, originalDescriptor?: TypedPropertyDescriptor<any>): any {

    // 創(chuàng)建私有名字
    const privatePropName = `$$__${propName}`;

    // 首先判斷是否設置過該值浆竭,防止重復操作
    if (Object.prototype.hasOwnProperty.call(target, privatePropName)) {
      console.warn(`The prop "${privatePropName}" is already exist, it will be overrided by ${name} decorator.`);
    }

    // 利用Object.defineProperty創(chuàng)建監(jiān)聽
    Object.defineProperty(target, privatePropName, {
      configurable: true,
      writable: true
    });

    // 觸發(fā)情況:訪問時會觸發(fā)getter,設值時會觸發(fā)setter
    // 具體不解釋,可以看es6對Object.defineProperty的解釋
    // 題外話惨寿,可以一同可看proxy邦泄,proxy對Object.defineProperty進行了優(yōu)化。
    return {
      get(): string {
        // get 雷同于Setter解釋
        return originalDescriptor && originalDescriptor.get
          ? originalDescriptor.get.bind(this)()
          : this[privatePropName];
      },
      set(value: T): void {
        // 首先判斷修飾器下是否存在Setter裂垦,setter存在于originalDescriptor中顺囊,我們將先進行規(guī)定的format
        // 然后將format值傳入其中,調(diào)用set 方法繼續(xù)按照開發(fā)者的要求執(zhí)行
        // 所以如下一個使用的執(zhí)行順序是:
        // @Input @InputBoolean set value(v){ this._v = v }
        //  1.調(diào)用Object.defineProperty對target上的$$__value進行監(jiān)聽缸废,創(chuàng)建setter + getter
        //  2.setter(內(nèi)) 觸發(fā) ,對傳入的值進行格式化(即:調(diào)用傳入的fallback(v))包蓝,拿到格式化的值后format_v
        //  3.調(diào)用originalDescriptor 中的setter(外:即開發(fā)者創(chuàng)建)方法,傳入格式化的值format_v進行做后續(xù)處理
        // 因此此處只會對傳入的值進行格式化企量,不會做任何操作。
        if (originalDescriptor && originalDescriptor.set) {
          originalDescriptor.set.bind(this)(fallback(value));
        }
        this[privatePropName] = fallback(value);
      }
    };
  }

  return propDecorator;
}

OK亡电,定義完工廠函數(shù)之后届巩,我們需要定義裝飾器入口(InputBoolean),和格式化的函數(shù)(toBoolean)

// 裝飾器入口
export function InputBoolean(): any {
  return propDecoratorFactory('InputBoolean', toBoolean);
}

// 格式化boolean函數(shù)
export function toBoolean(value: boolean | string): boolean {
  return coerceBooleanProperty(value);
}

然后使用:

  @Input() @InputBoolean() isLoading = false;

可以看到最終結果:

利用裝飾器 格式化后

以上是我學習后的記錄份乒,如若有問題恕汇,歡迎吐槽~~就稍微稍微輕一點點~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市或辖,隨后出現(xiàn)的幾起案子瘾英,更是在濱河造成了極大的恐慌,老刑警劉巖颂暇,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缺谴,死亡現(xiàn)場離奇詭異,居然都是意外死亡耳鸯,警方通過查閱死者的電腦和手機湿蛔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門膀曾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阳啥,你說我怎么就攤上這事添谊。” “怎么了察迟?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵斩狱,是天一觀的道長。 經(jīng)常有香客問我扎瓶,道長喊废,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任栗弟,我火速辦了婚禮污筷,結果婚禮上,老公的妹妹穿的比我還像新娘乍赫。我一直安慰自己瓣蛀,他們只是感情好,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布雷厂。 她就那樣靜靜地躺著惋增,像睡著了一般。 火紅的嫁衣襯著肌膚如雪改鲫。 梳的紋絲不亂的頭發(fā)上诈皿,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機與錄音像棘,去河邊找鬼稽亏。 笑死,一個胖子當著我的面吹牛缕题,可吹牛的內(nèi)容都是我干的截歉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼烟零,長吁一口氣:“原來是場噩夢啊……” “哼瘪松!你這毒婦竟也來了?” 一聲冷哼從身側響起锨阿,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宵睦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后墅诡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壳嚎,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诬辈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酵使。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖焙糟,靈堂內(nèi)的尸體忽然破棺而出口渔,到底是詐尸還是另有隱情,我是刑警寧澤穿撮,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布缺脉,位于F島的核電站,受9級特大地震影響悦穿,放射性物質(zhì)發(fā)生泄漏攻礼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一栗柒、第九天 我趴在偏房一處隱蔽的房頂上張望礁扮。 院中可真熱鬧,春花似錦瞬沦、人聲如沸太伊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僚焦。三九已至,卻和暖如春曙痘,著一層夾襖步出監(jiān)牢的瞬間芳悲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工边坤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留名扛,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓惩嘉,卻偏偏與公主長得像罢洲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子文黎,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,831評論 0 38
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,130評論 0 21
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔,今天18年5月份再次想寫文章殿较,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,767評論 2 9
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,238評論 0 4
  • 〖畫者基本信息〗(包括性別耸峭、年齡、職業(yè)淋纲、家庭情況及畫者困惑等) 男生13周歲劳闹,人際關系好,現(xiàn)在成績不太好,懂事本涕,勤...
    Ms小明閱讀 138評論 0 0