1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Decorator 裝飾器在項(xiàng)目中的應(yīng)用

        共 7509字,需瀏覽 16分鐘

         ·

        2022-03-16 02:02

        本文首發(fā)于政采云前端團(tuán)隊(duì)博客:Decorator 裝飾器

        https://www.zoo.team/article/decorator

        前言

        大家在前端開發(fā)過程中有遇到過 @ + 方法名 這種寫法嗎?當(dāng)我第一次看到的時(shí)候,直接懵了,這是什么東東……

        遇到困難解決困難,在我的一番查找后,我知道了,原來這東西叫裝飾器,英文名叫 Decorator ,那它到底是干什么的呢?接下來就讓我跟大家說道說道~

        什么是裝飾器

        裝飾者模式

        裝飾者模式就是能夠在不改變對(duì)象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)象動(dòng)態(tài)地添加職責(zé)。打個(gè)比方,一個(gè)人在天氣冷的時(shí)候要穿棉衣,天氣熱的時(shí)候穿短袖,可無(wú)論穿什么,本質(zhì)上他還是一個(gè)人,只不過身上穿了不同的衣服。

        所以簡(jiǎn)單來說, Decorator 就是一種動(dòng)態(tài)地往一個(gè)類中添加新的行為的設(shè)計(jì)模式, 它可以在類運(yùn)行時(shí), 擴(kuò)展一個(gè)類的功能, 并且去修改類本身的屬性和方法, 使其可以在不同類之間更靈活的共用一些屬性和方法。

        @ 是針對(duì)這種設(shè)計(jì)模式的一個(gè)語(yǔ)法糖,不過目前還處于第 2 階段提案中,使用它之前需要使用 Babel 模塊編譯成 ES5 或 ES6。

        怎么使用裝飾器

        三方庫(kù)使用

        Babel 版本 ≥ 7.x

        如果項(xiàng)目的 Babel 版本大于等于 7.x,那么可以使用 @babel/plugin-proposal-decorators

        • 安裝

          npm?install?--save-dev?@babel/plugin-proposal-decorators
        • 配置 .babelrc

          {
          ??"plugins":?[
          ????["@babel/plugin-proposal-decorators",?{?"legacy":?true?}],
          ??]
          }

        Babel 版本 ≤ 6.x

        如果小于等于 6.x,則可以使用 babel-plugin-transform-decorators-legacy

        • 安裝

          npm?install?--save-dev?@babel/plugin-proposal-decorators
        • 配置 .babelrc

          {
          ????"plugins":?["transform-decorators-legacy"]
          }

        使用方法

        裝飾器的寫法是 @ + 返回裝飾器函數(shù)的表達(dá)式,所以其使用方法如下:

        @classDecorator
        class?TargetClass?{?//?類
        ??@fieldDecorator
        ??targetField?=?0;?//?類實(shí)例屬性

        ??@funDecorator
        ??targetFun()?{?}?//?類方法

        ??@accessorDecorator
        ??get?targetGetFun()?{?}?//?類訪問器
        }

        如果一個(gè)對(duì)象使用多個(gè)裝飾器,那么執(zhí)行順序是什么呢?

        function?decorator1()?{
        ??console.log('decorator1');
        ??return?function?decFn1(targetClass)?{
        ????console.log('decFn1');
        ????return?targetClass;
        ??};
        }

        function?decorator2()?{
        ??console.log('decorator2');
        ??return?function?decFn2(targetClass)?{
        ????console.log('decFn2');
        ????return?targetClass;
        ??};
        }

        執(zhí)行順序:

        打印結(jié)果:

        根據(jù)以上,我們可知,裝飾器的執(zhí)行順序?yàn)橛赏庀騼?nèi)進(jìn)入,由內(nèi)向外執(zhí)行。

        使用范圍

        根據(jù)使用方法,我們可以看出裝飾器可以應(yīng)用于以下幾種類型:

        • 類(class)
        • 類實(shí)例屬性(公共、私有和靜態(tài))
        • 類方法(公共、私有和靜態(tài))
        • 類訪問器(公共、私有和靜態(tài))

        函數(shù)的裝飾

        當(dāng)我們看完裝飾器的使用方法和使用范圍時(shí),我們發(fā)現(xiàn),裝飾器不能修飾函數(shù),那原因到底是什么呢?原因就是函數(shù)有函數(shù)提升

        var?num?=?0;
        function?add?()?{
        ??num?++;
        }
        @add
        function?fn()?{}

        在這個(gè)例子中,我們想要在執(zhí)行后讓 num 等于 1,但其實(shí)結(jié)果并不是這樣,因?yàn)楹瘮?shù)提升,實(shí)際上代碼是這樣執(zhí)行的:

        function?add?()?{
        ??num?++;
        }
        @add
        function?fn()?{}
        var?num;
        num?=?0;

        如果一定要裝飾函數(shù)的話,可以采用高階函數(shù)的形式,這篇文章主要講裝飾器,有關(guān)高階函數(shù)就不在此贅述了,不了解的小伙伴們可自行查閱資料哈~

        裝飾器原理

        根據(jù)裝飾器的使用范圍,可以把它分為兩大類:類的裝飾與類方法的裝飾,下面就讓我為大家逐個(gè)分享一下。

        類的裝飾

        傳參

        首先我們先根據(jù)一個(gè)小例子看一下裝飾器接收參數(shù)的情況:

        function?decorator(...args)?{
        ??args.forEach((arg,?index)?=>?{
        ????console.log(`參數(shù)${index}`,?arg);
        ??});
        }

        @decorator
        class?TargetClass?{?}

        console.log('targetClass:',?TargetClass);

        打印結(jié)果如下:

        看到結(jié)果,我們發(fā)現(xiàn)裝飾器只接收一個(gè)參數(shù),就是被裝飾的類定義本身。

        返回值

        我們繼續(xù)通過一個(gè)小例子來看返回值的情況:

        function?returnStr(targetClass)?{
        ??return?'hello?world~';
        }

        function?returnClass(targetClass)?{
        ??return?targetClass;
        }

        @returnStr
        class?ClassA?{?}

        @returnClass
        class?ClassB?{?}

        console.log('ClassA:',?ClassA);
        console.log('ClassB:',?ClassB);

        結(jié)果如下:

        根據(jù)結(jié)果,我們發(fā)現(xiàn)裝飾器返回什么輸出的就是什么。

        結(jié)論

        通過以上的兩個(gè)例子,我們可以得出以下這個(gè)結(jié)論:

        @decorator
        class?TargetClass?{?}

        //?等同于

        class?TargetClass?{?}
        TargetClass?=?decorator(TargetClass)?||?TargetClass;

        所以說,裝飾器的第一個(gè)參數(shù)就是要裝飾的類,它的功能就是對(duì)類進(jìn)行處理。

        類裝飾器的使用

        • 添加屬性

          因?yàn)檠b飾器接收的參數(shù)就是類定義本身,所以我們可以給類添加屬性:

          function?addAttribute(targetClass)?{
          ??targetClass.isUseDecorator?=?true;
          }

          @addAttribute
          class?TargetClass?{?}

          console.log(TargetClass.isUseDecorator);?//?true

          在這個(gè)例子中,我們定義了 addAttribute 的裝飾器,用于對(duì) TargetClass 添加 isUseDecorator 標(biāo)記,這個(gè)用法就跟 Java 中的注解比較相似,僅僅是對(duì)目標(biāo)類型打上一些標(biāo)記。

        • 返回裝飾器函數(shù)的表達(dá)式

          上面有說裝飾器的寫法是 @ + 返回裝飾器函數(shù)的表達(dá)式,也就是說,@ 后邊可以不是一個(gè)方法名,還可以是能返回裝飾器函數(shù)的表達(dá)式

          function?addAttribute(content)?{
          ??return?function?decFn(targetClass)?{
          ????targetClass.content?=?content;
          ????return?targetClass;
          ??};
          }

          @addAttribute('這是內(nèi)容~~~')
          class?TargetClass?{?}

          console.log(TargetClass.content);?//?這是內(nèi)容~~~

          我們看到 TargetClass 通過 addAttribute 的裝飾,添加了 content 這個(gè)屬性,并且可以向 addAttribute 傳參來給 content 屬性賦值,這種使用方法使裝飾器變得更加靈活。

        • 添加原型方法

          在前面的例子中我們添加的都是類的靜態(tài)屬性,但是既然裝飾器接收的參數(shù)就是類定義本身,那么它也可以通過訪問類的 prototype 屬性來添加或修改原型方法:

          function?decorator(targetClass)?{
          ??targetClass.prototype.decFun?=?function?()?{
          ????console.log('這里是裝飾器?decorator?添加的原型方法?decFun~');
          ??};
          }

          @decorator
          class?TargetClass?{?}

          const?targetClass?=?new?TargetClass();

          console.log(targetClass);
          targetClass.decFun();

          結(jié)果如下:

        以上就是類裝飾器的使用,由此我們可以得出,裝飾器還可以對(duì)類型進(jìn)行靜態(tài)標(biāo)記和方法擴(kuò)展,還挺有用的對(duì)吧~那么看到這里,小伙伴們是不是發(fā)現(xiàn)了在實(shí)際項(xiàng)目中就有類裝飾器的使用,比如 react-redux 的 connect 就是一個(gè)類裝飾器、Antd 中的 Form.create 也是一個(gè)類裝飾器。

        //?connect
        class?App?extends?React.Component?{}
        export?default?connect(mapStateToProps,?mapDispatchToProps)(App);

        //?等同于

        @connect(mapStateToProps,?mapDispatchToProps)
        export?default?class?App?extends?React.Component?{}

        //?Form.create
        const?WrappedApp?=?Form.create()(App);

        //?等同于

        @Form.create()
        class?App?extends?React.Component?{}

        類方法的裝飾

        傳參

        我們把類實(shí)例屬性、類方法、類訪問器都?xì)w到這一類中的原因其實(shí)是因?yàn)樗鼈內(nèi)齻€(gè)就是作為某個(gè)對(duì)象的屬性(實(shí)例屬性、原型方法、實(shí)例訪問器屬性),也就是說它們接收的參數(shù)是類似的:

        function?decorator(...args)?{
        ??args.forEach((arg,?index)?=>?{
        ????console.log(`參數(shù)${index}`,?arg);
        ??});
        ??console.log('****************');
        }

        class?TargetClass?{
        ??@decorator
        ??field?=?0;

        ??@decorator
        ??fn()?{?}

        ??@decorator
        ??get?getFn()?{?}
        }

        const?targetOne?=?new?TargetClass();
        console.log(targetOne.field,?Object.getOwnPropertyDescriptor(targetOne,?'field'));

        結(jié)果如下:

        根據(jù)結(jié)果我們發(fā)現(xiàn),類方法裝飾器接收了三個(gè)參數(shù):類定義對(duì)象、實(shí)例屬性/方法/實(shí)例訪問器屬性名、屬性操作符。眼熟吧,沒錯(cuò),它與 Object.defineProperty() 接收的參數(shù)很像。

        Object.defineProperty(obj, props, descriptor)

        Object.defineProperty() 的作用就是直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象。該方法一共接收三個(gè)參數(shù):

        • 要定義屬性的對(duì)象(obj)
        • 要定義或修改的屬性名或 Symbol (props)
        • 要定義或修改的屬性描述符(descriptor)

        而對(duì)象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符存取描述符數(shù)據(jù)描述符是一個(gè)具有值的屬性,該值可以是可寫的,也可以是不可寫的;存取描述符是由 getter 函數(shù)和 setter 函數(shù)所描述的屬性。一個(gè)描述符只能是這兩者其中之一,不能同時(shí)是兩者。

        它們共享以下可選鍵值:

        • configurable

          屬性是否可以被刪除和重新定義特性,默認(rèn)值為 false

        • enumerable

          是否會(huì)出現(xiàn)在對(duì)象的枚舉屬性中,默認(rèn)值為 false

        數(shù)據(jù)描述符特有鍵值:

        • value

          該屬性對(duì)應(yīng)的值,默認(rèn)值為 undefined

        • writable

          是否可以被更改,默認(rèn)值為 false

        存取操作符特有鍵值:

        • get

          屬性的 getter 函數(shù),如果沒有 getter,則為 undefined;默認(rèn)為?undefined

        • set

          屬性的 setter 函數(shù),如果沒有 setter,則為 undefined;默認(rèn)為?undefined

        講完 Object.defineProperty(),接下來就讓我們看看該怎么使用它吧~

        類方法裝飾器的使用

        讓我們通過一個(gè)例子來了解一下:

        function?readonly(target,?name,?descriptor)?{
        ??descriptor.writable?=?false;
        ??return?descriptor;
        }

        class?Person?{
        ??@readonly
        ??name?=?'zhangsan';
        }

        const?person?=?new?Person();
        console.log(person.name,?Object.getOwnPropertyDescriptor(person,?'name'));

        打印結(jié)果如下:

        上面代碼說明,裝飾器會(huì)修改屬性的描述對(duì)象,然后被修改的描述對(duì)象再用來定義屬性。

        結(jié)論

        由此我們可以得出結(jié)論:

        function?changeName(target,?name,?descriptor)?{
        ??descriptor.value?=?'lisi';
        ??return?descriptor;
        }
        class?Person?{
        ??@changeName
        ??name?=?'zhangsan';
        }
        const?person?=?new?Person();

        //?等同于

        class?Person?{
        ??name?=?'zhangsan';
        }
        const?person?=?new?Person();
        Object.defineProperty(person,?'name',?{
        ??value:?'lisi',
        });

        裝飾器的應(yīng)用

        在項(xiàng)目中,可能會(huì)遇到這樣一種情況,好幾個(gè)組件的數(shù)據(jù)都是調(diào)用同一個(gè)后端接口獲得,只是傳參不同,有些小伙伴們?cè)趯懘a的時(shí)候可能就是每個(gè)組件都去手動(dòng)調(diào)用一次后端接口(以 React 項(xiàng)目為例):

        ...
        export?default?class?CompOne?extends?Component?{
        ??...
        ??getData?=?async?()?=>?{?//?調(diào)用后端接口
        ????const?data?=?await?request('/xxx',?{
        ??????params:?{
        ????????id:?'123',?//?不同組件傳參不同
        ??????},
        ????});
        ????this.setState({?data?});
        ??}
        ??render()?{
        ????...
        ????return?(
        ??????<div>
        ????????...
        ????????我是組件一:?{data}
        ????????...
        ??????div>

        ????)
        ??}
        }

        遇到這種情況,我們就可以用裝飾器解決呀~

        //?裝飾器
        function?getData(params)?{
        ??return?(Comp)?=>?{
        ????class?WrapperComponent?extends?Component?{
        ??????...
        ??????getData?=?async?()?=>?{
        ????????const?data?=?await?request('/xxx',?{
        ??????????params,
        ????????});
        ????????this.setState({?data?});
        ??????}
        ??????render()?{
        ????????...
        ????????return?(
        ??????????<Comp?data={data}?/>
        ????????)
        ??????}
        ????}

        ????return?Comp;
        ??}
        }

        //?組件
        ...
        @getData({
        ??id:?'123'
        })
        export?default?class?index?extends?Component?{
        ??...
        ??render()?{
        ????...
        ????const?data?=?this.props.data;?//?直接從?this.props?中獲取想要的數(shù)據(jù)
        ????return?(
        ??????<div>
        ????????...
        ????????我是組件一:?{data}
        ????????...
        ??????div>

        ????)
        ??}
        }

        總結(jié)

        好啦,今天的分享就要到此結(jié)束了哦,希望通過這篇文章大家能夠?qū)ρb飾器有一定的了解,如有不同意見,歡迎在評(píng)論區(qū)評(píng)論呦~就讓暴風(fēng)雨來得更猛烈些吧!

        參考鏈接

        裝飾器(https://www.bookstack.cn/read/es6-3rd/docs-decorator.md)

        ES7 提案: Decorators 裝飾器(https://blog.csdn.net/weixin_44691608/article/details/117180409)

        Object.defineProperty()(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

        babel-plugin-transform-decorators-legacy(https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy

        瀏覽 37
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            人人澡人人澡人人看欧美 | 九九在线| 中文字幕日本欧美 | 少妇浪妇荡欲黄蓉奶汁 | 免 费 成 人 在 线 观 看 | 美女无套内射视频 | 秋霞论乱 | 欧美国产综合福利在线 | 极品漂亮人妻找猛男3p | 九热精品视频在线观看 |