<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?

          共 17545字,需瀏覽 36分鐘

           ·

          2024-04-11 12:14


          前言

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

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

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

          10f2206504383d45b2e3abefb54472ec.webp

          介紹

          什么是 Decorator

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

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

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

          85aa0fef6515ba91f6957d992550f9ac.webp

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

          df4ff98684ed68add6e67786af119f20.webp

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

          為什么要使用 Decorator

          為什么要使用 Decorator,其實就是介紹到 AOP 范式的最大特點了:非侵入式增強。

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

                class PageContainer extends Components{
          xxx
          }

          這時候我正使用這個容器,想接入微信分享功能。或者錯誤兜底功能。但是使用這個容器的人非常多。分享不一定都是微信分享、錯誤兜底不一定都是張著我想要的樣子。所以我必定要對容器進行改造和增強

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

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

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

          關(guān)于 Typescript

          10fbed382bd235659de19b38c282a875.webp

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

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

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

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

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

          再推薦筆者收藏的兩個網(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 的三個類型簽名。

          基本配置

          由于 DecoratorTypescript 中還是一項實驗性的給予支持,所以在 ts 的配置配置文件中,我們指明編譯器對 Decorator 的支持。

          在命令行或tsconfig.json里啟用experimentalDecorators編譯器選項:

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

          類型

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

          class definitions

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

          注意,在 Typescript 中的class 關(guān)鍵字只是 JavaScript 構(gòu)造函數(shù)的一個語法糖。由于類裝飾器的參數(shù)是一個構(gòu)造函數(shù),其也應(yīng)該返回一個構(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);
          fc962d86d53437dd2d020922918d544a.webp

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

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

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

          properties

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

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

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

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

          class Person {
          @setDefaultValue
          name: string;
          }

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

          將上面的代碼修改一下,我們給靜態(tài)成員添加一個 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
          e96bf3f40749ef85e58e06d77fb7a57b.webp

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

          methods

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

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

          注意: 如果代碼輸出目標(biāo)版本小于ES5,descriptor將會是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"

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

          66b6d270fd4c22fc3d2f1c636fdf8014.webp

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

          64b7b93b86dc66800fa791cadaba4e22.webp

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

          accessors

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

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

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

                    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]);
          }
          f2359d855616f07e9c81614a4709492c.webp

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

          parameters

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

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

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

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

                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");

          對應(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方法包裹在一個函數(shù)里在調(diào)用原先的函數(shù)前驗證函數(shù)參數(shù).

          裝飾器工廠

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

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

          040fc782b26bc77928beb1dfb09a49fa.webp
                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ù)裝飾器
          // 如果第三個參數(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}`;
          }
          }

          加載順序

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

          • 有多個參數(shù)裝飾器(parameterDecorator)時,從最后一個參數(shù)依次向前執(zhí)行
          • 方法(methodDecorator)和方法參數(shù)裝飾器(parameterDecorator)中,參數(shù)裝飾器先執(zhí)行
          • 類裝飾器(classDecorator)總是最后執(zhí)行。
          • 方法(methodDecorator)和屬性裝飾器(propertyDecorator),誰在前面誰先執(zhí)行。因為參數(shù)屬于方法一部分,所以參數(shù)會一直緊緊挨著方法執(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

          實戰(zhàn)

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

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

          需求很簡單,就是是編寫一個頁面的容器。

          3187187f238b93e604e846d1e8f6cd8d.webp

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

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

          重點看下如下幾個文件

          542a7206ca38a6a28994b96acdb8194b.webp
          • base.tsx
          06b630a1d452d4c673e262c2654870e5.webp

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

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

          • scrollbase.tsx
          8c6eac919695b7ae6d9da33b59d91134.webp

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

          • 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;

          然后我們需要一個錯誤的兜底功能,但是這個本身應(yīng)該不屬于容器的功能。所以我們封裝一個 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();
          }
          };
          }

          然后我們進行整合導(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;

          使用如下:

          999af0397609484acf8a38d683ad48ce.webp 28f0fe8ffdda99bf753a3a668b276401.webp

          思維導(dǎo)圖

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

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

          27f9b2cea2b59136d4e9df7237653e5a.webp

          參考文獻

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


          ??  

          內(nèi) 發(fā)

          1. 內(nèi)
          2. 關(guān) 聯(lián) 網(wǎng) 創(chuàng)

          - - 經(jīng)

          T y p e S c r i p t


          -   E N D   -


          結(jié)


          a8aed4568db83011fd9db8898854433f.webp


          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美一级国产一级日韩一级 | 99热最新网址 | 三级网站在线观看视频 | 最新做爱网站 | 操精品|