zoukankan      html  css  js  c++  java
  • 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";

     

  • 相关阅读:
    变量的创建和初始化
    HDU 1114 Piggy-Bank (dp)
    HDU 1421 搬寝室 (dp)
    HDU 2059 龟兔赛跑 (dp)
    HDU 2571 命运 (dp)
    HDU 1574 RP问题 (dp)
    HDU 2577 How to Type (字符串处理)
    HDU 1422 重温世界杯 (dp)
    HDU 2191 珍惜现在,感恩生活 (dp)
    HH实习 acm算法部 1689
  • 原文地址:https://www.cnblogs.com/keatkeat/p/6017416.html
Copyright © 2011-2022 走看看