如何用 Decorator 裝飾你的 Typescript?
關(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)的將代碼切入到類的指定方法、指定位置上的編程思想就是 AOP。AOP 和我們熟悉的 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ǔ)法糖。
Decorator 是 ES7 添加的新特性,當(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 或者 Rax,HOC 也是一種很好的方案,當(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;
如上是 ClassDecorator、PropertyDecorator以及 MethodDecorator 的三個(gè)類型簽名。
基本配置
由于 Decorator 在 Typescript 中還是一項(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ì)剖析
基于Koa + React + TS從零開(kāi)發(fā)全棧文檔編輯器(進(jìn)階實(shí)戰(zhàn))
點(diǎn)個(gè)在看你最好看
