更新 : 2019-09-15 javascript
Utility Types html
在 typescript 咱们能够经过一些 "方法" 来改变原有的 type, 变成新的 typejava
这个在 c# 是没有的. git
先来讲说一些 build in 的方法,而后在讲讲它底层是怎样制做出来的. github
refer: https://www.typescriptlang.org/docs/handbook/utility-types.htmlajax
1. Partial<T>typescript
Partial 得能力是把 T 的属性变成 undefined able c#
好比有个接口,promise
interface A {
name: string;
age: number;
}
我想把它变成 app
interface AAA { name?: string | undefined; age?: number | undefined; }
那么我能够这样写
type AAA = Partial<A>;
常常初始化 class 变量
class Person { constructor(data?: Partial<Person>) { Object.assign(this, data); } name: string; age: number; } const p = new Person({ name: 'keatkeat' });
2. Required<T>
required 和 partial 的功能相反
interface A { name?: string | undefined; }
变成
interface AA {
name: string;
}
interface A {
name: string;
}
变成
interface AA {
readonly name: string;
}
写法
type AA = Readonly<A>;
4. NonNullable<T>
这里的 T 不是 class or interface 而是 type
好比
type A = string | number | undefined | null;
变成
type AA = string | number;
写法是
type AA = NonNullable<A>;
5. ReturnType<T>
当想获取到 function 的返回类型时就须要这个
class A { method(): string { return 'dada'; } }
type R = ReturnType<A['method']>; // string
若是是单独的方法要加上 typeof
function Abc() : string { return 'dada'; }
type R2 = ReturnType<typeof Abc>;
6. InstanceType<T>
效果是同样的.
const spot1: InstanceType<typeof Dog> = new Dog('Spot'); const spot2: Dog = new Dog('Spot Evil Clone');
它的使用场景是用于动态 class, 好比 mixin 或者是 generic
好比
declare function create<T extends new () => any>(c: T): InstanceType<T> class A { } class B { } let a = create(A) // A let b = create(B) // B
7.Record<K,T>
record 的做用是返回一个类型对象, 里面的 key 就是 K, value type 就是 T
好比我要作一个对象类型, 属性有 firstname, lastname, fullname, 类型都是 string
那么能够这样写
type A = Record<'firstname' | 'lastname' | 'fullname', string>
这个例子只是解释它的功能,真实场景都是配合泛型用的.
8. Pick<T,K>
pick 的做用是从一个对象类型中选择咱们要的属性, T 是源对象类型, K 就是指定的 keys 了
class A { name: string; age: number; } type G = Pick<A, 'name'>; type GG = { name: string };
9. Omit<T,K>
omit 和 pick 同样都是从源对象选出特定的属性,只不过 omit 的 K 是指不要的属性和 pick 相反.
10. Extract<T,U> and
Exclude<T,U>
这个和 pick omit 很像,只不过它是用来选择 keys 输出 keys 的. pick 和 omit 底层就是用它们实现的啦
type K = 'a' | 'b' | 'c'; type K2 = Extract<K, 'b'>; // pick 提取 type K22 = 'b'; type K3 = Exclude<K, 'b'>; // omit 排除 type K33 = 'a' | 'b';
上面这些 build in 其实都是用更底层的方法实现的.
1. Partial<T>
type MyPartial<T> = { [p in keyof T]? : T[p] };
里头有几个关键点,首先是 type MyPartial<T>
它有一个泛型,咱们能够把它想像成一个方法,经过这个方法能够制做出动态类型.
这个很很神奇吧,通常静态语言是没有这个概念的.
= 的后是一个对象, 意思是经过这个类型能够建立出一个对象类型.
而后经过 keyof T 把泛型的 keys for loop 放入到这个对象类型中.
属性的值类型,泽经过 T[p] 来获取回本来的类型.
经过 ? 来实现把全部的东西变成 undefined.
这就是 Partial 的实现过程.
其它的 build in 基本上也是按照上面这个思路作的。咱们一一来看看.
2. Required
type MyRequired<T> = { [p in keyof T]-? : T[p] };
关键是 -?
3. Readonly
type MyRequired<T> = { readonly [p in keyof T] : T[p] };
4. Record
type MyRecord<K extends string | number | symbol, T> = { [p in K] : T };
5.Pick
type MyPick<T, K extends keyof T> = { [p in K] : T[p] };
6.Omit
type MyOmit<T, K extends keyof T> = { [p in Exclude<keyof T, K>] : T[p] };
关键是用了 exclude
7. extract 的实现是这样的
type Extract<T, U> = T extends U ? T : never
用到了一个新的技巧相似 if else
当 T 是 extends U 那么输出 T 否则不输出 (never). 这样的一个设计就实现了过滤.
exclude 则反过来就好了
type Exclude<T, U> = T extends U ? never : T
咱们只要记得要过滤 keys 就能够用 if else 的方式就好了.
8. ReturnType 和
InstanceType 更复杂一些
除了用到 if else 也用到了一个新技巧
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
就是 infer R. 懒惰研究下去了. 下次继续更新吧
综合就是这几招啦
TypeFactory<T> = { [P in keyof T] : T[P] }
T extends U ? T : never
(...args: any) => infer R
https://fettblog.eu/typescript-built-in-generics/
http://realfiction.net/2019/02/03/typescript-type-shenanigans-2-specify-at-least-one-property
keyof, never, Pick, Exclude, Record, T in Keys, { }[Keys],
Partial
T extends U ? X : Y,
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
更新 2019-05-31
经常使用的 Pick, Exclude, Omit
refer : https://stackoverflow.com/questions/48215950/exclude-property-from-type
Omit 在 3.5 后是 default 了
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> class Person { name: string; age: number; } type keys = Exclude<keyof Person, 'name'>; const omit: Omit<Person, 'age'> = { name: 'name' } const y: Pick<Person, 'name'> = { name: 'name' }
还有 enum
enum OrderStatus { Pending = 'Pending', WaitForPayment = 'WaitForPayment', WaitForShipping = 'WaitForShipping', Completed = 'Completed', } const orderStatus: Exclude<OrderStatus, OrderStatus.Completed> = OrderStatus.Pending;
更新 2018-12-20
使用 mixin 代码
假设某些属性和方法咱们会在多个 component 上复用.
step 1 : 定义 Constructor 类型
type Constructor<T = {}> = new (...args: any[]) => T;
step 2 : 定义复用的 interface
export interface HaveGetNameMethod {
getName() : string
}
step 3: 定义这个 class 的 constructor
export type HaveGetNameCtor = Constructor<HaveGetNameMethod>;
step 4: 定义这个 class 的依赖 (一般是依赖注入的服务, 这里随便写而已)
export interface HaveGetNameMethodDependency {
name: string
}
step 5: 定义 mixin 方法
function MixinGetName<TBase extends Constructor<HaveGetNameMethodDependency>>(Base: TBase) : TBase & HaveGetNameCtor { return class extends Base { getName() { return this.name; } constructor(...args: any[]) { super(...args); } }; }
传入的 Base 必须实现依赖, 返回 Base & 这个 class, 这个 class 就是 step 3, 它实现了 step 1 的接口
step 6 : 定义咱们的 base 组件, 必须知足咱们要 exntends 的 class 的依赖
export class BaseComponent {
constructor(
public name: string
){ }
}
step 6 定义咱们要 extends 的 mixin class (这里能够接 combo)
export const MixinComponent: HaveGetNameCtor & typeof BaseComponent = mixinGetName(BaseComponent);
它的类型就是全部的 constructor 加起来. a(b(c(d))) <-- 如此的嵌套组合调用.
step 7 最后就是继承啦
export class TestMixinComponent extends MixinComponent implements OnInit, HaveGetNameMethod { constructor( ) { super('das'); } ngOnInit() { console.log(this.getName()); } }
implement 全部接口, 在 constructor 提供全部依赖. 这样就能够啦~
注 :
全部依赖都必须使用 public.
https://github.com/Microsoft/TypeScript/issues/17744
angular aot 有些场景下 life cycle hook 跑步起来哦
https://github.com/angular/angular/issues/19145
更新 2018-05-11
refer : https://blog.mariusschulz.com/2017/05/26/typescript-2-2-mixin-classes
class 动态继承, Mixin Classes
在写 Angular 的时候, component class 常常须要一些大众的功能或者属性.
要封装这些方法和属性,能够用 2 种方式,一种是 class 继承, 另外一种是注入另外一个 class
2 个方法各有各的优点.
今天主要说说继承 Mixin Classes
Material 里面有很好的实现,你们能够去看看代码.
Mixin Classes 是 typescript 的特性之一,比通常的继承灵活一些.
咱们假设有这样一个场景.
有 AbstractParentAComponent, ChildAComponent, AbstractParentBComponent, ChildBComponent 4 个组件类
ChildA 继承 ParentA, ChildB 继承 ParentB
假如 ChildA 和 ChildB 拥有共同的属性, 咱们要如何去封装复用呢?
这就是 Mixin 排上用场的地方
咱们把 A,B 共同点放入 ChildABComponent 类
而后 ChildA extends ChildAB extends ParentA 和 ChildB extends ChildAB extends ParentB
看到了吗, ChildAB 一下子继承了 ParentA 一下子又继承 ParentB,这就是灵活的地方了.
更新 2018-02-04
对于 null and undefined
咱们都知道 null 是一个很奇葩的东西.
好比 :
let a: { name: string } = null; //编辑时经过 console.log(a.name); //运行时报错
任何一个对象均可以是 null or underfined
因此就有了 a.name 在编辑时不会报错而在运行时报错的状况。
c# 也是这样的。
虽然咱们码农对代码意识很高,几乎每次都会很天然而然的避开这种错误的状况可是 "说好的编辑时报错呢 ? "
c# 中咱们会这样就规避上述的报错现象
a?.name。这和 angular template 语法是同样的。表示若是 a 是 null 那么就返回 null. 这样运行时获取的值是 null 也就不会报错了.
另外一种方法是 typescript 才有的, c# 没有. 叫 stricknullcheck = true
当你设置了这个后
let a: { name: string } = null; 在编辑时就报错了
你必须代表清楚
let a: { name: string } | null = null;
这样才行。
可是这样的代交是 a 因为是 对象或者 null
在智能提示时 a dot 就不会显示 name 了, 由于它有多是 null 啊
因而 就有了 感叹号 !
console.log( a!.name );
感叹号告诉 typescript 这里的 a 是不可能为 null or underfined 的。因此就 ok 了
1.接口奇葩验证
interface Abc { name : string } function abc(obj : Abc) { } let ttc = { name: "adad", age: 12 }; abc(ttc); //no error abc({ name: "adad", age: 12 }); //error
对象字面量会有特别的检查, 因此一个 no error ,一个 error.
2. readonly
const data: string = "xx"; let obj: { readonly name : string } = { name : "keatkeat" } obj.name = "tata"; //error
const for let, var, readonly for object properties.
3. 初始化对象时赋值 (v2.1)
class Person { constructor(data? : PartialPerson) { Object.assign(this, data); } public name : string } type PartialPerson = Partial<Person>; let person = new Person({ name : "x" }); console.log(person.name);
使用到了 v2.1 的特性 keyof, Partial<T>
4. async await
class Person { ajaxAsync(): Promise<string> { return new Promise<string>((resolve, reject) => { setTimeout(() => { resolve("data"); }, 5000); }); } }
和 c# 相似, c# 中 Task<string> 对应这里的 Promise<string>
(async function () { let person = new Person(); let data = await person.ajaxAsync(); console.log(data); person.ajaxAsync().then(() => { console.log(data); }); })()
使用也和 c# 同样, 必须在 async 方法中才可使用 await 关键字.
当 await 赶上 Promise 就会有反应了, 固然你也是把它当普通 promise 来使用哦.
捕获错误 :
使用 try catch 来捕获.
async method() { try { let data = await this.ajaxAsync(); } catch (error) { console.log(error); } }
不用 try catch 的捕获方式
async method() { let data = await this.ajaxAsync().catch((error) => { console.log(error); return "data"; //if error then data should be ? }); console.log(data); }
ajaxAsync 内部可使用 return Promise.reject("error loh") 或 throw "error loh" 的方式表示出错.
规则 :
await 关键字在 async 方法中才能使用
await 调用的方法 必须是 一个 async method 或则是一个返回 Promise 的方法.
try catch await 3个一块儿才能捕获错误.
执行顺序
class PersonComponent { timeout() : Promise<void> { return new Promise<void>((resolve) => { setTimeout(() => { console.log("2"); resolve(); }, 3000); }); } async ngOnInit() { await this.timeout(); console.log("3"); } } let p = new PersonComponent(); p.ngOnInit(); console.log("1");
因为没有使用 await p.ngOnInit() 因此 console.log("1") 优先执行. 而使用了 await 的 ngOnInit 是正常的. 因此即便 ng2 没使用 await 来调用 ngOnInit 咱们也不用担忧会有问题^^
容易产生的误解 : async & await , Promise , rxjs
首先 async await 只是让咱们的代码好读一些, 它也是使用 Promise 来作的.
rxjs 比 promise 灵活, 但不像 promise 简单理解, 而大部分的时候咱们只须要 promise (async & await), 因此只有当你要用 ”流“ 的概念时才使用 rxjs
而若是只是要一个异步方法那么请使用 async await / promise 就够了.
5. 扩展原型 extend prototype
refer : http://stackoverflow.com/questions/41192986/extending-the-string-class-doesnt-work-in-some-context
declare global { interface String { test(c: number): string; } interface Array<T> { test(c: number): string; } } String.prototype.test = function (c : number) { return "abc"; } Array.prototype.test = function (c : number) { return ""; } export class Extension { }
而后在 app.module import 它出来就能够了, 全局定义
import "./@stooges/extension";