2. TypeScript类型系统 -《TypeScript入门与实战》读后总结

导读

全面体系的进行TS认识,要想学好 TypeScript 必备的基础知识

正文

什么是类型

一种约束

ES类型系统

ES七种基本数据类型: [散乱的类型,不成体系]

  • Boolean
  • String
  • Number
  • undefined
  • null
  • Symbol
  • BigInt
  • Object [类,数组,函数]

TS类型特性: [成体系的,自顶向下划分的]

  • 子类型可以赋值给父类型, 父类型不能赋值给子类型, 相同类型可以赋值
  • 详细说明如下文

TS类型系统

TS基础(非复合)类型介绍

体系划分 [自顶向下]

顶端类型
any
  • any 代表的是任意类型
  • 使用场景:为了跳过所有编译类型检查
  • const a: any = 0; a.length;运行error,编译的时候不会报错
  • any 是所有类型的父类型, 所有类型的变量都可以赋值给 any 类型的变量
  • 但特殊的是 any 也可以赋值给所有类型
unknown
  • 代表未知类型, 是一种更安全的顶端类型
  • 所有类型都可以赋值给unknown类型
  • unknown 只能赋值给 unknownany, 不能给其他类型赋值
  • 使用场景:代表一种未知类型
function f(m: unknown) {
  if (typeof m === 'string') return m.length; //如果使用就必须细化
}
原始类型
boolean

const yes: boolean = true;

string

const s: string = '';

number

const n: number = 1;

undefined

const f: undefined = undefined;

null

const n: null = null;

bigint

const int: bigint = 10n;

symbol
  • 只有字面量类型(不可变的)
  • 在ES里,Symbol() 代表一个唯一值, 用作对象属性
  • TS里symbolSymbol() 实例化后的类型
let xx = Symbol('name'); //xx可变,因为只是个表达式
let say = Symbol('say');
// ES里这样写没问题
let obj = {
    [xx]: 'lnj',
    [say]: function() {
        console.log('say')
    }
}
// 加上类型描述后,obj的类型无法描述,因为xx是表达式
let obj: {
    [xx]: string, // error
    [say]: Function
} = {
    [xx]: 'lnj',
    [say]: function() {
        console.log('say')
    }
}
  • unique symbol的出现,用作接口,类的可计算属性名
  • 为了增加symbol类型下的字面量值 定义了一个不可改变的字面量类型 unique symbol
const a: unique symbol = Symbol() // 必须用const申明,代表不可改变
const a = Symbol() // a 也是unique symbol类型 
// unique symbol 为 symbol的子类,代表不可变的字面量
const b: unique symbol = Symbol();
interface xx {
    [b]: string
}
void [无类型]
  • 表示函数没有返回值
function a(): void {}
function a(): void { return undefined; }
enum [枚举类型]
  • 多个成员, 每个成员是个常量
  • 使用场景:不依赖枚举成员值, 降低代码耦合度
enum D {
    left, //0
    right //1
}

function a(d: D): void {
    switch (d) {
        case D.left:
            break;
        case D.right:
            break;
    }
}
  • 异构枚举 [不推荐]
enum D {
    up, // 0
    done, // 1
    left = "left",
        right = 4,
        iner // 5
    ccc = Math.pow(2, 3) // 计算
}
  • 枚举编译后的正反映射
enum D {
    left,
    right
}
const d: D = D.left;
console.log('d>>>', d); //0
console.log('D.left>>>', D.left); // 0
console.log('D[D.left]>>>', D[D.left]); // left
console.log('D[0]>>>', D[0]); // left
//编译后
(function(D) {
    D[D["left"] = 0] = "left";
    D[D["right"] = 1] = "right";
})(D || (D = {}));
  • const枚举
// JS里面没有枚举类型,枚举类型为常量时,编译后完全删除
const enum Dd {
    left,
    right
}
const dd = [ // Array<Dd>
    Dd.left,
    Dd.right
]
//编译后,只是添加了注释
const dd = [
    0 /* Dd.left */ ,
    1 /* Dd.right */
];
  • 联合枚举类型 [枚举成员为常量]
enum D {
    left,
    right
}
// <=> 等价于ß
type Dd = D.left | D.right;
非原始类型
数组类型
// 定义方式
const a: number[] = [1, 2]; //简便
const b: Array < string > = ['c', 'd']; //泛型
// 只读数组定义方式
const a: readonly number[] = [1, 2];
const b: ReadonlyArray < string > = ['c', 'd'];
const c: Readonly < string[] > = ['c', 'd']; // Readonly<T>
// 不能将只读数组赋值给常规数组 ,不能通过赋值放宽约束
const a: readonly number[] = [1, 2];
const b: Array < number > = a; // error
元祖类型 [数组是元组的父类]
// 对数组类型的进一步限定, 对数组长度的限定, 对数组元素类型的限定
// 使用方法
const a: [number, string] = [1, ''];
const b: [number, string ? ] = [1]; //可选
const c: [number, ...boolean[]] = [3, true]; //剩余
// 因为长度有限定,所以获取不存在报错
const a: [number, string] = [1, ''];
a[3] //error
Object [首字母大写]
  • 定义
  1. Object 类型是特殊对象 Object.prototype 的类型, 描述JavaScript中所有对象都共享( 原型继承) 的属性和方法
  • 兼容性
  1. 是一个顶层的对象类型, 除了 undefined 和 null 都可以赋值给 Object 类型( 因为原始类型, 原始值有‘ 自动封箱操作’)
  2. 因为Object本身就是顶层类型了, 只能赋值给 any / unknown / Object / object / {}
  • 自动封箱
  1. 在原始值上调用某个方法, 会将其转化成对象类型再调用 比如:
3..toString() //两个点
  1. JavaScript 中一切皆为对象, 为每个原始值提供了构造函数创建能够表示原始值的对象 比如: String() ,Number()
let b: Object = '1'; // new String('1');
  • 应用场景
  1. 编译后可以运行, 但这是一个使用错误, 不应该使用Object描述自定义对象, 它描述最顶层的对象类型, 只有公共属性
let b: Object = { a: 111 };
console.log(b.a); //error Object 上 不存在 a
  • Object 只描述 Object.prototype 的类型
object [首字母小写]
  • 意义
  1. 对于原始值类型 boolean number string undefined null..都有类型描述
  2. Object 也无法描述非原始值类型, 因为原始值可以给 Object 类赋值, 所以需要一种类型描述非原始值类型的顶层
  • 定义
  1. 表示除原始值意外的非原始值类型
  2. 比如:对象类型
  • object 与 Object的对比
  1. 不能理解为 Object 是 object 的父类, 它们只是描述的范围不同
let b: Object = '1';
let d: object = '1'; // error
let c: object = new String('1'); //只是不能对原始值自动封箱
  • 兼容性
  1. 是一个顶层的对象类型, 除了原始类型外的对象类型都可以赋值给 object 类型,比如
let c: object = new Date();
let d: object = {}
  1. object 类型可以赋值给 any / unknown / Object / object / {}
  • 场景
  1. 与 Object 类似, object 描述 对象类型的顶层, 也只会有公共属性
let b: object = { a: 111 };
console.log(b.a); //error object 上不存在 a
  1. 能够跟精确的描述 Object.create() 方法签名的类型 ObjectConstructor
interface ObjectConstructor {
    create(o: any..) // 没有 object之前只能用any
    create(o: object | null..) // 更好描述
}
Object.create(1) // error
Object.create 的参数

function f(o: object | null): object {
    return Object.create(o);
}
对象字面量类型
  • 定义
  1. 将一个对象字面量值当作类型
  • 对象类型字面量的类型成员
const b = "B";
const c: unique symbol = Symbol();
let o: {
    // 属性签名,必须是 string,number,unique symbol的字面量类型
    a: number, // Type 可以为any类型
    [b]: string,
    [c]: boolean,
    d ? : bigint, // 可选属性
    readonly e: null // 只读属性
    // 索引签名,n表示一个占位变量
    [n: number]: undefined
    // 调用签名
    (): void // o() 对象字面量类型可以表示函数类型
    // 构造签名
    new(): object // new o() 对象字面量类型可以表示类类型
    // 方法签名
    sub(x: number): void // 函数类型的使用成员
};
// 调用签名用法
let add: {(): void } = function() {
    console.log(1);
}
// 构造签名用法
let Abb: { new (name: string): object } = class {
    private name: string;
    constructor(name: string) {
        this.name = name;
    }
}
  • Object ,{},object 区别,
  1. {}和Object可以互换
    let o1: Object = 1;
    let o2: {} = 1;
    o1 = o2;
    o2 = o1;
  1. Object: 顶级对象类型, 强调对象公共属性和方法,一般不会用來直接申明
  2. {}: 强调不包含属性的对象类型, 相当于Object,可以作为Object的代理对象 < T > 如泛型参数如果, 没有约束条件, 默认的约束就是 {}
  3. object 强调非原始类型的公共属性和方法
  • 忽略多余属性检查的5种方式
const p0: { x: number } = { x: 1, y: '' } as { x: number } // 1 使用 as 强制转换
"suppressExcessPropertyErrors": true // 2 配置tsconfig
// @ts-ignore
const p1: { x: number } = { x: 1, y: '' } // 3 使用注释指令
const p2: { x: number, [p: string]: number } = { x: 2, y: 1 } // 4 目标对象添加索引名
const temp = { x: 0, y: 1 } // 5 使用一个临时变量,另源对象不再是全新的字面量类型,编译器不再执行多余的属性检查
const p3: { x: number } = temp;
函数类型
  • TS函数是如何定义的 [包括 参数,返回值,重载]
// [重载签名]
function f(m: number, n ? : string): void; //常规参数 可选参数 f(1,'') || f(1)
function f(...args: [number, string ? ]): number; //元组展开剩余参数,可选 f(1) || f(1,'')
function f(b: boolean, ...args: number[]): string; //数组展开剩余参数 f(true) || f(true,1,2,3,...)
function f({ x, y }: { x: number, y: number }): boolean; //解构参数 f({true,2})
function f(...args: any[]): never { //这行及以下为函数 [实现签名]
    /**
    * 重载签名必须与实现签名相符
    * 参数类型:兼容重载类型(并集)
    * 返回值类型:可以赋值给任意重载签名得返回值(交集)
    */
    throw new Error();
}
  • 函数的类型字面量定义
// 普通函数
let d: () => void = function(): void {};
// 构造函数
let g: new() => void = class { constructor() {} };
// 无法描述重载函数类型
  • 函数类型的对象字面量定义 [函数也是对象]
// 普通函数
let dd: {
    (): void } = function(): void {};
// 构造函数
let gg: new() => void = class { constructor() {} };
// 重载函数
let ff: {
    (n: number): string,
    (n: string): number
} = function(p: string | number): number & string {
    throw new Error();
}
  • 函数类型字面量 与 对象类型字面量 定义函数类型对比
    // 函数定义
    function fn(x: number): void {};
    fn.version = 1;
    // 函数字面量类型描述
    let foo: (x: number) => void = f; // 函数字面量类型更加简洁
    foo.version // error
    // 对象字面量描述函数对象
    let foo2: { // 对象字面量表达能力更强
        (x: number): void,
        version: number
    } = fn;
    foo2.version // 1
  • 包装函数的定义方式 // 既可以作为普通函数,又可以作为构造函数
    /**
    * 顶级声明必须以declare开头
    * declare声明的变量和模块后,其他地方不需要引入 
    */
    declare const F: {
        (n: number): Number,
        new(n: number): number
    }
    const a: Number = F(1);
    const b: number = new F(1);
    // 比如TS内部实现的 declare var Number: NumberConstructor;
    // NumberConstructor描述一个包装函数 Number 的类型, 但Number具体的实现不是TS定义的, 是JS本身固有的
    // 所以不必在意 Number 的具体实现方式
  • 重载函数的特性
    // 重载函数的执行顺序是从上往下,要保证跟精确的调用在靠前位置
    function f(x: any): number;

    function f(x: string): 0 | 1;

    function f(x: any): any {};
    const a: 0 | 1 = f(''); // error 不能将类型“number”分配给“0|1”
    // 接口及对象字面量可以定义重载函数, 函数类型字面量无法定义重载函数
    // 在返回值不变的情况下尽量少用重载函数, 用灵活的参数设置代替会更加简洁
    // 函数实现的函数签名不是重载函数的调用签名
  • 函数中的this类型
    function f() {
        this.a = 1; // error "this" 隐式具有类型 any
    }

    function f1(this: { a: number }) { // 作为可选参数,放在参数类型的第一个规定位置
        this.a = 1;
    }

    function f2(this: void) { // 定义纯函数的话可将this设置为void
        this.a = 1; // 类型“void” 上不存在属性“a”
    }
接口
  • 特点
  1. 相当于一种有名称的对象字面量类型, 并且可以定义类型参数 < T >
  2. 同名接口申明具有可合并的特点, 申明只存在编译阶段
  3. 接口是对对象字面量描述的另一层扩展
  4. js里大多数可以用对象字面量描述,一切皆为对象,比如一个类的实例或者一个函数的返回值
  • 定义方法
interface AA {
    name: string, // 属性签名
    x ? : sting, // 可选属性
    readonly y: string, // 只读属性
    (message ? : string): Error, // 调用签名
    new(message ? : string): Error, // 构造签名
    // 三种可互换
    getId(id: string): string | null, //方法签名
    getId: (id: string) => string | null, //属性签名+函数字面量
    getId: {(id: string): string | null }, //属性签名+对象字面量
    // 重载方法
    f(): number,
    f(x: boolean): string,
    f(x: string, y: string): string,
    ['f'](): string, //计算属性签名
    [prop: number]: number, //索引签名。如果同时存在字符串索引和数值索引,数值索引类型必须能赋值给字符串索引类型
}
  • 接口继承
    // 子类型继承父类同名属性的成员类型需要兼容->子类型能够赋值给父类
    interface F() {
        f(): { a: string }
    }
    interface G() {
        f(): { b: number }
    }
    interface S extends F, G {
        f(): { a: string, b: 1 } // 做兼容,不然报错
    }
类类型[类的实例类型] [描述的是类生成的实例的类型]
  • 详见 定义类的扩充 - TS的类
类构造函数类型[描述的是类生成的本质上是一个构造函数类型, 可以用一个有构造签名的对象字面量表示实例的类型]
  • 详见 定义类的扩充 - TS的类
尾端类型
never [代表一种不可能的类型]
  • 是最末端的类型, 是所有类型的子类型
  • 能赋值给所有类型, 只有never类型能赋值给never类型, any也不可以
    let x: never;
    let y: boolean = x; //严格模式编译报错 不允许x在申明之前使用,因为没有值是never
    const a: number = '1'
    as never;
    const b: object = '2'
    as never;
    const c: never = '2'
    as never;
    console.log(a); // 1
    console.log(b); // 2
  • 使用场景
    // 程序异常
    function f(): never {
        while (true) {
            // statement
        }
    }

    function f(): never {
        throw new Error()
    }
    // 类型推断:TS有静态类型检查,类型推断并不是推断出来某一个具体的值,而是推断出一个变量的类型
    // 1
    function f(p: string) {
        if (typeof p === 'string') {
            p // string
        } else {
            p // never
        }
    }
    // 2
    enum D {
        left,
        right
    }

    function fn(d: D) {
        switch (d) {
            case D.left:
                d // D.left
                break;
            case D.right:
                d // D.right
                break;
            default:
                d // never
                break;
        }
    }
定义类的扩充
类型别名 [可指代任意类]
  • 类别名可以指代任意类型, 包括原始类型, 对象类型, 接口, 联合类型, 交叉类型
    interface C { d: number };
    type t0 = C;
    type T = number | string;
    type T0 = { a: number, b: number } & { c: number, b: string } // 既是又是
  • 递归的类型别名。 定义一些常用的类
    /**
     * 递归类型别名
     * */
    // 对象字面量
    type T0 = null | {
        right: T0,
        left: T0,
        value: number
    }
    let tree: T0 = {
        right: {
            right: null,
            left: null,
            value: 0
        },
        left: null,
        value: 1
    }
    // 函数字面量,构造函数类型
    type T1 = () => T0;
    type T2 = new() => T1;
    // 接口类型
    interface A < T > {
        [p: string]]: T
    }
    type T3 = A < T3 > | undefined | 'Bob'
    let a: T3 = {
        job: undefined,
        name: {
            fristName: 'Bob',
            lastNAme: undefined
        }
    };
    // 数组/元组类型
    type T4 = null | [number, T4];
    let c: T4 = [1, [2, null]]
    // 泛型类
    class B < T > {
        name: T | undefined
    }
    type T5 = { fristName: string } | B < T5 > ;
    let x: T5 = new B < { fristName: 'Jone' } > ();
  • 类型别名与接口对比
  1. 指代范围不同: 类别名可以指代任何类型, 接口只能表示对象类型
  2. 继承实现不同: 接口有继承功能, 类别名的继承功能只能通过交叉类型来实现类似的效果
    type S = { a: string };
    type O = S & { r: number };
    let a: O = { a: '2', r: 123 };
  1. 接口有申明合并的特性, 而类型别名不能重复申明
    interface A { x: number }
    interface A { y: number } 
    // <=>
    interface A { x: number, y: number }
类 [TS对类进行了扩展]
  • TS中的类成员是如何定义的
    class A {
        name: string = 'A';
    } // 类的申明
    let B = class C {
        name: string = 'C';
    } // 类的表达式
    // [成员变量/函数/存取器/索引成员]
    class C {
        name: string = 'C'; // 严格模式必须初始化
        age: number; // 或在构造函数中初始化
        occupation ? : stirng; // 间接初始化要加?非空断言
        /**
        * 只读属性不能简介初始化,只能在申明初始化或者构造函数中初始化
        * 如果不确定是否只读,可以添加readonly修饰符,需要修改再去掉
        */
        readonly sexy: string;
        // [成员函数]
        init() {
            this.occupation = 'Engineer';
            // this.sexy = ''; error
        }
        constructor() {
            this.age = 18;
            this.sexy = '男';
            this.init();
        }
        /**
        * [存取器]
        * get/set 必须有相同的可访问属性
        * 提供一层额外的访问控制
        */
        private _money: number = 100;
        get money(): number = {
            return this._money;
        }
        set money(value: number) {
            if (value > 0) {
                this._money = value;
            }
        }
        /**
        * [索引成员]
        * 不允许定义可访问修饰符
        */
        [prop: number]: boolean;
    }
    let o = new C();
    o.money = 1;
    o[1111] = false;
  • 类成员可访问性
    class A {
        public a: string = 'a'; // 默认,类内部,类外部,派生类内部,派生外部
        protected b: stirng = 'b'; // 类内部,派生类内部
        private c: string = 'c'; // 类内部
        #r: string = 'r'; // 私有字段-私有属性新的表达式
        init() {
            this.a;
            this.b;
            this.c;
            this.#r;
        }
    }
    class Aa extends A {
        init() {
            this.a;
            this.b;
            this.c; //error
            this.#r; //error
        }
    }
    let o = new A();
    this.a;
    this.b; //error
    this.c; //error
    this.#r; //error
    let oo = new Aa();
    this.a;
    this.b; //error
    this.c; //error
    this.#r; //error
  • 构造函数
    class A {
        radius: number;
        /**
        * public 默认公有
        * private 私有属性访问:不允许再类外部实例化
        * protected 受保护,只允许在内部,和派生内部
        */
        public constructor(r: number, x: string, y: number); // 重载构造函数
        public constructor(r: number, x: boolean, y: number); // 重载构造函数
        // 构造函数没有返回值,因为返回对象实例
        public constructor(r: number, x: string | boolean, public y: number) { // 如果参数带属性修饰符,则会申明为类的成员变量
            this.radius = r;
        }
        init() {
            return new A(123, 'x', 1); // public protected private 
        }
    }
    class B extends A {
        init() {
            return new A(123, 'x', 1); // public protected 
        }
    }
    let a = new A(123, 'x', 1); // public
    console.log(a.y) // 1
  • 继承
    class A {
        public x: number = 1;
        protected y: number;
        private z: number;
        color: 'red' | 'yellow' | 'blue';
        constructor() {
            this.y = 2;
            this.z = 3;
            this.color = 'red';
        }
        init() {
            console.log('A zhongde init');
        }
    }
    class B extends A {
        public x: number = 2; //重写同名属性
        // private y:number = 3 error 只允许放宽基类的可访问性,不允许缩小
        public y: number = 3;
        // color:'red' | 'yellow' | 'blue' | 'xxxx' error 重写基类成员要保证兼容性,不能比父类范围小
        init() { //重写父类同名方法
            console.log('B 中的 init');
            console.log(this.x); // 2 重写同名属性
            console.log(super.x); // udnefined 同名属性不能用super访问
        }
        constructor() {
            // this 在派生构造函数中,this必须在super()之后使用
            super(); // 派生类的构造函数必须用super实现父类构造函数
            this.init();
            this.color = 'red';
        }
    }
    let b = new B();
  • 实例化派生类的初始化顺序
  1. 初始化基类属性
  2. 调用基类构造函数
  3. 初始化派生类属性
  4. 调用派生类构造函数
  • 接口继承类 [仅支持单继承, 不允许多继承, 只能指定一个基类]
    class A {
        a: string = '';
        b(): boolean {
            return true;
        }
    }
    interface B extends A {};
    // <=>
    /**
    * {
    *  a:string,
    *  b():boolean
    * }
    * 
    */
    const b: B = {
        a: '123',
        b(): boolean {
            return false
        }
    }
    // 如果继承非共有成员,那么接口的实现必须由基类或者基类的子类
    class C {
        private x: boolean = true;
        protected y: string = '1';
    }
    interface cc extends C {}
    class D extends C implements cc {
        protected y: string = '3';
    }
  • 类实现接口
    // 一个类可以实现多个接口
    // 接口定义的成员没有修饰符,所以类继承的时候都是public
    interface Cc {
        color: string
    }
    interface P {
        area(): number
    }
    class CC implements Cc, P {
        color: string = 'q';
        area(): number {
            return 1
        }
    }
  • 静态成员
    class F {
        public static a: string = 'a';
        protected static b: string = 'b';
        private static c: string = 'c';
        a() {
            F.a;
            F.b;
            F.c;
        }
    }
    F.a;
    // F.b; error
    // F.c; error
    class E extends F {
        a() {
            F.a;
            F.b;
            // F.c;error
        }
    }
    E.a;
    // E.b;error
    // E.c;error
  • 抽象类与抽象成员
    abstract class Base {
        abstract a: string; // 抽象成员不包含具体实现代码
        abstract b: boolean;
    }
    abstract class Fase extends Base { //抽象类可以继承抽象类
        abstract a: string;
        // private abstract c:boolean; error 抽象成员不能申明为 private
        b: boolean = false // 抽象类中的成员可以是抽象成员也可以不是
    }
    class Ease extends Fase {
        a: string = 'a'; //继承抽象类必须实现所有抽象成员
        c: boolean = false;
    }
    // let aa = new Fase() error 抽象类不能被实例化
  • this类
    class Co {
        private count: number = 0;
        add(): this {
            this.count++;
            return this
        }
        sub(): this {
            this.count--;
            return this
        }
    }
    let ct = new Co();
    ct.add().sub() //返回this类型可以链式调用
    // this类型为动态的,即使继承了父类的方法返回的this,也是本身的实例
    class Bo extends Co {}
    let bt = new Bo();
    bt.add() // 返回的是 bt实例,类型为Bo
  • 类类型
        class Lo {
            static a: string = 'a';
            c: boolean = false;
            area(): number {
                return 1;
            }
        }
        interface k {
            c: boolean,
                area(): number,
        }
        let xx: Lo = new Lo(); // 类类型就是类的实例的类型
        let yy: k = new Lo(); // 本质上就相当于实现了一个对象类型接口
    }
  • 类的构造函数类型
// 类本质上是一个构造函数
interface Rr {
    new(): Lo,
    a: string
}
const r: Rr = Lo; // Rr 为类的构造函数类型

特性划分

单元类型
  • 仅仅包含一个可能值的类型
  • undefined 类型
  • null 类型
  • unique symbol 类型
  • void 类型
  • 字面量类型
  • 联合枚举成员类型
字面量类型
  • 每一个字面量类型都有一个可能的值就是自己本身

TS复合类型介绍

  • 具有更丰富表达的更高级的类型,包括一些类的运算(类参数,作用域,运算符,等)

泛型 [关键词<T>]

泛型参数基本定义
    /**
        * 泛型,可选类型参数,形式类型参数列表,默认类型参数
        * 如果有两个形式类型参数那么函数的参数至少有两个
        * T的泛型约束是 string,T的参数类型一定是基约束[string]的子类型
        * 如果没有申明约束默认是any类型
        */
    function identify < T extends string, U = T, F = boolean > (a: T, b: U, c: F): T | U {
        return c ? a : b;
    }
    let a = identify <'1'> ('1', '1', false);
泛型函数
    // 调用签名
    <T>(x:T):T 
    // 构造签名
    new <T>():T[]
    // 泛型函数类型推断
    function f<T>(x:T):T{return x;}
    /**
        * 非必要不放宽
        * 编译器会尽可能细化类型
        */
    f<string>('a'); // => 推断返回类型为 'a'
    // 注意事项
    // 泛型函数的泛型参数本质是描述参数与参数之间,参数与返回值之间的类型关联
    // 如果泛型参数除本身申明以外只在函数签名中出现了一次,那就说明这个泛型类型参数可有可无
    // 非必要不使用泛型类型参数
泛型接口
    interface Array<T>{
        pop():T|undefined,
        [n:number]:T
        // ....
    }
泛型别名
    type T0<T> = {
        right:T0<T> | null,
        left:T0<T> | null,
        value:T
    }
    let tree:T0<number> = {
        right:{
            right:null,
            left:null,
            value:0
        },
        left:null,
        value:1
    }
泛型类
    interface A<T>{
        a:T
    }
    class Base<T>{
        b?:T
    }
    class C<T> extends Base<T> implements A<T>{
        constructor(public readonly a:T){
            super();
        }
    }

局部类型

    function f<T>(x:boolean){
        if(x){
            interface T{a:number} //局部作用域,两个T不影响
            const x:T = {a:1};
        }else{
            interface T{a:string};
            const x:T = {a:""};
        }
        interface Tt<T> {t:T};// 可用外部泛型T
    }

联合类型 [关键词 '|']

    /**
    * 联合类型:既是也是
    * 类型成员为交集
    * 同名类型成员返回值为并集
    * 可选属性与非可选属性为可选属性
    * 小范围|大范围=>大范围
    * 相同列表参数的调用签名/构造签名返回值并集
    * 同名函数类型成员=>参数成员列表并集,同位参数类型交集,返回值并集
    */
    interface c{
        a:string,
        b:number,
        c:boolean,
        [x:number]:boolean,
        (x:number):number[]
        aaa(y:boolean,z:bigint):number
    }
    interface t{
        a:bigint,
        d:null,
        e:number[],
        [x:number]:undefined,
        (x:number):string[];
        aaa(x:number):string
    }
    type T = c|t;
    let x:T;
    // x.a //公共属性 => string | bigint
    // x.b error
    // x.c error
    // x.d error
    // x.e error
    // x[1] => undefined | boolean
    // x() => string[] | number[]
    // x.g => number | string 可选
    // x.aaa => aaa(arg0: never, z: bigint): string | number

交叉类型 [关键词 &]

    /**
    * 交叉类型:既是又是
    * 类型成员为并集
    * 同名类型成员返回值为交集
    * 相同列表参数的调用签名/构造签名=>重载 要注意重載順序
    * 小范围&大范围=>小范围
    */
    interface c{
        a:string,
        b:number,
        c:boolean,
        [x:number]:boolean,
        (x:number):number,
        g?:string,
        aaa(y:boolean,z:bigint):number
    }
    interface t{
        a:bigint,
        d:null,
        [x:number]:undefined,
        (x:string):boolean;
        g:number,
        aaa(x:number):string
        aaa(x:bigint):boolean
    }

    type T = c&t;
    let x:T;        
    // x.a //公共属性为交集 => never
    // x.b //类型成员为并集 => number 
    // x.c //boolean
    // x.d //null
    // x[1] //=> never
    // x.aaa //=> 重载
    // x.g //=> number&string=>never 必选

联合类型与交叉类型运算 [关键词 分配律]

    a&b|c&d <=> (a&b)|(c&d) //相当于乘法和和加法,可以使用分配律
    never | 0 => 0 // never是任何类型的子类型,并集可消去

索引类型 [关键字 keyof]

    interface Point{
    x:number,
    y:number,
    [p:number]:boolean
    }
    interface B{[p:string]:1};
    interface C{[p:number]:1};
    // 索引类型查询
    /**
    * 对象,接口都有索引
    * 所以引入索引类型查询
    * 索引类型只可能是 string number symbol 的联合类型
    * 联合类型的索引类型查询为交集=>参考联合类型成员计算
    * 交叉类型的索引类型查询为并集=>参考交叉类型成员计算
    * 如果索引为字符串索引,那么索引查询类型为 string | number
    * 如果索引为数值索引,那么索引查寻类型为 number
    */
    type T = keyof Point; // 'x'|'y'|number
    type T1 = keyof ({a:number,b:number}&{c:string,b:string});// 'a'|'b'|'c'
    type T2 = keyof {a:1,b:2} // 'a'|'b'
    type T3 = keyof any; // string | number | symbol
    type T4 = keyof unknown; // never 这个比较特殊
    type T8 = keyof B; // string | number
    type T9 = keyof C; // number
    // 索引访问类型
    /**
    * 通过索引类型获取索引成员
    * 同名属性拥有更高的优先级
    */
    type T5 = Point[T]; // number | boolean
    type T6 = Point['x']; // number
    interface P {
    a:true;
    [p:string]:boolean;
    }
    type Ta = P['a']; // true
    type Tb = P['b']; // boolean
    // 索引类型的应用
    /**
    * 实现安全的对象属性访问
    * 在对象属性不存在时会产生编译错误
    * 避免 a.x === undefined 的情况出现
    */
    function getProperty<T,K extends keyof T>(obj:T,key:K):T[K]{
    return obj[key];
    }
    function xx(p:Point){
    // 如果没有显示传入参数类型,泛型函数会自己推断类型,会比显示传入类型更加精确
    return getProperty(p,'cc'); // error 类型 ‘cc’ 的参数不能赋值给‘x’|‘y’的参数
    }

映射对象类型 [关键词 in]

  • 映射对象类型是什么
  1. 用于操作类型的运算符 {[P in K]:T}
  2. 本质是遍历一个联合类型,将联合类型的各个类型成员作为新的对象类型的各个属性
  3. 由于 string|number|symbol 才能作为键名,所以K必须能赋值给这些类型
  • 映射对象为什么会出现
  1. 定义对象类型的扩展
  • 映射对象的应用
    // 映射对象类型in+索引对象查询keyof+索引访问类型[]=> 映射对象类型
    type T =  { a: number, b: string };
    // 非同态对象类型映射,可加各种修饰符,值的限定
    type M = { readonly [P in keyof T]?: T[P] };// readonly ? 修饰符都可选
    // 同态对象类型映射, 映射对象保留属性修饰符
    type Pick<T, K extends keyof T> = { [P in K]: T[P] };
    // 改进的修饰符拷贝=>添加或者删除修饰符(原来只能添加,不可删除)
    type C =  { +readonly [P in keyof T]-?: T[P] } // 添加只读属性,减去必选属性
    // 同态对象映射默认保留修饰符(是类型原有的修饰符,不是属性修饰符)
    type V = readonly number [];
    type Ho<T> =  {[P in keyof T]:T[P]};
    let a:Ho<V> = [1,2,3];
    a[0] = 2; // error 默认也是只读

条件类型 [关键词 ?]

    // 条件类型 :根据条件判断返回类型
    type TypeName<T> = T extends string
        ? 'stirng'
        : T extends number
        ? 'number'
        : T extends boolean
        ? 'boolean'
        : never;
    // 裸类型参数:没有任何装饰的类型参数
    type T0<T> = T extends string ? true : false; // 裸类型参数
    type T1<T> = [T] extends [string] ? true : false; // 非裸类型参数
    // 分布式行为:如果实参为联合类型,会将条件类型展开为子类型构成的·联合类型
    type T3 = A | B;
    type R1 = T3 extends K ? X : Y;
    // <=> 
    type R1 = (A extends K ? X : Y) | (B extends K ? X : Y);
    // 分布式行为的应用:过滤联合类型
    type Exclude<T,U> = T extends U ?never:T;
    type C = Exclude<string|undefined,null|undefined>
            = (string extends null|undefined ? never : string) 
            | (undefined extends null|undefined ? never : undefined)
            = string | never 
            = string;
    // 避免分布式行为:利用裸类型参数
    type CT<T> = T extends string ? true :false; // 具有分布式行为
    type T = CT<string|number>; // => true | false = boolean
    type CP<T> = [T] extends [string] ? true :false; // 避免分布式行为,将裸类型参数修改为非裸类型参数
    type T = CT<string|number>; // => false 

类型推断 [关键字 infer]

  1. 是什么
  • infer 是用在条件类型上的类型修饰符(并且该类型只能在true分支出现)
  1. 为什么会出现
  • 需要推断类型,推断的是输入的类型T中包含的类型
  1. 用法
    type R<T> = T extends Array<infer U>?U:never;
  1. 用法扩展
    type M<T> = T extends { a: infer U, b: infer U } ? U : never;
    type rr = M<{ a: string, b: number }>;//string | number

内置工具类

  1. 根据输入的类型返回一个新的类型
  2. lib/lib.es5.d.ts

类型查询 [关关键字 typeof]

  1. TS在JS的 typeof 操作符上进行了扩展,
  2. JS上的 typeof 能够获取值的类型,而TS扩展后的 typeof 能够在类型表达式使用
  3. 注意 typeof 在类型表达式中的用法和值表达式中的用法的区别
    let a:{x:string} = {x:'1111'};
    type T = typeof a;
    if(typeof a === 'object'){
        console.log(11111);
    } 

类型断言 [关键字 as <T>]

作用
  • 开发者比编译器更清楚类型
  • 使用场景
    class P{
        x:number =1;
        y:string = '';
    }
    class C extends P {
        x:number = 2;
        y:string = '';
        z:boolean = false;
    }
    function getP(t:string):P{
        if(t === 'p')return new P();
        return new C();
    }
    let x = getP('c');
    x.z;// Property 'z' does not exist on type 'P'
    // 解决方法,类型断言
    (<C>x).z;
    (x as C).z
  • 类型断言的约束
  1. exp as T
  2. 必选满足 exp类型 能赋值给T类型,或者 T类型 能赋值给exp类型
    // 虽然有约束,也可以有变通方法 ,利用顶端类型
    const a  = 1 as string; // error
    const a = 1 as unknown as string;
    console.log(typeof a,a); // number 1 
    const x:typeof a = '1'; // 在类型表达式中,typeof a 就为 string了
  • 其他断言符号
    // const断言,转为不可变类型
    let a =  0 as const;
    let x = [1,2] as const; // 如果为对象或者数字,则转变为只读
    //非空断言,
    let a:(string | undefined) 
    let x = a.length; // error 编译器无法识别是string,undefined
    let y = a!.length;

类型细化

是什么
  1. TS能识别的代码解构进行-类型细化
  2. 类型细化的本质:
  3. 我们可以在类型表达式中推断出类型
  4. 但如何根据值的表达式中推断出类型的范围
  5. 所以需要类型的细化
  6. 这些工作也是编译器可以识别的
方式
  1. 类型守卫
    // typrof 获得操作数的数据类型
    function f(p: unknown) {
        if (typeof p === 'string') {
            // string
        }
    }
    // instanceof 实例对象与构造函数之间的关系
    class A { x: number = 1; };
    class B { y: number = 2 };
    function f0(p: A | B) {
        if (p instanceof A) {
            // A
        }
    }
    // in 判断原型链中是否存在某属性
    function f1(x: A | B) {
        if ('x' in x) {
            // A
        }
    }
    // 逻辑与或非,与等式类型守卫
    function f2(p: null | undefined | number) {
        if (p) {
            // number
        } else if (p === 0) {
            // 0
        } else {
            // null/undefined
            p = 12 // 如果是用了逻辑类型守卫,又对变量赋值,则该类似守卫无效
            console.log(1223)
        }
    }
    // 自定义类型守卫函数,本质就是返回一个boolean的函数
    function isTypeB(p: A | B): p is B { // 就是返回一个boolean
        return (p as B).y !== undefined;
    }
    function f3(x: A | B) {
        if (isTypeB(x)) {
            // B
        }
    }
    // this 类型守卫,只能用于函数或者方法,不能用于属性或者存取器
    class Student {
        isStudent(): this is Student {
            return true
        }
    }
  1. 可识别联合类型
    interface C { type: 'C' };
    interface D { type: 'D' };
    function f4(x: C | D) {
        if (x.type === 'C') {
            // C
        }
    }
  1. 赋值语句
    let x = 1; // => x number
    let y: typeof x = '' // error number
  1. 控制流语句
    function f5(c: boolean) { //分析可能的执行路径 
        let x: number;
        x// error 赋值前使用
        if (c) {
            x = 1;
            x;// number
        }
    }
  1. 断言函数 =>本质与 自定义类型守卫函数 一样,只是更加语义化
    function f6(x:unknown):asserts x{
        if(!x){
            throw new Error("x 为假")
        }
    }
    function f7(x:unknown):asserts x is number{
        if(typeof x !== 'number'){
            throw new TypeError('不为 number')
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容