摘要:這有一些偏方,能讓你順利從遷移至顯式賦值斷言修飾符,即是在類(lèi)里,明確說(shuō)明某些屬性存在于類(lèi)上采用聲明合并形式,多帶帶定義一個(gè),把用擴(kuò)展的屬性的類(lèi)型,放入中是的一個(gè)提案,它主要用來(lái)在聲明的時(shí)候添加和讀取元數(shù)據(jù)。
Decorator
Decorator 早已不是什么新鮮事物。在 TypeScript 1.5 + 的版本中,我們可以利用內(nèi)置類(lèi)型 ClassDecorator、PropertyDecorator、MethodDecorator 與 ParameterDecorator 更快書(shū)寫(xiě) Decorator,如 MethodDecorator :
declare type MethodDecorator =(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor ) => TypedPropertyDescriptor | void;
使用時(shí),只需在相應(yīng)地方加上類(lèi)型注解,匿名函數(shù)的參數(shù)類(lèi)型也就會(huì)被自動(dòng)推導(dǎo)出來(lái)了。
function methodDecorator (): MethodDecorator {
return (target, key, descriptor) => {
// ...
};
}
值得一提的是,如果你在 Decorator 給目標(biāo)類(lèi)的 prototype 添加屬性時(shí),TypeScript 并不知道這些:
function testAble(): ClassDecorator {
return target => {
target.prototype.someValue = true
}
}
@testAble()
class SomeClass {}
const someClass = new SomeClass()
someClass.someValue() // Error: Property "someValue" does not exist on type "SomeClass".
這很常見(jiàn),特別是當(dāng)你想用 Decorator 來(lái)擴(kuò)展一個(gè)類(lèi)時(shí)。
GitHub 上有一個(gè)關(guān)于此問(wèn)題的 issues,直至目前,也沒(méi)有一個(gè)合適的方案實(shí)現(xiàn)它。其主要問(wèn)題在于 TypeScript 并不知道目標(biāo)類(lèi)是否使用了 Decorator,以及 Decorator 的名稱(chēng)。從這個(gè) issues 來(lái)看,建議的解決辦法是使用 Mixin:
type Constructor= new(...args: any[]) => T // mixin 函數(shù)的聲明,還需要實(shí)現(xiàn) declare function mixin (...MixIns: [Constructor , Constructor ]): Constructor ; class MixInClass1 { mixinMethod1() {} } class MixInClass2 { mixinMethod2() {} } class Base extends mixin(MixInClass1, MixInClass2) { baseMethod() { } } const x = new Base(); x.baseMethod(); // OK x.mixinMethod1(); // OK x.mixinMethod2(); // OK x.mixinMethod3(); // Error
當(dāng)把大量的 JavaScript Decorator 重構(gòu)為 Mixin 時(shí),這無(wú)疑是一件讓人頭大的事情。
這有一些偏方,能讓你順利從 JavaScript 遷移至 TypeScript:
顯式賦值斷言修飾符,即是在類(lèi)里,明確說(shuō)明某些屬性存在于類(lèi)上:
function testAble(): ClassDecorator {
return target => {
target.prototype.someValue = true
}
}
@testAble()
class SomeClass {
public someValue!: boolean;
}
const someClass = new SomeClass();
someClass.someValue // true
采用聲明合并形式,多帶帶定義一個(gè) interface,把用 Decorator 擴(kuò)展的屬性的類(lèi)型,放入 interface 中:
interface SomeClass {
someValue: boolean;
}
function testAble(): ClassDecorator {
return target => {
target.prototype.someValue = true
}
}
@testAble()
class SomeClass {}
const someClass = new SomeClass();
someClass.someValue // true
Reflect Metadata
Reflect Metadata 是 ES7 的一個(gè)提案,它主要用來(lái)在聲明的時(shí)候添加和讀取元數(shù)據(jù)。TypeScript 在 1.5+ 的版本已經(jīng)支持它,你只需要:
npm i reflect-metadata --save。
在 tsconfig.json 里配置 emitDecoratorMetadata 選項(xiàng)。
它具有諸多使用場(chǎng)景。
獲取類(lèi)型信息譬如在 vue-property-decorator 6.1 及其以下版本中,通過(guò)使用 Reflect.getMetadata API,Prop Decorator 能獲取屬性類(lèi)型傳至 Vue,簡(jiǎn)要代碼如下:
function Prop(): PropertyDecorator {
return (target, key: string) => {
const type = Reflect.getMetadata("design:type", target, key);
console.log(`${key} type: ${type.name}`);
// other...
}
}
class SomeClass {
@Prop()
public Aprop!: string;
};
運(yùn)行代碼可在控制臺(tái)看到 Aprop type: string。除能獲取屬性類(lèi)型外,通過(guò) Reflect.getMetadata("design:paramtypes", target, key) 和 Reflect.getMetadata("design:returntype", target, key) 可以分別獲取函數(shù)參數(shù)類(lèi)型和返回值類(lèi)型。
自定義 metadataKey除能獲取類(lèi)型信息外,常用于自定義 metadataKey,并在合適的時(shí)機(jī)獲取它的值,示例如下:
function classDecorator(): ClassDecorator {
return target => {
// 在類(lèi)上定義元數(shù)據(jù),key 為 `classMetaData`,value 為 `a`
Reflect.defineMetadata("classMetaData", "a", target);
}
}
function methodDecorator(): MethodDecorator {
return (target, key, descriptor) => {
// 在類(lèi)的原型屬性 "someMethod" 上定義元數(shù)據(jù),key 為 `methodMetaData`,value 為 `b`
Reflect.defineMetadata("methodMetaData", "b", target, key);
}
}
@classDecorator()
class SomeClass {
@methodDecorator()
someMethod() {}
};
Reflect.getMetadata("classMetaData", SomeClass); // "a"
Reflect.getMetadata("methodMetaData", new SomeClass(), "someMethod"); // "b"
例子
控制反轉(zhuǎn)和依賴(lài)注入
在 Angular 2+ 的版本中,控制反轉(zhuǎn)與依賴(lài)注入便是基于此實(shí)現(xiàn),現(xiàn)在,我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單版:
type ConstructorController 與 Get 的實(shí)現(xiàn)= new (...args: any[]) => T; const Injectable = (): ClassDecorator => target => {} class OtherService { a = 1 } @Injectable() class TestService { constructor(public readonly otherService: OtherService) {} testMethod() { console.log(this.otherService.a); } } const Factory = (target: Constructor ): T => { // 獲取所有注入的服務(wù) const providers = Reflect.getMetadata("design:paramtypes", target); // [OtherService] const args = providers.map((provider: Constructor) => new provider()); return new target(...args); } Factory(TestService).testMethod() // 1
如果你在使用 TypeScript 開(kāi)發(fā) Node 應(yīng)用,相信你對(duì) Controller、Get、POST 這些 Decorator,并不陌生:
@Controller("/test")
class SomeClass {
@Get("/a")
someGetMethod() {
return "hello world";
}
@Post("/b")
somePostMethod() {}
};
這些 Decorator 也是基于 Reflect Metadata 實(shí)現(xiàn),不同的是,這次我們將 metadataKey 定義在 descriptor 的 value 上:
const METHOD_METADATA = "method";
const PATH_METADATA = "path";
const Controller = (path: string): ClassDecorator => {
return target => {
Reflect.defineMetadata(PATH_METADATA, path, target);
}
}
const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {
return (target, key, descriptor) => {
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);
Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value);
}
}
const Get = createMappingDecorator("GET");
const Post = createMappingDecorator("POST");
接著,創(chuàng)建一個(gè)函數(shù),映射出 route:
function mapRoute(instance: Object) {
const prototype = Object.getPrototypeOf(instance);
// 篩選出類(lèi)的 methodName
const methodsNames = Object.getOwnPropertyNames(prototype)
.filter(item => !isConstructor(item) && isFunction(prototype[item]));
return methodsNames.map(methodName => {
const fn = prototype[methodName];
// 取出定義的 metadata
const route = Reflect.getMetadata(PATH_METADATA, fn);
const method = Reflect.getMetadata(METHOD_METADATA, fn);
return {
route,
method,
fn,
methodName
}
})
};
我們可以得到一些有用的信息:
Reflect.getMetadata(PATH_METADATA, SomeClass); // "/test"
mapRoute(new SomeClass())
/**
* [{
* route: "/a",
* method: "GET",
* fn: someGetMethod() { ... },
* methodName: "someGetMethod"
* },{
* route: "/b",
* method: "POST",
* fn: somePostMethod() { ... },
* methodName: "somePostMethod"
* }]
*
*/
最后,只需把 route 相關(guān)信息綁在 express 或者 koa 上就 ok 了。
至于為什么要定義在 descriptor 的 value 上,我們希望 mapRoute 函數(shù)的參數(shù)是一個(gè)實(shí)例,而非 class 本身(控制反轉(zhuǎn))。
更多巧用 TypeScript(一)
深入理解 TypeScript
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/98994.html
摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進(jìn)行分類(lèi),具體內(nèi)容看這里前端每周清單年度總結(jié)與盤(pán)點(diǎn)。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進(jìn)行分類(lèi),具...
摘要:官方文檔高級(jí)類(lèi)型優(yōu)先閱讀,建議閱讀英文文檔。關(guān)鍵字這個(gè)關(guān)鍵字是在版本引入的在條件類(lèi)型語(yǔ)句中,該關(guān)鍵字用于替代手動(dòng)獲取類(lèi)型。源碼解釋使用條件判斷完成示例官方作用該類(lèi)型可以獲得函數(shù)的參數(shù)類(lèi)型組成的元組類(lèi)型。 學(xué)習(xí) TypeScript 到一定階段,必須要學(xué)會(huì)高階類(lèi)型的使用,否則一些復(fù)雜的場(chǎng)景若是用 any 類(lèi)型來(lái)處理的話(huà),也就失去了 TS 類(lèi)型檢查的意義。 本文羅列了 TypeScript...
簡(jiǎn)介 可視區(qū)域、頁(yè)面優(yōu)化、DOM節(jié)點(diǎn)多、圖片懶加載、性能 可視區(qū)域是一個(gè)前端優(yōu)化經(jīng)常出現(xiàn)的名詞,不管是顯示器、手機(jī)、平板它們的可視區(qū)域范圍都是有限。在這個(gè) 有限可視區(qū)域 區(qū)域里做到完美顯示和響應(yīng),而在這個(gè)區(qū)域外少做一些操作來(lái)減少渲染的壓力、網(wǎng)絡(luò)請(qǐng)求壓力。在 每日 30 秒之 對(duì)海量數(shù)據(jù)進(jìn)行切割 中的使用場(chǎng)景,我們就是利用了 有限可視區(qū)域 只渲染一部分 DOM 節(jié)點(diǎn)來(lái)減少頁(yè)面卡頓。 既然 可視區(qū)域 ...
摘要:前端每周清單年度總結(jié)與盤(pán)點(diǎn)在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽藘杉?,工作與整理前端每周清單。本文末尾我會(huì)附上清單線(xiàn)索來(lái)源與目前共期清單的地址,感謝每一位閱讀鼓勵(lì)過(guò)的朋友,希望你們能夠繼續(xù)支持未來(lái)的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結(jié)與盤(pán)點(diǎn) 在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽?..
閱讀 2651·2021-11-22 09:34
閱讀 1049·2021-11-19 11:34
閱讀 2874·2021-10-14 09:42
閱讀 1588·2021-09-22 15:27
閱讀 2445·2021-09-07 09:59
閱讀 1808·2021-08-27 13:13
閱讀 3493·2019-08-30 11:21
閱讀 830·2019-08-29 18:35