typescriptlang 笔记

目录

website

1. Get Started

  • typescript 给 js 添加了类型系统
  • ts 扩展了基本类型 any, unknown, never, void

2. Handbook

2.1. The TypeScript Handbook

ts 的目的是给 js 提供静态类型检测

2.2. The Basic

2.2.1. 静态类型检测

  • 静态类型系统描述了程序运行时 该有的行为和形状
  • 静态类型检测可以在程序运行前发现类型异常

2.2.2. Non-exception Failure

ts 可以捕获如下不会引起异常的错误

const user = {
    name: "Daniel",
    age: 26,
};

user.location; // 不存在的变量
user.name(); // 不可调用的函数

const v = Math.random() > 0.5 ? 'a' : 'b'
// 基本逻辑错误
if (v != 'a') {
    //    
} else if (v == 'b') {
    //
}

2.2.3. 作为工具

  • 提供补全功能
  • 重构
  • 跳到定义
  • 查找引用

2.2.4. tsc编译器

2.2.5. Emitting with Errors

使用 tsc 时, 加上 tsc --noEmitWithError 那么有异常时, 不会生成新的内容

2.2.6. 显式类型

2.2.7. 擦除类型

ts 编译后, 会把类型注解擦除掉

2.2.8. 降级

tsc --target esnext 可以编译成兼容指定 es 版本的代码

2.2.9. 严格性

  • tsconfig.json"strict": true 开启严格检查
  1. noImplicitAny

    出现 any 就抛出异常

  2. strictNullChecks

    默认情况下, null, undefined 可以赋值给任意值, 开启会检测该行为

2.3. 日常使用的类型

2.3.1. 基础类型 string, number, boolean

  • 与 js 一致
  • 不要使用 String Number Boolean , 虽然他们是合法的

2.3.2. Arrays

let a: number[] = [1]
let b: Array<String> = ["h"]

2.3.3. any

  • 如果想跳过某个值的类型检查时,可以指定它为 any
  • 如果编译器不能推测出对应值的类型,那么会将它设置成 any , 使用 noImplicitAny 禁用此行为

2.3.4. 变量上的类型注解

let a: string = "Hello"

大部分情况不需要加上这个注解

2.3.5. 函数

function foo(name: string): string {
    return name + "!"
}
let b = ["a", "b"]
b.forEach((s) => console.log(s.toUpperCase()))
  • 注解参数
  • 注解返回值
  • 通过 contextual typing 推测匿名函数的类型注解

2.3.6. 对象类型

function printName(obj: { first: string; last?: string }): void {
    console.log(typeof obj.last === 'undefined' ? obj.first : obj.last)
}
  • last? 属于可选属性

2.3.7. Union Types

function welcome(people: string[] | string) {
    if (Array.isArray(people)) {
        console.log("Welcome", people.join('and'))
    } else {
        console.log('Welcome: ' + people)
    }
}

function get3(s: number[] | string) {
    return s.slice(0, 3)
}
  • 可使用条件表达式, 获取分流指定的类型
  • 可以访问 union type 的公共属性

2.3.8. 类型别名

type ID = number | string
type Point = {
    x: number;
    y: number;
}
type Str = string

Note that aliases are only aliases - you cannot use type aliases to create different/distinct “versions” of the same type

2.3.9. Interfaces

interface Point {
    x: number;
    y: number
}
  1. interface 和 type 的区别
    1. 扩展属性
      interface P {
          name: string
      }
      interface C extends P {
          ho: string
      }
      
      type TP = {
          name: string
      }
      type TC = TP & {
          ho: string
      }
      let a: C;
      let b: TC
      a.ho
      b.ho
      
    2. 添加字段

      type 不能给已有类型添加字段

      interface A {
          a: string
      }
      interface A {
          b: string
      }
      let a: A
      a.b
      
      type W = {
          a: string
      }
      type W = {
          b: string
      }
      let aa: W
      aa.b // no Exist
      

2.3.10. 类型断言

function foo(p) {
    (p as string).toLocaleUpperCase()
}

类型断言只能将类型转换成更精确或更不精确的版本, 所以下面行为是错误的

const x = "hello" as number

但是可以通过下面方式绕过

const x = ("hello" as unknown) as number

2.3.11. 字面量类型

function foo(align: 'left' | 'right') { }
type Pos = 1 | 2
  1. 字面量推测
    function handleRequest(url: string, method: 'GET' | 'POST') { }
    
    const req = { url: "www.baidu.com", method: 'GET' }
    handleRequest(req.url, req.method) // wrong. Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
    
    // 可通过下面方式修复
    const req1 = { url: "www.baidu.com", method: 'GET' } as const;
    handleRequest(req1.url, req1.method)
    
    const req2 = { url: "www.baidu.com", method: "GET" as "GET" };
    handleRequest(req2.url, req2.method);
    handleRequest(req.url, req.method as "GET");
    

2.3.12. null 和 undefined

通过 strictNullChecks 严格检测空值

declare const loggedInUsername: string;
const users = [
    { name: 'Oby', age: 12 },
    { name: 'Heera', age: 32 },
];
const loggedInUser = users.find((u) => u.name === loggedInUsername);
// 如果开启 strictNullChecks, 下面会报错 Object is possibly 'undefined'.
console.log(loggedInUser.age);

// 在开启状态下,可以通过强制的非空断言绕过
console.log(loggedInUser!.age)

2.3.13. 枚举

2.3.14. 少用的基础类型

const a: bigint = 10n

const x1 = Symbol('name')
const x2 = Symbol('name')
if (x1 === x2) {
    // 可以识别出此类错误
    // This condition will always return 'false' since the types 'typeof x1' and 'typeof x2' have no overlap
}

2.4. 类型收缩 Narrowing

2.4.1. typeof 类型守卫

使用 typeof 把变量收缩指指定类型

function printAll(strs: string | string[]) {
    if (typeof strs === 'string') {
        console.log(strs.repeat(2))
    } else {
        console.log(strs.join(','))
    }
}

2.4.2. truthiness 收缩

function printAll(strs: string | string[] | null) {
    if (strs && typeof strs === 'object') {
        console.log(strs.join(','))
    } else if (typeof strs === 'string') {
        console.log(strs.repeat(2))
    }
}

2.4.3. 等式收缩

function foo(x: string | number, y: string | null) {
    if (x === y) {
        // 可以推测出 x,y 都为 string
        console.log(x.repeat(1))
    }
}

2.4.4. in 收缩

type Fish = { swim: () => void }
type Bird = { fly: () => void }
type Human = { swim?: () => void; fly?: () => void }

function move(animal: Fish | Bird | Human) {
    if ('swim' in animal) {
        // 可以推测是 animal 为 Fish | Human    
    } else {
        // 可以推测是 animal 为 Bird | Human    
    }
}

2.4.5. instanceof 收缩

class Foo {
    print() { }
}
function bar(x: Foo | string) {
    if (x instanceof Foo) {
        // 如果 x 的原型链上有 Foo.prototype
        x.print()
    } else {
        x.repeat(2)
    }
}

2.4.6. 赋值

let x = Math.random() < 0.5 ? 10 : 'hello'
x = true; // wrong, 因为 x 声明为 number|string

2.4.7. 控制流分析

2.4.8. 类型预测

使用 param is Type 的模式,收缩变量的类型

type Fish = { swim: () => void }
type Bird = { fly: () => void }
function isFish(animal: Fish | Bird): animal is Fish {
    return (animal as Fish).swim !== undefined
}
function foo(animal: Fish | Bird) {
    if (isFish(animal)) {
        animal.swim()
    } else {
        animal.fly()
    }
}

function getFishs(animal: (Fish | Bird)[]): Fish[] {
    return animal.filter(isFish)
}

2.4.9. 区分联合类型

interface Circle {
    kind: 'circle'
    radius: number
}
interface Square {
    kind: 'square',
    sideLength: number
}
type Shape = Circle | Square
function area(shape: Shape) {
    if (shape.kind === 'circle') {
        // 可以推测出 shape 为 Cicle
        return Math.PI * shape.radius ** 2
    } else {
        // 可以推测出 shape 为 Square
        return shape.sideLength ** 2
    }
}

area({
    kind: 'circle', // 输入 cicle时, 补全会过滤掉非 Circle 的属性
    radius: 10,
})

2.4.10. never

当收缩联合类型时, 剔除掉所有可能的类型后,ts 会把变量标记为 never

function foo(x: string | number) {
    if (typeof x === 'string') {
        //
    } else if (typeof x === 'number') {
        //
    } else {
        // x 为 never
    }
}

2.4.11. 使用 never 穷尽检测

任意的类型都可以赋值给 never

function foo(x: string | number) {
    if (typeof x === 'string') {
        //
    } else if (typeof x === 'number') {
        //
    } else {
        // 假如 x 改为 string|number|null, 那么ts 会报错。
        // 用此种方式可以保证后续给类型添加成员时,所以分支情况都考虑到
        const _exhaustiveCheck: never = x;
    }
}

2.5. 函数

2.5.1. 函数类型表达式

function foo(fn: (a: string) => void) { }
type Callback = (a: string) => void
type C2 = (a) => void; // a 为 any

如果没有指定参数类型, 那么其为 any

2.5.2. 给函数添加属性

type FnWithDesc = {
    desc: string;
    (arg: string): string; // 注意 不是 => 而是 :
}

function foo(fn: FnWithDesc) {
    return fn.desc + fn("hello")
}

2.5.3. 构造器签名

interface Some {
    foo: string
}

// 既可以直接调用,又可以作为构造器
type SomeContruct = {
    new(arg: string): Some;
    (arg: string): Some
}

// 等同上述的
interface Some2Contruct {
    new(arg: string): Some;
    (arg: string): Some
}

function foo(C: SomeContruct, C2: Some2Contruct) {
    let c1 = new C('c')
    c1.foo
    let c2 = C('c')
    c2.foo
    let c3 = new C2('')
    c3.foo
    let c4 = C2('')
    c4.foo
}

2.5.4. 泛型函数

function first<T>(arr: T[]): T | undefined {
    return arr[0]
}
let a = first(['1']) // string 
let b = first([2]) // number
let c = first[] // undefined
  1. 推测
  2. 约束

    使用 extends 约束 类型

    function longer<T extends { length: number }>(a: T, b: T): T {
        return a.length > b.length ? a : b
    }
    longer('a', 'bbb')
    longer(10, 2) // 报错
    
  3. 写好泛型的规则
    • 减少类型参数的使用
    • 如果一个类型参数只出现一次,那么它通常是不需要的

2.5.5. 可选参数

function foo(x?: number) {

}

尽量不要给回调函数添加可选参数签名,如下

function foreach<T>(arr: T[], callback: (a: T, index?: number) => void) {
    for (let i = 0; i < arr.length; i++) {
        callback(arr[i], i)
    }
}

foreach([], (ele, idx) => {
    console.log(idx.toFixed(10), ele) // Object(idx) is possibly 'undefined'
})

函数调用多传参数, ts 不会报错的

2.5.6. 函数重载

// 重载签名
function makeDate(ts: number): Date;
function makeDate(m: number, d: number, y: number): Date;

// 实现签名
// 注意返回值也写上,与上述签名保持一致
function makeDate(mOrTs: number, d?: number, y?: number): Date {
    if (d !== undefined && y !== undefined) {
        return new Date(y, mOrTs, d);
    } else {
        return new Date(mOrTs);
    }
}

makeDate(10);
makeDate(10, 12, 34);
makeDate(10, 23); // error

外部只能看到重载签名, 不能看到实现签名

实现签名 必须和 重载签名 兼容 , 包括参数签名和返回值

function fn(x: number): number;
function fn(x: string): string; // wrong
function fn(x: number): number {
    return x
}

function fn1(x: number): number;
function fn1(x: string): boolean; // wrong
function fn1(x: number | string) {
    return 1
}

尽可能用联合类型替代重载

2.5.7. 声明 this

interface User {
    admin: string
}

interface DB {
    filterUsers(filters: (this: User) => boolean): User[]
}
function foo(db: DB) {
    // 注意不要使用箭头函数
    let users = db.filterUsers(function () {
        return this.admin === 'admin'
    })
}

2.5.8. 其他涉及函数的类型

  1. void

    voidundefined 是不一样的

  2. object

    objectObject 是不一样的, 应该始终使用 object

  3. unknown

    unknownany 类似,但是对其值的操作都是非法的

    function f1(a: any) {
        a.aa
    }
    function f2(a: unknown) {
        a.aa // wrong
    }
    
  4. never

    抛出异常或不可能达到的分支时

    function foo(): never {
        throw 'error'
    }
    
  5. Function

    如果要接受任意函数,但是不调用它,可以使用 ()=>void

2.5.9. 剩余参数

function add(x: number, ...rest: number[]): number {
    return x + rest.reduce((a, b) => a + b, 0)
}

2.5.10. 参数解构

function foo({ a, b }: { a: number, b: string }) {
    return b.length + a
}

2.5.11. void 返回值

当函数的类型定义返回值是 void 时,

  • 其他有返回值的函数,也可以赋值给该类型
  • 函数调用的结果为 void
type Fn = () => void
let f1: Fn = () => true
let f2: Fn = () => 1
let a1 = f1() // void
let a2 = f2() // void

如果是函数实现中使用 void 那么就必须匹配

function foo(): void {
    return 1; // wrong
}

2.6. 对象类型

2.6.1. 属性修饰符

  1. 可选属性
    interface Shape {
        xPos: number
        yPos?: number
    }
    
  2. 只读属性
    interface P {
        readonly name: string
    }
    interface P2 {
        name: string
    }
    let p: P = {
        name: "Hi"
    }
    let p2: P2 = p;
    // 可以改变 p 的值
    p2.name += ' age'
    
    • readonly 表示该属性不能重新写入,如果其值是对象,不会影响该对象的属性的写入
    • 可以通过别名的方式,更改 readonly 的属性的值
  3. 索引签名
    // 索引类型只能是 string 或 number
    interface Arr {
        [index: number]: string
    }
    interface Arr1 {
        [index: string]: number[]
    }
    
    // 如果同时存在 string 或 number 索引,
    // number 索引的类型必须与 string 的兼容
    interface Arr2 {
        [index: number]: string
        [other: string]: string | number
    }
    
    interface S0 {
        // 只读
        readonly [name: string]: string
    }
    
    // 如果同时存在索引属性和具名属性
    // 那么具名属性的类型 必须与 string 的索引属性的类型兼容
    interface S2 {
        [name: string]: string | number
        length: number
        val: string
    }
    

2.6.2. 扩展类型

interface P {
    name: string,
    tmp: any[]
}
interface C extends P {
    age: number,
    tmp: string[]
}
let c: C = {
    name: 'name',
    age: 1,
    tmp: ['str']
}

如果扩展已有的属性,那么其类型必须与父级的类型的子集

  1. 交叉类型
    interface Colorful {
        color: string
    }
    interface Circle {
        radius: number,
        list: number[]
    }
    
    interface Other {
        list: object
    }
    
    type ColorfulCircle = Colorful & Circle & Other
    
    let c: ColorfulCircle = {
        color: "0xffffff",
        radius: 12,
        // number[] & object
        list: [12]
    }
    
    • & 产生新的类型, 会合并所有的属性
    • 如果两个类型有相同名字的属性, 那么其类型也是使用 &

    交叉类型和 extends 不同在于对于冲突的属性的处理方式, 交叉类型会将两个类型交叉, extends 则是子类型属性的类型是父类型属性的类型的子集

2.6.3. 泛型对象类型

interface Box<T> {
    contents: T
}
type B<T> = {
    contents: T
}
type OrNull<T> = T | null

2.6.4. 只读数组

function foo(arr: readonly string[]) {
    // 不能使用 push, sort 等做改变该数组的操作
    // 不能重新赋值给元素
    let a: string[] = arr; // wrong, 不能赋值给非只读数组
}

// 等同上
function bar(arr: ReadonlyArray<string>) {

}

typescript 特有, 运行时没有这种类型

2.6.5. 元组

type Pair = [string, number]
type OptionalTuple = [string, number?]
type StringNumbers = [string, ...number[]]
type StringsNumber = [...string[], number]
type StringBoolsNumber = [string, ...boolean[], number]
  1. 只读元组
    function foo([x, y]: readonly [x: number, y: number]) {
        return x * y
    }
    

    一般都会把元组声明成只读

2.7. 类型操作 - 从类型中创建类型

2.7.1. 泛型

  1. 泛型方法
    function id<T>(arg: T): T {
        return arg;
    }
    
    let id1: <T>(a: T) => T = id;
    let id2: { <T>(a: T): T } = id;
    interface IdFn {
        <T>(a: T): T;
    }
    let id3: IdFn = id;
    
    interface IdFn2<T> {
        (a: T): T;
    }
    let id4: IdFn2<number> = id;
    function id<T>(arg: T): T {
        return arg
    }
    
    let id1: <T>(a: T) => T = id
    let id2: { <T>(a: T): T } = id
    

    没有泛型 enum 和 泛型 namespace

  2. 泛型类
    class My<T> {
        static p1: T; // wrong
        prop1: T;
    }
    

    泛型只作用与类的实例部分,不能作用于 static 部分

  3. 泛型约束
    interface LengthWise {
        length: number
    }
    
    function foo<T extends LengthWise>(arg: T): number {
        return arg.length
    }
    foo({ length: 3, a: 23 })
    
  4. 使用类型参数
    1. 获取对象的属性集合
      function prop<T, P extends keyof T>(obj: T, key: P) {
          return obj[key];
      }
      
      let obj = { a: 1, b: 2 };
      prop(obj, 'a');
      prop(obj, 'c'); // wrong
      
  5. 使用泛型类

    工厂函数

    class Animal {
        name = 'animal';
    }
    class Bird extends Animal {
        fly() { }
    }
    
    class Fish extends Animal {
        swim() { }
    }
    
    function instance<T extends Animal>(arg: new () => T): T {
        return new arg();
    }
    
    instance(Fish).swim();
    instance(Bird).fly();
    

2.7.2. keyof

interface Point {
    x: number;
    y: number
}
type P = keyof Point
let p: P = 'x' // 'x' 'y'

interface A {
    [index: number]: string
}
type PA = keyof A // number

interface B {
    [index: string]: string
}
type PB = keyof B // number | string

2.7.3. typeof

function f() {
    return { x: 1, ops: 'hello' }
}
type F = ReturnType<typeof f>
let ff: F = {
    x: 23,
    ops: 'name'
}

typeof 仅作用于 标识符和它的属性

2.7.4. 索引访问符

interface Person {
    name: string,
    age: number
}
type Name = Person['name'] // string

let list = [{ name: 'Mark', age: 10 }]
type P = typeof list[number]
let p: P = {
    name: 'name',
    age: 9
}

const pp = 'age';
type PPP = Person[pp]; // wrong  不能用变量

type Age = 'age'
type PPP1 = Person[Age] // 可以用 类型别名.  number

2.7.5. 条件类型

interface IdLabel {
    id: number;
}
interface NameLabel {
    name: string;
}
type Label<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(arg: T): Label<T> {
    return (
        typeof arg === 'number'
            ? {
                id: arg,
            }
            : { name: arg }
    ) as Label<T>;
}
let n = createLabel('hello');
n.name;
let a = createLabel(10);
a.id;
interface IdLabel {
    id: number
}
interface NameLabel {
    name: string
}
type Label<T extends number | string> = T extends number ? IdLabel : NameLabel
function createLabel<T extends number | string>(arg: T): Label<T> {
    return typeof arg === 'number' ? {
        id: arg
    } : { name: arg }
}
let n = createLabel('hello')
n.name
let a = createLabel(10)
a.id

根据函数输入参数的类型决定返回类型

使用条件类型进行约束

type MessageOf<T> = T extends { message: unknown } ? T['message'] : never
type A = MessageOf<{ message: string[] }> // string[]

type Flatten<T> = T extends any[] ? T[number] : T
type B = Flatten<number[]> // number
type C = Flatten<number> // number
  1. infer
    type Flatten<T> = T extends Array<infer Item> ? Item : T
    type A = Flatten<string[]> // string
    
    type ReturnType0<T> = T extends () => infer Return ? Return : never
    type B = ReturnType0<() => number> // number
    

    infer 会产生一个新的类型变量

    infer 有多个签名的函数时, 会使用最后一个的类型

    declare function stringOrNum(x: string): number;
    declare function stringOrNum(x: number): string;
    declare function stringOrNum(x: string | number): string | number;
    
    type T1 = ReturnType<typeof stringOrNum>; // string | number
    

    对于联合类型, 会自动分配

    type ToArray<T> = T extends any ? T[] : never
    type A = ToArray<number | string> // number[] | string[]
    
    type ToArrayNoDist<T> = [T] extends [any] ? T[] : never
    type B = ToArrayNoDist<number | string> // (number|string)[]
    

2.7.6. Mapped Types

  1. 修改某个类型的属性的类型
    type OptionFlag<T> = {
        [Prop in keyof T]: boolean
    }
    
    interface Animal {
        fly(): void;
        name: string
    }
    
    type Ops = OptionFlag<Animal>
    let a: Ops = {
        fly: true,
        name: false
    }
    
  2. 更变修饰符
    interface ReadonlyAccount {
        readonly name: string
        readonly id: number
        time?: Date
    }
    
    type RmModifier<T> = {
        -readonly [P in keyof T]-?: T[P]
    }
    type LooseAccount = RmModifier<ReadonlyAccount>
    // {
    //     name: string;
    //     id: string;
    //     time: Date
    // }
    
  3. 重新映射类型
    1. 重命名属性
      type Getters<T> = {
          [Prop in keyof T as `get${Capitalize<Prop & string>}`]: () => T[Prop];
      };
      interface Person {
          name: string;
          age: number;
      }
      type GetPerson = Getters<Person>;
      let p: GetPerson = {
          getName() {
              return '';
          },
          getAge() {
              return 1;
          },
      };
      
    2. 过滤某个属性
      type RmKind<T> = {
          [Prop in keyof T as Exclude<Prop, 'kind'>]: T[Prop];
      };
      interface Foo {
          kind: 'hello';
          age: 10;
      }
      type FooWithoutKind = RmKind<Foo>;
      
      let a: FooWithoutKind = {
          age: 10,
      };
      
    3. 映射并集
      type EventConfig<Events extends { kind: string }> = {
          [E in Events as E['kind']]: (event: E) => void;
      };
      type ClickEvt = {
          kind: 'click';
          x: number;
      };
      type MoveEvt = {
          kind: 'move';
          y: number;
      };
      type Config = EventConfig<ClickEvt | MoveEvt>;
      let c: Config = {
          click(e) {
              e.x;
          },
          move(e) {
              e.y;
          },
      };
      
  4. 模板字面量类型
    1. 构建事件
      type ToEventSource<T> = {
          on(eventName: `${string & keyof T}Changed`, cb: (event: any) => void): void;
      };
      
      function makeWatch<T>(obj: T): ToEventSource<T> {
          throw '';
      }
      
      let p = makeWatch({ firstName: 'Mery', lastName: 'John' });
      p.on('firstNameChanged', () => 1);
      p.on('lastNameChanged', () => 2);
      
    2. 上述的,根据名字指定 cb 的参数类型
      type ToEventSource<T> = {
          on<Key extends string & keyof T>(eventName: `${Key}Changed`, cb: (event: T[Key]) => void): void;
      };
      
      function makeWatch<T>(obj: T): ToEventSource<T> {
          throw '';
      }
      
      let p = makeWatch({ firstName: 'Mery', lastName: 'John', age: 2 });
      p.on('firstNameChanged', (e) => {
          typeof e === 'string';
      });
      p.on('ageChanged', (e) => {
          typeof e === 'number';
      });
      
    3. 内置的操作 StringType 的类型
      1. Uppercase<StringType>
        type A = 'hello, wor'
        type B = Uppercase<A>; // 'HELLO, WOR'
        
      2. Lowercase<StringType>
        type A = 'HELL'
        type B = Lowercase<A> // 'hell
        
      3. Capitalize<StringType>
        type A = 'hello'
        type B = Capitalize<A> // 'Hello'
        
      4. Uncapitalize<StringType>
        type A = 'HLL'
        type B = Uncapitalize<A> // 'hLL'
        

        内部实现原理是使用了 js 的 toUpperCasetoLowerCase

2.8.

2.8.1. 类成员

  1. 字段
    class Greeter {
        x: number;
        y = 1;
        z;
        readonly aa = 1;
    }
    
  2. strictPropertyInitialization

    如果开启了该 flag 那么字段必须被初始化, (在构造器或字段声明位置)

    class Ok {
        x: number; // wrong
        y: number;
        z: number; // wrong
        aa!: number; // 加个 ! 可以绕过 strictPropertyInitialization
        constructor() {
            this.y = 1
            // 因为方法可能被子类重写,所以在通过调用方法来初始化字段不能绕过
            this.setZ() 
        }
        setZ() {
            this.z = 1
        }
    }
    
  3. 构造器
    class Base {
        x: number
    }
    
    class Child extends Base {
        constructor() {
            super()
            this.x = 1
        }
    }
    
    • 子类使用 this 前必须调用 super()
    • 构造器没有泛型和返回类型注解
  4. 方法
    class Foo {
        x = 1
        print(): void {
            console.log(this.x)
        }
    }
    
  5. Getters/Setters
    class Thing {
        _size = 1;
        get size() {
            return this._size;
        }
        set size(v: string | number) {
            this._size = Number(v);
        }
        _x = 2;
        get x() {
            return this._x;
        }
        set x(v) {
            // v 被推测成 number
        }
    }
    
    • 如果有 get 没有 set 那么会变成 readonly
    • 如果 set 没有指定类型, 那么会推测成与 get 一致
    • getset 需要有相同的可见性
  6. 索引签名
    class Foo {
        [name: string]: boolean | (() => void)
        print() {
    
        }
        x = true
    }
    

    因为涉及到 索引签名 与其他成员的兼容性问题, 所以一般不怎么使用

2.8.2. 继承

  1. implements
    interface A {
        x: number;
        y?: number;
    }
    interface B {
        foo(): void;
    }
    
    class C implements A, B {
        x = 1;
        foo() {
            //
        }
    }
    
    • 可以实现多个接口
    • implements 只是检查 class 是否满足接口, 接口的类型不会影响到 class 的成员的类型
  2. extends
  3. 重写
    1. 重写方法
      class Base {
          greet() {
              console.log('hello');
          }
      }
      class Derived extends Base {
          greet(name?: string): void {
              super.greet();
              console.log(name);
          }
      }
      

      重写的方法, 签名必须兼容父类的方法,不然编译不通过

    2. 重写字段

      如果只是想指定子类字段类型为父类的子集,而不是重新声明,可以使用 declare

      class Base {
          x: number | string
      }
      class Derived extends Base {
          declare x: string
      }
      
  4. 初始化顺序

    依据 js 的定义

    • 父类字段
    • 父类构造器
    • 子类字段
    • 子类构造器
  5. 继承内置类型

    如果要转义成 ES5 的代码, 继承内置类型时,使用如下方式, 不然原型链会缺失当前的类 (MsgError)

      class MsgError extends Error {
        constructor(m: string) {
          super(m);
          // Set the prototype explicitly.
          Object.setPrototypeOf(this, MsgError.prototype);
        }
        sayHello() {
          return "hello " + this.message;
        }
      }
    #+end_#+begin_src 
    
    

2.8.3. 成员可视性

  1. public

    默认

  2. protected

    可以通过继承改变对应字段的访问性

    class Base {
        protected x = 1
    }
    class Derived extends Base {
        x = 2
    }
    new Derived().x
    
    class Base {
        protected x = 1;
    }
    class Derived2 extends Base {
        f1(other: Derived2) {
            other.x = 10;
        }
        f2(other: Base) {
            other.x = 10; // wrong
        }
    }
    

    不能通过父类的引用访问 protected 成员

  3. private

    ts 运行跨实例访问 private 成员

    class A {
      private x = 10;
      public sameAs(other: A) {
        // No error
        return other.x === this.x;
      }
    }
    

    成员可视性只运用于编译阶段,如果要实现真正的私有字段,使用 # ,或者 WeakMap (当使用 # 编译成js时,ts就是使用 WeakMap)

2.8.4. 静态成员

class Base {
    static x = 1
    // 添加可视性
    private static y = 2
}
// 可继承
class Derived extends Base {

}
Base.x
Derived.x
Base.y // wrong

对于 name , length , call 不能使用 static 字段

ts 没有静态类

  1. static 块
    class Foo {
        static #count = 0;
        get count() {
            return Foo.#count;
        }
        static {
            Foo.#count--;
        }
    }
    

    可在 static 块执行代码,访问私有成员

2.8.5. 泛型类

class A<T>{
    content: T
    constructor(c: T) {
        this.content = c
    }
}
let a = new A('')
a.content // string

静态成员不能引用类的泛型参数

2.8.6. this

  1. 箭头函数
    class MyClass {
        name = "My"
        getName = () => this.name
    }
    
    new MyClass().getName() // My
    
    • 使用箭头函数可以不丢失 this
    • 每个实例都会有一个 该函数的副本, 所以会有更多的内存消耗
    • 子类不能使用 super.getName 访问父类的箭头函数
  2. this 参数
    class MyClass {
      name = "MyClass";
      getName(this: MyClass) {
        return this.name;
      }
    }
    const c = new MyClass();
    // OK
    c.getName();
    
    // Error, would crash
    const g = c.getName;
    console.log(g());
    
    • 可以通过该方法,在编译时校验调用时的 this 指向
    • 实例共享方法,所以不会有额外的内存消耗
    • 子类可以使用 super.getName
  3. this 类型

    使用 this 类型,可以动态引用当前类型

    class Box {
        add(box: this) {}
    }
    
    class ChildBox extends Box {
        content: string;
    }
    
    let child = new ChildBox();
    let base = new Box();
    child.add(base);
    
  4. this is Type

    可以用来收缩成具体的实例

    class FSObj {
        isFile(): this is FileObj {
            return this instanceof FileObj
        }
        isDir(): this is DirObj {
            return this instanceof DirObj
        }
    }
    class FileObj extends FSObj {
        content: string = ''
    }
    class DirObj extends FSObj {
        children = []
    }
    
    let a: FSObj = new FileObj()
    if (a.isFile()) {
        a.content
    }
    if (a.isDir()) {
        a.children
    }
    

2.8.7. 构造器参数属性

class Foo {
    constructor(private a: number = 1, public readonly b: number[] = [])
}
const f = new Foo()
f.b

2.8.8. 类表达式

let Some = class <T>{
    constructor(public content: T) {

    }
}
let a = new Some('')
a.content

2.8.9. 抽象类

abstract class Base {
    abstract getName(): string
    printName() {
        console.log('welcome', this.getName())
    }
}

class Derived extends Base {
    getName() {
        return "Mary"
    }
}

function greet(ctor: new () => Base) {
    let c = new ctor()
    c.printName()
}
greet(Derived)
greet(Base) // wrong

2.8.10. 类之间的关系

如果两个类,字段重合,那么可以认为是等同的

class A {
    name: string
}
class B {
    name: string
}
class C {
    name: string
    last: string
}
let a: A = new B() // right
let c: A = new C() // right

2.9. 模块

2.9.1. 如何定义模块

  • ts 中, 只要在顶级有 importexport , 都会被认为是模块
  • 模块中的变量,都在自己的作用域中

2.9.2. ts 额外的语法

  1. import type
    import type { B } from './m.js'
    import { A, type B } from './a.js'
    

3. Reference

3.1. 内置的工具类型

3.1.1. Awaited<T>

type A = Awaited<Promise<string>>; // string
// 可以递归
type B = Awaited<Promise<Promise<string>>>; // string
type C = Awaited<Promise<string> | number>; // string | number

3.1.2. Partial<T>

将属性都标识成 可选

interface Todo {
    title: string;
    desc: string;
}
type PT = Partial<Todo>;
// {
//     title?: string | undefined;
//     desc?: string | undefined;
// }

3.1.3. Required<T>

将所有属性标识成 必须

interface Todo {
    title: string;
    desc?: string;
}
type T = Required<Todo>;
// {
//     title: string;
//     desc: string;
// }

3.1.4. Readonly<T>

将所有属性标识成 只读

interface Todo {
    title: string;
    desc?: string;
}
type T = Readonly<Todo>;
// {
//     readonly title: string;
//     readonly desc?: string | undefined;
// }

function freeze<T>(obj: T): Readonly<T>

3.1.5. Record<Keys, T>

根据 keys 构建一个类型, 其属性的类型为 T

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

let cats: Record<'kitty' | 'boris', CatInfo> = {
    kitty: { name: 'kitt', age: 1 },
    boris: { name: 'boris', age: 1 },
};

cats.boris.age;

3.1.6. Pick<T, Keys>

从 T 中选择 Keys 构成新的类型

interface Todo {
    title: string;
    desc: string;
    completed: boolean;
}

type TodoPreview = Pick<Todo, 'desc' | 'title'>;
// {
//     desc: string;
//     title: string;
// }

3.1.7. Omit<T, Keys>

从 T 中剔除 Keys 构成新的类型

interface Todo {
    title: string;
    desc: string;
    completed: boolean;
}

type TodoPreview = Omit<Todo, 'desc' | 'completed'>;
// {
//     title: string;
// }

3.1.8. Exclude<UnionType, ExcludeMembers>

从 UnionType 中 剔除 ExcludeMembers

type A = 'a' | 'b' | 'c' | 'd';
type B = Exclude<A, 'c' | 'd'>; // 'a' | 'b'

3.1.9. Extract<Type, Union>

从 Type 中提取 Union 构成新的类型

type A = 'a' | 'b' | 'c' | 'd';
type B = Extract<A, 'c' | 'd' | 'e'>; // 'c' | 'd'

3.1.10. NonNullable<T>

剔除 null undefined

type A = string | number | null | undefined;
type B = NonNullable<A>; // string | number

3.1.11. Parameters<T>

将函数类型的参数提取成元组类型

type A = Parameters<(a: string, b: boolean) => void>;
// [a: string, b: number]

// 泛型
type B = Parameters<<T>(a: T) => T>;
// [a: unknown]

type C = Parameters<any>;
// unknown[]

declare function f1(arg: { a: number; b: string }): void;
type D = Parameters<typeof f1>;
// [arg: {
//     a: number;
//     b: string;
// }]

3.1.12. ConstructorParameters<T>

与 Parameters<T> 类似, 但是 T 必须是构造器

type T0 = ConstructorParameters<ErrorConstructor>;
// [message?: string | undefined]

3.1.13. ReturnType<T>

获取函数的返回类型

type T0 = ReturnType<any>; // any

type T1 = ReturnType<<T>(a: T) => T>; // unknown

type T2 = ReturnType<<T extends number[]>(a: T) => T>; // number[]

declare function f1(): number;
type T3 = ReturnType<typeof f1>; // number

3.1.14. InstanceType<T>

类的实例的类型

class C {
    a: string;
    b: number;
}
type C0 = InstanceType<typeof C>;
let a: C0 = {
    a: ',',
    b: 1,
};

3.1.15. ThisParameterType<T>

获取 this 的类型

declare function f(this: string): void;
type T0 = ThisParameterType<typeof f>; // string

3.1.16. OmitThisParameter<T>

去除 this 的类型

declare function f(this: string): void;
type f1 = OmitThisParameter<typeof f>; // ()=>void

3.1.17. ThisType<T>

没有产生新的类型, 但是可以给标记其他类型中的 this 的类型, 需要开启 noImplicitThis

type ObjectDescriptor<D, M> = {
    data?: D;
    methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
    let data: object = desc.data || {};
    let methods: object = desc.methods || {};
    return { ...data, ...methods } as D & M;
}
let obj = makeObject({
    data: { x: 0, y: 0 },
    methods: {
        moveBy(dx: number, dy: number) {
            this.x += dx; // Strongly typed this
            this.y += dy; // Strongly typed this
            this.xxx // wrong
        },
    },
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

3.1.18. Uppercase<StringType>

3.1.19. Lowercase<StringType>

3.1.20. Capitalize<StringType>

3.1.21. Uncapitalize<StringType>

3.2. Cheat Sheet

内置的基础类型: number string boolean null undefined symbol bigint any unknown void never

3.3. 装饰器

日期: 2022-11-22

Created: 2022-12-20 Tue 17:55

Validate