Angular 学习笔记 (Typescript 高级篇)

由于 typescript 越来越复杂. 所以特意开多一个篇幅来记入一些比较难的, 和一些到了一定程度需要知道的基础.

主要参考

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

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

版本 feature

字节前端的 typescript (它掘金几篇都写的不错)

字节前端的 typescript (它掘金几篇都写的不错)

 

 

1. 名词术语

Basic Annotation 是基本注解 let a : string

Inline Type Annotation 是内联类型注解 let a : { name: string }

Union 是联合类型 string | number

intersection 是交叉类型 string & number

Tuple 是 元组类型[string, number]

Nullish Coalescing 是 ??

Optional Chaining 是可选链 obj?.name

Rest parameter 是  call(...args : string[])

Spread operator 是 call(...['a', 'b']) 

Conditional 是 T extends string ? number : never;

Type Guard 是 类型保护 arg is Foo

Destructuring 是解构 const { name } = { name: 'Derrick' }

Naked type 是 type Naked<T> = T extends ... (没有被任何东西包装)

NotNaked type 是 type NotNaked<T> = { o: T } extends ... // 在对象里面, 算被抱着了

Utility Types 是 Partial, Required, 等这些东西

Mapped types 是 { [P in TKeys] : any }

object literals 是 { name: string } 

 

2. Number Enums as flags

flags 这个概念 c# 也有 (点这里这里), 经常看到 enum 可以配合 | 来用, 比如 : 

PropertyInfo[] attrs = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); //获取attrs

简单理解就是把一个 enum 变成 类似 enum list

typescript 也允许我们做到这点. 点这里

首先定义 enum 的时候需要多写一些号码和符号进去进去 (这里用到的是二进制的移位方法, 我不太懂)

enum Abc {
    A = 0,
    B = 1 << 0,
    C = 1 << 1,
    D = 1 << 2,
    E = 1 << 3
}

然后呢,就可以这样子去使用了. 

let x: Abc = Abc.A | Abc.B;
if(x & Abc.B) {
    console.log('has B');
}
const hasB = !!(x & Abc.B); // true
const hasC = !!(x & Abc.C); // false

书里有写它的原理,和如果添加移除,但我目前没有使用到,所以就不研究了. 

 

2.1 扩展特定 enum 方法

通过 namespace

enum Abc {
    A
}
namespace Abc {
    export function print(): string {
        return 'dada';
    }
}
Abc.print()

 

3. 理解 target 和 lib 点这里

target es6 的话, lib 就会有 promise 这些东西.

有些时候我们想分开来管理. 

target 指的是 output 出来的 js 等级

比如 es5, 那么它就不会有 Promise, Set, Map 之类的.

lib 是我们开发环境用的. 我们当然希望什么都有咯.

所以一个比较合理的做法就是,

target es5 出来的 js 是 es5

lib dom, es6, 开发的时候我们用的 es6 的 js 

然后通过 Polyfill 来补上就好了.

 

4. declare overload method 点这里

没有 overload 的情况下可以这样 declare, 或者用 interface 也可以

type LongHand = {
  (a: number): number;
};
type ShortHand = (a: number) => number;

overload 情况下只可以用第一种

type LongHandAllowsOverloadDeclarations = {
  (a: number): number;
  (a: string): string;
};

 

5. 兼容性和变体 点这里

兼容性是指一个类型是否可以当另一个类型来使用 (有时候过多的限制会让代码很难写,所以哪怕是强类型我们也经常会要强转之类的, 所以不可以档死了)

对象结构 

type IsExtends<T, U> = T extends B ? true : false;
type A = { age: number };
type B = { age: number, name: string }; // 属性可以多, 不可以少
type result1 = IsExtends<B, A>; // true

函数参数

type Method = (age: number) => void;
type Method2 = () => void; // 参数可以少, 不可以多
type result2 = IsExtends<Method2, Method>; // true

 

上面都是比较简单的例子,只是数量上的问题

复杂的情况是参数类型,返回类型这些. 

比如 2 个方法对比, 第 1 个参数类型是子类, 第 2 个是父类, 这个能兼容吗 ?

说到这里可能就需要引入变体的概念了

点这里

首先讲一下定义

A ≼ B 意味着 A 是 B 的子类型。

A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。

协变(Covariant):只在同一个方向;

逆变(Contravariant):只在相反的方向;

双向协变(Bivariant):包括同一个方向和不同方向 (通常这个是不安全的,但有时候没有办法就比如强转一样)

不变(Invariant):如果类型不完全相同,则它们是不兼容的。

来一个函数类型的例子, 2个函数是否可兼容依据参数类型和返回类型 

灰狗 ≼ 狗 ≼ 动物

有一个方法是 狗 → 狗 

那么怎样的方法是和它兼容的 ? 

1. 灰狗 → 灰狗

2. 灰狗 → 动物

3. 动物 → 动物

4. 动物 → 灰狗 (正解)

参数类型默认情况下是双向的 (开启 strictFunctionTypes 之后就换成逆变),也就是说,只要是父类或者子类都 ok,返回类型是协变的,所以 a extends b 的话, a 的返回子类是 ok 的.

没开启 strictFunctionTypes 的时候

type methodParent = (p: Parent) => Parent;
type methodChild = (c: Child) => Child;
type result = IsExtends<methodChild, methodParent>; // true

开启之后就不行了, 改成父类就 ok 

type methodParent = (p: Parent) => Parent;
type methodChild = (c: Parent) => Child;
type result = IsExtends<methodChild, methodParent>; // true

为什么参数是逆变的, 而返回值是协变的呢 ? 

继承的概念就是我可以用 B 去替代 A. A 方法可以跑的地方,我全部换成 B 方法,它也要可以跑. 

假设方法 A 的参数是狗 (要求抽象), 那么调用 A 方法的时候就有可能传入狗或者灰狗 (要求抽象, 但也可以传入具体).

如果 B 方法的参数是灰狗 (要求具体), 而我拿 B 替换了 A, 当调用的人传入狗的时候就不 ok 了 (因为 A 只要求抽象,所以调用的人可能传入抽象, 但是 B 内部却需要具体, 所以直接翻车).

所以参数是逆变的. (参数需要更抽象才行)

假设返回值 A 是狗 (给予具体), 那么获取返回值之后使用的就是具体属性.

如果返回值 B 是动物 (给予更抽象), 那么就翻车了咯

所以返回值是协变的 (返回值要更具体)

协变就是说要求 A 类的地方可以用 A 或 A 的子类来替换 (不可以用 A 的父类), 更具体

逆变就是要求 A 类的地方可以用 A 或 A 的父类来替换 (不可以用 A 的子类). 更抽象

双向就是上面 2 个都可以

不变就是 A 只能用 A 不可以是其它的

 

6. Conditional, Infer

refer : https://fettblog.eu/typescript-union-to-intersection/ (联合类型转交叉类型)

当 contional 遇到 union (Distributive Conditional Types)

如果是 naked 的话会被拆成多个来解析, not naked 的话就是把 T 传过去用而已

type Naked<T> = T extends any ? { name: T } : never;
type result = Naked<'a' | 'b'>; // { name: 'a' } | { name: 'b' }

type NotNaked<T> = T[] extends any ? { name: T } : never;
type result2 = NotNaked<'a' | 'b'>; // { name: 'a' | 'b' }

Infer 有个特色就是如果 infer 是用在参数类型上, 由于是逆变 position, 最终出来的结果会变成交叉类型.

如果不是逆变的话,出来的结果会是联合类型

// 这是转为联合类型
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>;  // string
type T11 = Foo<{ a: string, b: number }>;  // string | number
// 这是转为交叉类型
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number

这几招通常用在转换 union to array, union to intersection 等

 

posted @ 2021-03-05 18:25  兴杰  阅读(700)  评论(0编辑  收藏  举报