• angular2 学习笔记 (Typescript)


    更新: 2021-03-05

    高手进修篇

    https://basarat.gitbook.io/typescript/ 高级书

    https://jkchao.github.io/typescript-book-chinese/ 高级书中文版

    https://zhuanlan.zhihu.com/p/296277982

    https://jkchao.github.io/typescript-book-chinese/tips/infer.html#%E4%BB%8B%E7%BB%8D

    小小黑科技 

    1.type Test<T> = T extends any ? ....

    这招经常用来处理 T = Union 然后希望出来的 result 也是 Union.

    2. keyof any 

    这个就是 string | number | symbol, 能被 object 接受的 key 就对了

    3. tuples 转 union 

    type array = [string, number];

    type union = array[number];

    number 就是 keyword 啦

    Conditional 和 infer 详解 

    Conditional types 和 Distributive conditional types

    https://juejin.cn/post/6844904057010651143

    conditional type 就是 type Test<T> = T extends string ? string : number 这种 if else

    Distributive conditional types 是说当 Union 遇上 conditional, 假设 T 是一个 Union 的时候. 它会被拆分 

    Test<string | number | boolean> =

    string extends string ? string : number | 

    number extends string ? string : number | 

    boolean extends string ? string : number

    变成 3 个然后是 Union

    下面这个例子很好理解

    conditional 经常配合 never 来用. 

    当 never 遇上 union 的时候会把 union 拿掉. 比如 type a = string | number | never. 最终 never 会消失. 

    当 never 遇到创建对象时, 比如下面这个, never 就表示这个 key 不要. 

    type FunctionPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
    }[keyof T];

    distributive 有一些限制, 有时候需要骗一下 typescript

    infer 是让我们拆解取出 type 的一个手法

    type UnboxPromise<T> = T extends Promise<infer V> ? V : never;
    type a = UnboxPromise<Promise<string>>; // string

    用法挺简单的, 就是写一个结构, 然后 extends, 在要拆解的地方写 infer 配上一个变量. 

    https://github.com/Microsoft/TypeScript/pull/21496

    infer 有时候会返回  Union 有时候返回 Intersection 

    我也看不太懂, 关键就是 co-variant positions 返回 union 和 contra-variant positions 返回 intersection 

    网上看到的解释是

    协变 (covariance), 逆变 (contravariance) 与不变 (invariance)

    能在使用父类型的场景中改用子类型的被称为协变。

    能在使用子类型的场景中改用父类型的被称为逆变。

    不能做到以上两点的被称为不变。

     

    当 Union 遇上 keyof

    type Test = keyof ({ name: string; age: string } | { age: string }) // age

    只有每一个 union 都有的 key 才会在最终出现. 

    https://stackoverflow.com/questions/57103834/typescript-omit-a-property-from-all-interfaces-in-a-union-but-keep-the-union-s

    来看看这个问题, 如果把 union 对象某个共同属性洗掉,然后留下其余的

    interface A {
      toRemove: string;
      key1: "this1";
      key2: number;
    }
    interface B {
      toRemove: string;
      key1: "this2";
      key3: string;
    }
    type C = A|B;

    如果我们用直觉写可能是这样 

    type CC = Omit<C, "toRemove">; // { key1: 'this1' | 'this2' }

    但是出来的结构不对, 因为 Omit 和 Exclude 是这样执行的

    type Exclude2<T, U> = T extends U ? never : T
    type Omit2<T, K extends string | number | symbol> = { [P in Exclude2<keyof T, K>]: T[P]; }

    最关键的地方就是 keyof T. 由于 T 是 union, 所以它只会取所有 union 对象都有的 key. 所以最终 loop 出来的结构就没有了 key 2 和 key 3

    要解决这个问题, 那么可以这么写 

    type DistributiveOmit<T, TKeys extends keyof T> = T extends any ? Omit<T, TKeys> : never;
    type Result = DistributiveOmit<C, 'toRemove'>; // type pp2 = Omit<A, "toRemove"> | Omit<B, "toRemove">

     

     

    更新: 2021-03-04

    一些常用的名词

    [string, number] 叫 Tuple

    string | number 叫 联合类型 又叫 Union

    string & number 叫 交叉类型 又叫 Intersection

    ?? 叫 Nullish Coalescing

    .? 叫 Optional Chaining

    ... 叫 Rest operator

    T extends string ? string : number 叫 Conditional

    T is string 叫 Type guard

    const { name } = { name: 'derrick' } 叫 解构 又叫 Destructuring

    更新: 2020-10-16

    generics and overload 

    https://medium.com/@wittydeveloper/typescript-generics-and-overloads-999679d121cf

    如果可以的话,我们通常会用 generics 和 conditional 来表达动态, 但是很多时候 typescript 很笨无法做到太过复杂的动态。

    那么只能回到最原始的 overload 写一堆. overload 最怕就是笛卡尔积, 一瞬间可以去到几十个 method 也不是问题...

    据说 4.1 会好一点. 希望吧 

    更新: 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://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";

     

  • 相关阅读:
    SSM整合
    SpringMVC学习笔记
    Spring笔记
    Spring之AOP在XML中的配置方法
    Spring之AOP的注解配置
    Mybatis学习笔记
    css学习笔记
    DOM技术
    Javascript学习笔记
    Hive导出复杂数据到csv文件
  • 原文地址:https://www.cnblogs.com/keatkeat/p/6017416.html
Copyright © 2020-2023  润新知