<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何用 Decorator 裝飾你的 Typescript?

          共 19777字,需瀏覽 40分鐘

           ·

          2021-07-07 10:28

          關(guān)注并將「趣談前端」設(shè)為星標(biāo)

          每早08:30按時(shí)推送技術(shù)干貨/優(yōu)秀開(kāi)源/技術(shù)思維

          前言

          原創(chuàng)文章匯總:github/Nealyang

          正在著手寫 THE LAST TIME 系列的 Typescript 篇,而Decorator 一直是我個(gè)人看來(lái)一個(gè)非常不錯(cuò)的切面方案。所謂的切面方案就是我們常說(shuō)的切面編程 AOP。一種編程思想,簡(jiǎn)單直白的解釋就是,一種在運(yùn)行時(shí),動(dòng)態(tài)的將代碼切入到類的指定方法、指定位置上的編程思想就是 AOPAOP 和我們熟悉的 OOP 一樣,只是一個(gè)編程范式AOP 沒(méi)有說(shuō)什么規(guī)定要使用什么代碼協(xié)議,必須要用什么方式去實(shí)現(xiàn),這只是一個(gè)范式。而 Decorator 也就是AOP 的一種形式。

          而本文重點(diǎn)不在于討論編程范式,主要介紹 Typescript+Decorator 下圖的一些知識(shí)講解,其中包括最近筆者在寫項(xiàng)目的一些應(yīng)用。

          介紹

          什么是 Decorator

          貌似在去年的時(shí)候在公眾號(hào):【全棧前端精選】中,有分享過(guò)關(guān)于 Decorator 的基本介紹:Decorator 從原理到實(shí)戰(zhàn),里面有對(duì)Decorator 非常詳細(xì)的介紹。

          本質(zhì)上,它也就是個(gè)函數(shù)的語(yǔ)法糖。

          DecoratorES7 添加的新特性,當(dāng)然,在 Typescript 很早就有了。早在此之前,就有提出與 Decorator 思想非常相近的設(shè)計(jì)模式:裝飾者模式

          上圖的WeaponAccessory就是一個(gè)Decorator,他們添加額外的功能到基類上。讓其能夠滿足你的需求。

          簡(jiǎn)單的理解 Decorator,可以認(rèn)為它是一種包裝,對(duì) 對(duì)象,方法,屬性的包裝。就像 Decorator 俠,一身盔甲,只是裝飾,以滿足需求,未改變是人類的本質(zhì)。

          為什么要使用 Decorator

          為什么要使用 Decorator,其實(shí)就是介紹到 AOP 范式的最大特點(diǎn)了:非侵入式增強(qiáng)。

          比如筆者正在寫的一個(gè)頁(yè)面容器,叫 PageContainer.tsx,基本功能包括滾動(dòng)、autoCell、事件注入與解綁、placeHolder Container 的添加等基本功能。

          class PageContainer extends Components{
          xxx
          }

          這時(shí)候我正使用這個(gè)容器,想接入微信分享功能。或者錯(cuò)誤兜底功能。但是使用這個(gè)容器的人非常多。分享不一定都是微信分享、錯(cuò)誤兜底不一定都是張著我想要的樣子。所以我必定要對(duì)容器進(jìn)行改造和增強(qiáng)

          從功能點(diǎn)劃分,這些的確屬于容器的能力。所以在無(wú)侵入式的增強(qiáng)方案中,裝飾者模式是一個(gè)非常好的選擇。也就是話落到我們所說(shuō)的 Decorator。(對(duì)于 React 或者 RaxHOC 也是一種很好的方案,當(dāng)然,其思想是一致的。)

          + @withError
          + @withWxShare
          class PageContainer extends Components{
          xxx
          }

          我們添加 Decorator,這樣的做法,對(duì)原有代碼毫無(wú)入侵性,這就是AOP的好處了,把和主業(yè)務(wù)無(wú)關(guān)的事情,放到代碼外面去做

          關(guān)于 Typescript

          JavaScript 毋庸置疑是一門非常好的語(yǔ)言,但是其也有很多的弊端,其中不乏是作者設(shè)計(jì)之處留下的一些 “bug”。當(dāng)然,瑕不掩瑜~

          話說(shuō)回來(lái),JavaScript 畢竟是一門弱類型語(yǔ)言,與強(qiáng)類型語(yǔ)言相比,其最大的編程陋習(xí)就是可能會(huì)造成我們類型思維的缺失(高級(jí)詞匯,我從極客時(shí)間學(xué)到的)。而思維方式?jīng)Q定了編程習(xí)慣,編程習(xí)慣奠定了編程質(zhì)量,工程質(zhì)量劃定了能力邊界,而學(xué)習(xí) Typescript,最重要的就是我們類型思維的重塑

          那么其實(shí),Typescript 在我個(gè)人理解,并不能算是一個(gè)編程語(yǔ)言,它只是 JavaScript 的一層殼。當(dāng)然,我們完全可以將它作為一門語(yǔ)言去學(xué)習(xí)。網(wǎng)上有很多推薦 or 不推薦 Typescript 之類的文章這里我們不做任何討論,學(xué)與不學(xué),用或不用,利與弊。各自拿捏~

          再說(shuō)說(shuō) typescript,其實(shí)對(duì)于 ts 相比大家已經(jīng)不陌生了。更多關(guān)于 ts 入門文章和文檔也是已經(jīng)爛大街了。此文不去翻譯或者搬運(yùn)各種 api或者教程章節(jié)。只是總結(jié)羅列和解惑,筆者在學(xué)習(xí) ts 過(guò)程中曾疑惑的地方。道不到的地方,歡迎大家評(píng)論區(qū)積極討論。

          首先推薦下各自 ts 的編譯環(huán)境:typescriptlang.org

          再推薦筆者收藏的兩個(gè)網(wǎng)站:

          • Typescript 中文網(wǎng)
          • 深入理解 Typescript
          • TypeScript Handbook
          • TypeScript 精通指南

          Typescript 中的 Decorator 簽名

          interface TypedPropertyDescriptor<T> {
          enumerable?: boolean;
          configurable?: boolean;
          writable?: boolean;
          value?: T;
          get?: () => T;
          set?: (value: T) => void;
          }

          declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
          declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
          declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
          declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

          如上是 ClassDecoratorPropertyDecorator以及 MethodDecorator 的三個(gè)類型簽名。

          基本配置

          由于 DecoratorTypescript 中還是一項(xiàng)實(shí)驗(yàn)性的給予支持,所以在 ts 的配置配置文件中,我們指明編譯器對(duì) Decorator 的支持。

          在命令行或tsconfig.json里啟用experimentalDecorators編譯器選項(xiàng):

          • 命令行:
          tsc --target ES5 --experimentalDecorators
          • tsconfig.json
          {
          "compilerOptions": {
          "target": "ES5",
          "experimentalDecorators": true
          }
          }

          類型

          Typescript 中,Decorator 可以修飾五種語(yǔ)句:類、屬性、方法、訪問(wèn)器方法參數(shù)

          class definitions

          類裝飾器應(yīng)用于構(gòu)造函數(shù)之上,會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,類的構(gòu)造函數(shù)作為其唯一的參數(shù)。

          注意,在 Typescript 中的class 關(guān)鍵字只是 JavaScript 構(gòu)造函數(shù)的一個(gè)語(yǔ)法糖。由于類裝飾器的參數(shù)是一個(gè)構(gòu)造函數(shù),其也應(yīng)該返回一個(gè)構(gòu)造函數(shù)。

          我們先看一下官網(wǎng)的例子:

              function classDecorator<T extends { new (...args: any[]): {} }>(
          constructor: T
          ) {
          return class extends constructor {
          newProperty = "new property";
          hello = "override";
          };
          }

          @classDecorator
          class Greeter {
          property = "property";
          hello: string;
          constructor(m: string) {
          this.hello = m;
          }
          }
          const greeter: Greeter = new Greeter("world");
          console.log({ greeter }, greeter.hello);

          { new (...args: any[]): {} }表示一個(gè)構(gòu)造函數(shù),為了看起來(lái)清晰一些,我們也可以將其聲明到外面:

          /**
          *構(gòu)造函數(shù)類型
          *
          * @export
          * @interface Constructable
          */

          export interface IConstructable {
          new (...args:any[]):any
          }

          properties

          屬性裝飾器有兩個(gè)參數(shù):

          • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
          • 成員的key。

          descriptor不會(huì)做為參數(shù)傳入屬性裝飾器,這與TypeScript是如何初始化屬性裝飾器的有關(guān)。因?yàn)槟壳皼](méi)有辦法在定義一個(gè)原型對(duì)象的成員時(shí)描述一個(gè)實(shí)例屬性,并且沒(méi)辦法監(jiān)視或修改一個(gè)屬性的初始化方法。返回值也會(huì)被忽略。因此,屬性描述符只能用來(lái)監(jiān)視類中是否聲明了某個(gè)名字的屬性。

              function setDefaultValue(target: Object, propertyName: string) {
          target[propertyName] = "Nealayng";
          }

          class Person {
          @setDefaultValue
          name: string;
          }

          console.log(new Person().name); // 輸出: Nealayng

          將上面的代碼修改一下,我們給靜態(tài)成員添加一個(gè) Decorator

              function setDefaultValue(target: Object, propertyName: string) {
          console.log(target === Person);

          target[propertyName] = "Nealayng";
          }

          class Person {
          @setDefaultValue
          static displayName = 'PersonClass'

          name: string;

          constructor(name:string){
          this.name = name;
          }
          }

          console.log(Person.prototype);
          console.log(new Person('全棧前端精選').name); // 輸出: 全棧前端精選
          console.log(Person.displayName); // 輸出: Nealayng

          以此可以驗(yàn)證,上面我們說(shuō)的:Decorator 的第一個(gè)參數(shù),對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象

          methods

          方法裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入下列3個(gè)參數(shù):

          • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
          • 成員的名字。
          • 成員的屬性描述符 descriptor

          注意: 如果代碼輸出目標(biāo)版本小于ES5,descriptor將會(huì)是undefined。

              function log(
          target: Object,
          propertyName: string,
          descriptor: TypedPropertyDescriptor<(...args: any[]
          ) => any>
          )
          {
          const method = descriptor.value;
          descriptor.value = function(...args: any[]) {
          // 將參數(shù)轉(zhuǎn)為字符串
          const params: string = args.map(a => JSON.stringify(a)).join();

          const result = method!.apply(this, args);

          // 將結(jié)果轉(zhuǎn)為字符串
          const resultString: string = JSON.stringify(result);

          console.log(`Call:${propertyName}(${params}) => ${resultString}`);

          return result;
          };
          }

          class Author {
          constructor(private firstName: string, private lastName: string) {}

          @log
          say(message: string): string {
          return `${message} by: ${this.lastName}${this.firstName}`;
          }
          }

          const author:Author = new Author('Yang','Neal');
          author.say('《全站前端精選》');//Call:say("全站前端精選") => "全站前端精選 by: NealYang"

          上述的代碼比較簡(jiǎn)單,也就不做過(guò)多解釋了。其中需要注意的是屬性描述符 descriptor 的類型和許多文章寫的類型有些不同:propertyDescriptor: PropertyDescriptor

          從官方的聲明文件可以看出,descriptor 設(shè)置為TypedPropertyDescriptor加上泛型約束感覺(jué)更加的嚴(yán)謹(jǐn)一些。

          當(dāng)然,官網(wǎng)也是直接聲明為類型PropertyDescriptor的。這個(gè),仁者見(jiàn)仁。

          accessors

          訪問(wèn)器,不過(guò)是類聲明中屬性的讀取訪問(wèn)器和寫入訪問(wèn)器。訪問(wèn)器裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入下列3個(gè)參數(shù):

          • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
          • 成員的名字。
          • 成員的屬性描述符。

          如果代碼輸出目標(biāo)版本小于ES5,Property Descriptor將會(huì)是undefined。同時(shí) TypeScript 不允許同時(shí)裝飾一個(gè)成員的get和set訪問(wèn)器

              function Enumerable(
          target: any,
          propertyKey: string,
          descriptor: PropertyDescriptor
          )
          {
          //make the method enumerable
          descriptor.enumerable = true;
          }

          class Person {
          _name: string;

          constructor(name: string) {
          this._name = name;
          }

          @Enumerable
          get name() {
          return this._name;
          }
          }

          console.log("-- creating instance --");
          let person = new Person("Diana");
          console.log("-- looping --");
          for (let key in person) {
          console.log(key + " = " + person[key]);
          }

          如果上面 get 不添加Enumerable的話,那么 for in 只能出來(lái)_name  _name = Diana

          parameters

          參數(shù)裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入下列3個(gè)參數(shù):

          • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類的原型對(duì)象。
          • 成員的名字。
          • 參數(shù)在函數(shù)參數(shù)列表中的索引。

          參數(shù)裝飾器只能用來(lái)監(jiān)視一個(gè)方法的參數(shù)是否被傳入。

          在下面的示例中,我們將使用參數(shù)裝飾器@notNull來(lái)注冊(cè)目標(biāo)參數(shù)以進(jìn)行非空驗(yàn)證,但是由于僅在加載期間調(diào)用此裝飾器(而不是在調(diào)用方法時(shí)),因此我們還需要方法裝飾器@validate,它將攔截方法調(diào)用并執(zhí)行所需的驗(yàn)證。

          function notNull(target: any, propertyKey: string, parameterIndex: number) {
          console.log("param decorator notNull function invoked ");
          Validator.registerNotNull(target, propertyKey, parameterIndex);
          }

          function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
          console.log("method decorator validate function invoked ");
          let originalMethod = descriptor.value;
          //wrapping the original method
          descriptor.value = function (...args: any[]) {//wrapper function
          if (!Validator.performValidation(target, propertyKey, args)) {
          console.log("validation failed, method call aborted: " + propertyKey);
          return;
          }
          let result = originalMethod.apply(this, args);
          return result;
          }
          }

          class Validator {
          private static notNullValidatorMap: Map<any, Map<string, number[]>> = new Map();

          //todo add more validator maps
          static registerNotNull(target: any, methodName: string, paramIndex: number): void {
          let paramMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
          if (!paramMap) {
          paramMap = new Map();
          this.notNullValidatorMap.set(target, paramMap);
          }
          let paramIndexes: number[] = paramMap.get(methodName);
          if (!paramIndexes) {
          paramIndexes = [];
          paramMap.set(methodName, paramIndexes);
          }
          paramIndexes.push(paramIndex);
          }

          static performValidation(target: any, methodName: string, paramValues: any[]): boolean {
          let notNullMethodMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
          if (!notNullMethodMap) {
          return true;
          }
          let paramIndexes: number[] = notNullMethodMap.get(methodName);
          if (!paramIndexes) {
          return true;
          }
          let hasErrors: boolean = false;
          for (const [index, paramValue] of paramValues.entries()) {
          if (paramIndexes.indexOf(index) != -1) {
          if (!paramValue) {
          console.error("method param at index " + index + " cannot be null");
          hasErrors = true;
          }
          }
          }
          return !hasErrors;
          }
          }

          class Task {
          @validate
          run(@notNull name: string): void {
          console.log("running task, name: " + name);
          }
          }

          console.log("-- creating instance --");
          let task: Task = new Task();
          console.log("-- calling Task#run(null) --");
          task.run(null);
          console.log("----------------");
          console.log("-- calling Task#run('test') --");
          task.run("test");

          對(duì)應(yīng)的輸出位:

          param decorator notNull function invoked
          method decorator validate function invoked
          -- creating instance --
          -- calling Task#run(null) --
          method param at index 0 cannot be null
          validation failed, method call aborted: run
          ----------------
          -- calling Task#run('test') --
          running task, name: test

          @validate裝飾器把run方法包裹在一個(gè)函數(shù)里在調(diào)用原先的函數(shù)前驗(yàn)證函數(shù)參數(shù).

          裝飾器工廠

          裝飾器工廠真的也就是一個(gè)噱頭(造名詞)而已,其實(shí)也是工廠的概念哈,畢竟官方也是這么號(hào)稱的。在實(shí)際項(xiàng)目開(kāi)發(fā)中,我們使用的也還是挺多的

          **裝飾器工廠就是一個(gè)簡(jiǎn)單的函數(shù),它返回一個(gè)表達(dá)式,以供裝飾器在運(yùn)行時(shí)調(diào)用。**其實(shí)說(shuō)白了,就是一個(gè)函數(shù) return 一個(gè) Decorator。非常像 JavaScript 函數(shù)柯里化,個(gè)人稱之為“函數(shù)式Decorator”~

          import { logClass } from './class-decorator';
          import { logMethod } from './method-decorator';
          import { logProperty } from './property-decorator';
          import { logParameter } from './parameter-decorator';

          // 裝飾器工廠,根據(jù)傳入的參數(shù)調(diào)用相應(yīng)的裝飾器
          export function log(...args) {
          switch (args.length) {
          case 3: // 可能是方法裝飾器或參數(shù)裝飾器
          // 如果第三個(gè)參數(shù)是數(shù)字,那么它是索引,所以這是參數(shù)裝飾器
          if typeof args[2] === "number") {
          return logParameter.apply(this, args);
          }
          return logMethod.apply(this, args);
          case 2: // 屬性裝飾器
          return logProperty.apply(this, args);
          case 1: // 類裝飾器
          return logClass.apply(this, args);
          default: // 參數(shù)數(shù)目不合法
          throw new Error('Not a valid decorator');
          }
          }

          @log
          class Employee {
          @log
          private name: string;

          constructor(name: string) {
          this.name = name;
          }

          @log
          greet(@log message: string): string {
          return `${this.name} says: ${message}`;
          }
          }

          加載順序

          一個(gè)類中,不同位置聲明的裝飾器,按照以下規(guī)定的順序應(yīng)用:

          • 有多個(gè)參數(shù)裝飾器(parameterDecorator)時(shí),從最后一個(gè)參數(shù)依次向前執(zhí)行
          • 方法(methodDecorator)和方法參數(shù)裝飾器(parameterDecorator)中,參數(shù)裝飾器先執(zhí)行
          • 類裝飾器(classDecorator)總是最后執(zhí)行。
          • 方法(methodDecorator)和屬性裝飾器(propertyDecorator),誰(shuí)在前面誰(shuí)先執(zhí)行。因?yàn)閰?shù)屬于方法一部分,所以參數(shù)會(huì)一直緊緊挨著方法執(zhí)行。
          function ClassDecorator() {
          return function (target) {
          console.log("I am class decorator");
          }
          }
          function MethodDecorator() {
          return function (target, methodName: string, descriptor: PropertyDescriptor) {
          console.log("I am method decorator");
          }
          }
          function Param1Decorator() {
          return function (target, methodName: string, paramIndex: number) {
          console.log("I am parameter1 decorator");
          }
          }
          function Param2Decorator() {
          return function (target, methodName: string, paramIndex: number) {
          console.log("I am parameter2 decorator");
          }
          }
          function PropertyDecorator() {
          return function (target, propertyName: string) {
          console.log("I am property decorator");
          }
          }

          @ClassDecorator()
          class Hello {
          @PropertyDecorator()
          greeting: string;


          @MethodDecorator()
          greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
          }

          輸出為:

          I am parameter2 decorator
          I am parameter1 decorator
          I am method decorator
          I am property decorator
          I am class decorator

          實(shí)戰(zhàn)

          由于是業(yè)務(wù)代碼,與技術(shù)無(wú)關(guān)瑣碎,只截取部分代碼示意,非 Decorator 代碼,以截圖形式

          這應(yīng)該也是整理這篇文章最開(kāi)始的原因了。直接說(shuō)說(shuō)項(xiàng)目(rax1.0+Decorator)吧。

          需求很簡(jiǎn)單,就是是編寫一個(gè)頁(yè)面的容器。

          部分項(xiàng)目結(jié)構(gòu):

          pm-detail
          ├─ constants
          │ └─ index.ts //常量
          ├─ index.css
          ├─ index.tsx // 入口文件
          └─ modules // 模塊
          └─ page-container // 容器組件
          ├─ base //容器基礎(chǔ)組件
          ├─ decorator // 裝飾器
          ├─ index.tsx
          ├─ lib // 工具
          └─ style.ts

          重點(diǎn)看下如下幾個(gè)文件

          • base.tsx

          其實(shí)是基礎(chǔ)功能的封裝

          在此基礎(chǔ)上,我們需要個(gè)能滾動(dòng)的容器

          • scrollbase.tsx

          也是基于 Base.tsx 基礎(chǔ)上,封裝一些滾動(dòng)容器具有的功能

          • style decorator
          import is from './util/is';
          import map from './util/map';

          const isObject = is(Object);
          const isFunction = is(Function);

          class Style {
          static factory = (...args) => new Style(...args);

          analyze(styles, props, state) {
          return map(v => {
          if (isFunction(v)) {
          const r = v.call(this.component, props, state);
          return isObject(r) ? this.analyze(r, props, state) : r;
          }
          if (isObject(v)) return this.analyze(v, props, state);
          return v;
          })(styles);
          }

          generateStyles(props, state) {
          const { styles: customStyles } = props;
          const mergedStyles = this.analyze(this.defaultStyles, props, state);
          if (customStyles) {
          Object.keys(customStyles).forEach(key => {
          if (mergedStyles[key]) {
          if (isObject(mergedStyles[key])) {
          Object.assign(mergedStyles[key], customStyles[key]);
          } else {
          mergedStyles[key] = customStyles[key];
          }
          } else {
          mergedStyles[key] = customStyles[key];
          }
          });
          }
          return {
          styles: mergedStyles,
          };
          }

          constructor(defaultStyles = {}, { vary = true } = {}) {
          const manager = this;

          this.defaultStyles = defaultStyles;

          return BaseComponent => {
          const componentWillMount = BaseComponent.prototype.componentWillMount;
          const componentWillUpdate = BaseComponent.prototype.componentWillUpdate;

          BaseComponent.prototype.componentWillMount = function() {
          manager.component = this;
          Object.assign(this, manager.generateStyles(this.props, this.state));
          return componentWillMount && componentWillMount.apply(this, arguments);
          };

          if (vary) {
          BaseComponent.prototype.componentWillUpdate = function(nextProps, nextState) {
          Object.assign(this, manager.generateStyles(nextProps, nextState));
          return componentWillUpdate && componentWillUpdate.apply(this, arguments);
          };
          }

          return BaseComponent;
          };
          }
          }

          export default Style.factory;

          然后我們需要一個(gè)錯(cuò)誤的兜底功能,但是這個(gè)本身應(yīng)該不屬于容器的功能。所以我們封裝一個(gè) errorDecorator

          • withError.txs
          function withError<T extends IConstructable>(Wrapped: T) {
          const willReceiveProps = Wrapped.prototype.componentWillReceiveProps;
          const didMount = Wrapped.prototype.componentDidMount;
          const willUnmount = Wrapped.prototype.componentWillUnmount;

          return class extends Wrapped {
          static displayName: string = `WithError${getDisplayName(Wrapped)}·`;

          static defaultProps: IProps = {
          isOffline: false,
          isError: false,
          errorRefresh: () => {
          window.location.reload(true);
          }
          };

          private state: StateType;
          private eventNamespace: string = "";

          constructor(...args: any[]) {
          super(...args);
          const { isOffline, isError, errorRefresh, tabPanelIndex } = this.props;
          this.state = {
          isOffline,
          isError,
          errorRefresh
          };
          if (tabPanelIndex > -1) {
          this.eventNamespace = `.${tabPanelIndex}`;
          }
          }

          triggerErrorHandler = e => {...};

          componentWillReceiveProps(...args) {
          if (willReceiveProps) {
          willReceiveProps.apply(this, args);
          }
          const [nextProps] = args;
          const { isOffline, isError, errorRefresh } = nextProps;
          this.setState({
          isOffline,
          isError,
          errorRefresh
          });
          }

          componentDidMount(...args) {
          if (didMount) {
          didMount.apply(this, args);
          }
          const { eventNamespace } = this;
          emitter.on(
          EVENTS.TRIGGER_ERROR + eventNamespace,
          this.triggerErrorHandler
          );
          }

          componentWillUnmount(...args) {
          if (willUnmount) {
          willUnmount.apply(this, args);
          }
          const { eventNamespace } = this;
          emitter.off(
          EVENTS.TRIGGER_ERROR + eventNamespace,
          this.triggerErrorHandler
          );
          }

          render() {
          const { isOffline, isError, errorRefresh } = this.state;

          if (isOffline || isError) {
          let errorType = "system";
          if (isOffline) {
          errorType = "offline";
          }
          return <Error errorType={errorType} refresh={errorRefresh} />;
          }

          return super.render();
          }
          };
          }

          然后我們進(jìn)行整合導(dǎo)出

          import { createElement, PureComponent, RaxNode } from 'rax';
          import ScrollBase from "./base/scrollBase";
          import withError from "./decorator/withError";

          interface IScrollContainerProps {
          spmA:string;
          spmB:string;
          renderHeader?:()=>RaxNode;
          renderFooter?:()=>RaxNode;
          [key:string]:any;
          }
          @withError
          class ScrollContainer extends PureComponent<IScrollContainerProps,{}> {

          render() {
          return <ScrollBase {...this.props} />;
          }
          }

          export default ScrollContainer;

          使用如下:

          思維導(dǎo)圖

          最后附一張,本文思維導(dǎo)圖。

          公眾號(hào)回復(fù):【xmind1】 獲取思維導(dǎo)圖源文件

          參考文獻(xiàn)

          • TypeScript裝飾器(decorators)
          • 文檔
          • TypeScript - Class Decorators


          ?? 看完三件事

          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          • 點(diǎn)個(gè)【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容

          • 關(guān)注公眾號(hào)【趣談前端】,定期分享 工程化 可視化 / 低代碼 / 優(yōu)秀開(kāi)源



          Dooring可視化搭建平臺(tái)數(shù)據(jù)源設(shè)計(jì)剖析

          可視化搭建的一些思考和實(shí)踐

          基于Koa + React + TS從零開(kāi)發(fā)全棧文檔編輯器(進(jìn)階實(shí)戰(zhàn))

          從零使用electron搭建桌面端Dooring


          點(diǎn)個(gè)在看你最好看


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产极品无码AV在线观看 | 波多野吉衣AⅤ无码一区小说 | 爱搞中文字幕 | 欧美在线91 | 插菊花中文网久久久 |