angular2 学习笔记 (Typescript)

时间:2022-03-26 18:52:04

更新: 2020-04-21 

strictPropertyInitialization

class A {
age! : number;
}
interface A {
age: number;
} const a: {};
const aa = new A();

interface 是会直接被保护的,但是 class 不会, 如果希望行为一致那么就要开启 strictPropertyInitialization. 那么 class 就需要放入初始值了.

这也符合 c# 的方式,挺好的. 但是面对 angular 的话, 比如 @Input 你可能不希望它检查. 那么可以 age! 加叹号在属性后面来做断言

更新 : 2019-12-22 

3.7 的 ?. 和 ??

这个在 c# 也是有, 主要的功能就是替换掉 undefined 和 null 的写法, 注意在这里 null 和 undefined 没用特别区分

interface A {
b?: {
c?: {
age?: 'dada'
}
} | null
} const a: A | undefined = { b: null }
console.log(a?.b?.c); // undefined
console.log(a?.b?.c ?? 'dada'); // 'dada'

一个长长的对象, const result =  a.b.c.d; 如果其中一个是 undefined or null 那么就会报错.

有了 ?. 就可以这样写 const x = a?.b?.c?....只要其中一个是 null or undefined 那么就会直接返回 undefined

这样就不会报错了. then 之后要做的就是如果是 undefined 要怎样处理它.

const a = x?.y?.z ?? 'defualt value', 配上 ?? 就可以写 default value 了, ?? 的意思是如果前面是 null or undefined 就返回后面的 value

通常这 2 个会一直使用.

更新: 2019-12-11

keyof T string | number | symbol

2.9 版本后 keyof T 不仅仅是 string 了.

如果我们确定它是 string 可以这样写

type K2 = Extract<keyof Foo, string>;

更新 : 2019-11-23

Partial config 的做法

export type SetPartial<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
export type SetOmitPartial<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>; interface FullConifg {
name: string;
age: number;
option?: number
}
type PartialKeys = 'name';
type PartialConfig = SetPartial<FullConifg, PartialKeys>;
type DefaultConfig = SetOmitPartial<FullConifg, PartialKeys>; function method(config: PartialConfig) {
const defaultConfig: DefaultConfig = {
name: 'dada'
}
const { name, age, option } = {
...defaultConfig,
...config
};
console.log(name);
}

更新 : 2019-09-15 

Utility Types

在 typescript 我们可以通过一些 "方法" 来改变原有的 type, 变成新的 type

这个在 c# 是没有的.

先来说说一些 build in 的方法,然后在讲讲它底层是怎样制作出来的.

refer: https://www.typescriptlang.org/docs/handbook/utility-types.html

1. Partial<T>

Partial 得能力是把 T 的属性变成 undefined able

比如有个接口,

interface A {
name: string;
age: number;
}

我想把它变成

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

3. Readonly<T> 

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://*.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://*.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";