接着上次TypeScript进阶(一),这次把剩下的知识补充上。

TypeScript进阶(二)

类型推论

类型推论又叫类型推断。

之前对类型推论的基础做了笔记,这次写的是多类型联合和上下文类型。

多类型联合

多类型联合的推论是指根据等号右边推论出左边。

// 多类型联合
let arr7 = [1, 'abcd'];     // 根据右边的[1, 'abcd'],推断出arr7是(string | number)[]类型
arr7 = ['a', 4, 'd'];   // 这里再次赋值(元素是number或string)是可以的

// arr7 = [0, true]; // error

上下文类型

上下文类型的推论是指根据等号左边推论出右边。

举例。

// 上下文类型
window.onmousedown = (mouseEvent) => {  // 根据window.onmousedown这个对象,这里推断出mouseEvent是MouseEvent类型
    console.log(mouseEvent);
}

image.png

类型兼容性

深层次递归检测

// 类型兼容性,深层次递归检测
interface Geometry {
    count: number,
    info: {
        background: string,
        faceColor: string
    }
}

let geom1: Geometry;
geom1 = {
    count: 10,
    info: {                     // 这里会往下检测info对象
        background: 'black',
        faceColor: 'lightblue'
    }
}

函数兼容性

函数参数个数

赋给函数的参数个数要少于函数定义的参数个数。

举个例子。

forEach((item, index, arr) => {
    // ...
});

// 这个函数定义了三个参数,但实际上,我们使用的时候,可以只传一个参数

forEach((item) => {
    // ...
});
函数的参数类型
// 两个函数的参数类型相同
let xx = (a: string) => 0;

let yy = (a: string) => 0;

xx = yy; // 没问题
// 两个函数的参数类型不同
let xx = (a: string) => 0;

let yy = (a: number) => 0;

xx = yy; // 报错,因为参数类型不一样
函数的可选参数和剩余参数
const getSum = (arr: number[], callback: (...args: number[]) => number): number => {

    // 把第一个参数数组传给回调函数
    // 回调函数返回接收了第一个参数的回调函数
    return  callback(...arr);
}

const sum1 = getSum([1, 2], (...args: number[]): number => {

    // 回调函数返回参数数组每个元素的累加和
    return  args.reduce((a, b) => {
        return a + b;
    }, 0);
});

const sum2 = getSum([1, 8, 0], (arg1: number, arg2: number, arg3: number): number => {
    return arg1 + arg2 + arg3;
});

console.log(sum1);  // 3
console.log(sum2);  // 9
函数参数双向协变
let func1 = (n1: number | string): void => {}
let func2 = (n1: number): void => {}

// func1 = func2; // error
func2 = func1; // right
返回值类型
let x = (): string | number =>  0
let y = (): string =>  'a'

x = y  // right
// y = x // error
函数重载
function merge(arg1: number, arg2: number): number
function merge(arg1: string, arg2: string): string
function merge(arg1: any, arg2: any) {
    return arg1 + arg2;
}

console.log(merge('1', '2').length);
// 2

function sum(arg1: number, arg2: number): number
function sum(arg1: any, arg2: any): any {
    return arg1 + arg2;
}

let func = merge;  // 这里func的类型是一个对象接口,会有两个重载的函数
// func = sum;   // error 如果再把sum赋值给func,因为sum少了一个重载函数,所以会报错

枚举兼容性

enum Status1 {
    On,
    Off
};

enum Status2 {
    Dog,
    Cat
};

let s1 = Status1.On;
s1 = 1; // right
s1 = 2; // right
// s1 = Status2.Dog; // error 不兼容

类的兼容性

类实例成员
class A1 {
    static age: number;
    constructor(public name: string) {}
}

class B {
    static age: string;
    constructor(public name: string) {}
}

class C {
    constructor(public name: number) {}
}

let a11: A1;
let b: B;
let c: C;

b = new B('Jack');

a11 = b; // 这里没问题,类A1、B的实例都是传一个string类型参数,所以把b赋给a11实际上没问题

c = new C(10);
// a11 = c; // 这里有问题,类A1的实例传string类型参数,类C的实例传number类型参数,所以把c赋给a11有类型兼容问题
类的受保护成员
private
class A2 {
    age: number;
    constructor(age: number) {
        this.age = age;
    }
}

class B2 extends A2 {
    constructor(age: number) {
        super(age)
    }
}

class C2 {
    private age: number
    constructor(age: number) {
        this.age = age
    }
}

const a22: A2 = new B2(10); // 子类可以赋值给父类类型的变量
// const a23: A2 = new C2(10); // 类C2含有私有属性,不能赋值
protected
class A3 {
    protected age: number;
    constructor(age: number) {
        this.age = age;
    }
}

class B3 extends A3 {
    constructor(age: number) {
        super(age)
    }
}

class C3 {
    protected age: number
    constructor(age: number) {
        this.age = age
    }
}

const a3: A3 = new B3(10); // 子类可以赋值给父类类型的变量

// const a32: A3 = new C3(10); // C3类并不是A3类的子类,同时age属性受保护,这里有问题

泛型兼容性

interface Data<T> {}

let data1: Data<number>
let data2: Data<string>

data1 = {}
data2 = data1; // 这种情况是可以的

高级类型(一)

交叉类型

交叉类型是指多个类型的并集,使用&符号表示。

const mergeFunc = <T, U>(arg1: T, arg2: U): T & U => {
    let res = {} as T & U;
    res = Object.assign(arg1, arg2);
    return  res;
}

console.log(mergeFunc({ a: 1 }, { b: 'b' }));
// {a: 1, b: "b"}

联合类型

const getLengthFunc = (content: string | number): number => {
    return typeof content === 'string' ? content.length : content.toString().length;
}

console.log(getLengthFunc('abcd'));
// 4

console.log(getLengthFunc(1111009));
// 7

类型保护

定义方法进行类型保护
function isString (value: string | number): value is string {
    return typeof value === 'string';
}

const getLen = (arg: string | number): number => {
    if(isString(arg)) {
        return arg.length;
    } else {
        return arg.toString().length;
    }
}

console.log(getLen('aaaa'));
// 4

console.log(getLen(123456789));
// 9
typeof类型保护

如果是简单的判断,可以直接使用typeof来做类型保护,而如果是比较复杂的判断,则使用定义函数的方式来进行类型保护。以上例子改写成使用typeof的类型保护方式如下。

const getLen = (arg: string | number): number => {
    if (typeof arg === 'string') {
        return arg.length;
    } else {
        return arg.toString().length;
    }
}

console.log(getLen('aaaa'));
// 4

console.log(getLen(123456789));
// 9

typeof只对string、number、boolean、symbol这几种类型做保护。

typeof只做 === 或 !== 判断的类型保护,像includes这样的判断不能实现类型保护。

instanceof做类型保护
class cClass1 {
    age = 18;
    constructor() {}
}

class cClass2 {
    name = 'Jack';
    constructor() {}
}

function getRandomItem () {
    return Math.random() > 0.5 ? new cClass1() : new cClass2();
}

const r1 = getRandomItem();
if (r1 instanceof cClass1) {
    console.log('This is a instance of cClass1');
} else {
    console.log('This is a instance of cClass2');
}
// 会根据随机数的大小,返回相应实例的构造函数log

null/undefined

null和undefined是任何类型的子类型。

区别对待null和undefined
// number | undefined 、 number | null 、 number | null | undefined 是不一样的
const getSum3 = (a: number, b?: number) => {
    return a + (b || 0);
}

console.log(getSum3(1));

image.png

类型断言

// 类型断言,使用"!"手动指明类型不是null或undefined
function getSplitStr (arg: number | null | undefined): string {
   
   arg = arg || 0.1;
   
    function getRes(prefix: string) {
        return `${ prefix }_${ arg!.toFixed().toString() }`
    }
    
    return  getRes('Y');
}

console.log(getSplitStr(7.3));
// Y_7

console.log(getSplitStr(null));
// Y_0

console.log(getSplitStr(undefined));
// Y_0

类型别名

定义树状结构类型
type Childs<T> = {
    current: T,
    child?: Childs<T>
}

const c9: Childs<object> = {
    current: {},
    child: {
        current: {}
    }
}
类型别名与接口兼容
// 类型别名与接口兼容
type Alias = {
    num: number
}

interface Inter {
    num: number
}

let alias: Alias = {
    num: 123
}

let inter: Inter = {
    num:  1
}

alias = inter;  // 没问题

字面量类型

字符串字面量类型
// 字符串字面量类型
type StrType = 'str_type';

// let str: StrType = 's'; // error
let str: StrType = 'str_type'  // right
// 字符串字面量的联合类型

type Direction = 'North' | 'East' | 'West' | 'South';

function getDirectionFirstLetter(direction: Direction) {
    return  direction.substr(0, 1);
}

console.log(getDirectionFirstLetter('East'));
// E
数字字面量类型
// 数字字面量类型
type Age = 18;

interface Info {
    name: string,
    age: Age
}

let p1: Info = {
    name: 'Tom',
    age: 18     //这个值必须跟Age类型别名的值一致
}

// let p1: Info = {
//    name: 'Tom',
//    age: 19   // error
}

可辨识联合

可辨识联合的两要素:1、具有普通的单例类型属性;2、一个类型别名包含了多个类型的联合。

interface Square {
    kind: 'square',
    size: number
}

interface Rectangle {
    kind: 'rectangle',
    height: number,
    width: number
}

interface Circle {
    kind: 'circle',
    radius: number
}

type Shape = Square | Rectangle | Circle;

function getArea (s: Shape) {
    switch (s.kind) {
        case 'square': return s.size * s.size; break;
        case 'rectangle': return s.width * s.width; break;
        case 'circle': return Math.floor(Math.PI) * s.radius ** 2; break;
    }
}

console.log(getArea({
    kind: 'rectangle',
    width: 100,
    height: 100
}));
// 10000

console.log(getArea({
    kind: 'square',
    size: 4
}));
// 16

console.log(getArea({
    kind: 'circle',
    radius: 3
}));
// 27
完整性检查
function assetNever (value: never): never {
    throw new Error(`Unexpected object ${ value }`);
}

function getArea (s: Shape): number {
    switch (s.kind) {
        case 'square': return s.size * s.size; break;
        case 'rectangle': return s.width * s.width; break;
        
        // case 'circle': return Math.floor(Math.PI) s.radius ** 2; break;
        
        default: return assetNever(s)
    }
}

这里写一个函数assetNever()用来检查switch代码的完整性,如果漏写了case,default中会检测到代码的问题。

高级类型(二)

this类型

使用this返回实例,实现链式调用。

class Counter {
    constructor(public count: number) {}
    public add (v: number) {
        this.count += v;
        return this;
}

    public substract (v: number) {
        this.count -= v;
        return this;
    }
}

var count1 = new Counter(3);

console.log(count1.add(10));
// Counter {count: 13}

console.log(count1.add(10).substract(1));
// Counter {count: 22}

console.log(count1.add(9).substract(9).add(1));
// Counter {count: 23}
class powCounter extends Counter {
    constructor(public count: number = 0) {
        super(count);
    }

    pow (v: number) {
        this.count = this.count ** v;
        return this;
    }

}

var p2 = new powCounter(2);

console.log(p2.pow(3));
// powCounter {count: 8}

console.log(p2.substract(2));
// powCounter {count: 6}

索引类型

索引类型查询操作符
// keyof
interface InfoInterfaceAdvanced {
    name: string,
    age: number
}

let infoProp: keyof InfoInterfaceAdvanced

infoProp = 'name'
infoProp = 'age'
// 泛型使用索引类型
function getValue<T, K extends keyof T> (obj: T, names: K[]):  T[K][] {
    return names.map((n) => obj[n]);
}

console.log(getValue({ width: 100, id: 'A001' }, ['width', 'id']));
// [100, "A001"]
function getProperty<O, K extends keyof O>(o: O, key: K):  O[K] {
    return o[key];
}
interface Objs<T> {
    [key: number]: T
}

let key1: keyof Objs<number>
interface Type {
    a: never;
    b: never;
    c: string;
    d: number;
    e: boolean;
    f: null;
    g: undefined;
    h: object
}

type Test = Type[keyof Type]

image.png

keyof不会查询出never类型的属性。

索引访问操作符[]
interface InfoInterfaceAdvanced {
    name: string,
    age: number
}

type NameTypes = InfoInterfaceAdvanced['name'];  // type NameTypes = string

image.png

interface Objs22<T> {
    [key: string]: T
}

let key22: Objs22<number>['age']

image.png

映射类型

基础
只读属性
interface InterInfo {
    width: number,
    color: string,
    height: number
}

// 映射
type ReadonlyInfoMap<T> = {
    readonly [P in keyof T]: T[P]
}

type ReadonlyInfo = ReadonlyInfoMap<InterInfo>;

这样,类型别名ReadonlyInfo就是由InterInfo映射成的只读属性类型列表。

image.png

可选属性

如果属性是可选,则

type ReadonlyInfoMap<T> = {
    readonly [P in keyof T]?: T[P]
}

image.png

部分属性
type Pick1<T, K extends keyof T> = {
    [P in K]: T[P]
}

type keyList = 'width' | 'color';
type ReadonlyInfo4 = Pick1<InterInfo, keyList>;

image.png

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick1<T, K> {
    const res: any = {}
    keys.map((key) => res[key] = obj[key]);
    return res;
}

console.log(pick({ width: 100, radius: '50%' }, ['radius']));
// {radius: "50%"}
内置映射类型

也可以使用内置类型,Readonly、Partial、Pick、Record。

// Readonly
type ReadonlyInfo2 = Readonly<InterInfo>;

image.png

// Partial
type ReadonlyInfo3 = Partial<InterInfo>;

image.png

// Pick
type ReadonlyInfo4 = Pick<InterInfo, keyList>;
// Record
// 把对象属性值映射成其他属性值
function mapObject<K extends string | number, V, U> (obj: Record<K, V>, f: (x: V) => U): Record<K, U> {
    let res: any = {}
    for(const key in obj) {
        res[key] = f(obj[key]);
    }
    return res;
}

const names = { a: 'aa', b: 'bbb', c: 'cccc' };
const len = mapObject(names, (s) => s.length );

console.log(len);
// {a: 2, b: 3, c: 4}

由映射类型进行推断

举例。

type Proxy<V> = {
    get(): V
    set(newval: V): void
}

type Proxify<O> = {

    /**
     * 把属性值传给Proxy作为Proxy的泛型
     * 每个属性都带有get、set
     */
    [K in keyof O]: Proxy<O[K]>
}

function proxify<O>(obj: O): Proxify<O> {
    const result = {} as Proxify<O>;
    for (const key in obj) {

        // 每个属性都带有set、get
        result[key] = {
            get: () => obj[key],
            set: (newval) => obj[key] = newval
        }
    }

    return  result;

}

let modal = {
    name: 'cancel',
    width:  500,
    msg: 'success',
    radius: '5%'
}

let proxyProps = proxify(modal);

console.log(proxyProps);
// {name: {…}, width: {…}, msg: {…}, radius: {…}}

console.log(proxyProps.name.get());
// cancel
// 拆包
function unproxify<O> (obj: Proxify<O>): O{
    const result = {} as  O;
    for (const  key  in  obj) {

        // 每个属性还原回原来的属性值
        result[key] = obj[key].get()
    }

    return  result;

}

console.log(unproxify(proxyProps));
// {name: "cancel", width: 500, msg: "success", radius: "5%"}

增加和移除修饰符

添加修饰符使用+,移除修饰符使用-。

// 移除修饰符 -
type RemoveReadonlyInfo<T> = {
    -readonly [P in keyof T]-?: T[P]
}

type NotReadonly = RemoveReadonlyInfo<InterInfo>

元组和数组上的映射类型

type MapToPromise<T> = {
    [K in keyof T]: Promise<T[K]>
}

type Tuple = [number, string, boolean];
type promiseTuple = MapToPromise<Tuple>;

let t1: promiseTuple = [
    new Promise((resolve, reject) => {resolve(1)}),     // 1 对应T[K]
    new Promise((resolve, reject) => {resolve('1')}),   // '1' 对应T[K]
    new Promise((resolve, reject) => {resolve(true)})   // true 对应T[K]
];  

console.log(t1);

image.png

unknown类型

1、任何类型都可以赋值给unknown类型

// 1、任何类型都可以赋值给unknown类型
let value2: unknown;
value2 = 123;
value2 = 'abcd';

2、如果没有类型断言或基于控制流的类型细化时,unknown不可以赋值给其他类型,此时它只能赋值给unknown和any类型

let value11: unknown;
let value2: unknown;
value11 = value2;

3、如果没有类型断言或基于控制流的类型细化时,不能进行任何操作

let value4: unknown;
// value4 += 1; // error

4、unknown与任何其他类型组成的交叉类型,最后都等于其他类型

type type1 = string & unknown;  // string
type type2 = unknown & unknown;     // unknown

5、unknown与任何其他类型(除了any)组成的联合类型,最后都等于unknown类型

type type5 = unknown | string; // unknown

type type6 = any | unknown; // any

type type7 = unknown | number[]  // unknown

6、never类型是unknown类型的子类型

type type8 = never extends unknown ? true : false;  // true

7、keyof unknown 等于类型never

type type9 = keyof unknown;     // never

8、只能对unknown做等或不等的操作,不能进行运算等其他操作

let value11: unknown;
let value2: unknown;

value11 !== value2
value11 === value2

// value11 += value2;   // error

9、unknown类型的值不能访问它的属性、作为函数调用和作为类创建实例

let value: unknown;

// value.aa     // error
// value()      // error
// new value()      // error

10、使用映射类型时,如果遍历的是unknown类型,则不会映射任何属性

type Type11<T> = {
    [P in keyof T]: number
}

type type11 = Type11<any>;  // { [x: string]: number }
type type12 = Type11<unknown>;  // {}

条件类型

基础

形如:

T extends U ? x : y

条件类型举例。

type Type2<T> = T extends string ? string : number;

let index: Type2<'a'> // let index: string

let index2: Type2<1> // let index2: number

let index3: Type2<false> // let index3: number
type TypeName<T> = T extends any ? T : never;

type type13 = TypeName<string | number>;    // string | number

分布式条件类型

type TypeList<T> =
    T extends string ? string :
    T extends number ? number :
    T extends boolean ? boolean :
    T extends undefined ? undefined :
    T extends () => void ? () => void  :
    object

type Type14 = TypeList<number[]>    // object
type Type15 = TypeList<() => void>     // () => void
type Type16 = TypeList<string>      // string
type Diff<T, U> = T extends U ? never : T;

type Type17 = Diff<string | number | boolean, undefined | number>;      // string | boolean
type Type18<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T]      // keyof不返回never类型的属性名,除了函数,其他都是never

interface Part {
    id: number,
    name: string,
    subparts: Part[],
    updatePart(name: string): void
}

type Type19 = Type18<Part>  // updatePart

image.png

条件类型的类型推断-infer

type Type20<T> = T extends any[] ? T[number] : T;   // T[number]可以看作是返回每个元素对应的类型

type Type21 = Type20<string[]>     // string

type Type21 = Type20<(string | number)[]>   // string | number

type Type22 = Type20<number>    // number
type Type20<T> = T extends any[] ? T[number] : T;

// 等价于使用infer进行的推断
type Type23<T> = T extends Array<infer U> ? U : T;

ts预定义内置条件类型

Exclude<T, U>
type Etype1 = Exclude<'a' | 'b' | 'c', 'c'>

image.png

Extract<T, U>
type Etype2 = Extract<'a' | 'b' | 'c', 'c'> 

image.png

NonNullable<T>
type Ntype1  = NonNullable<string | number | boolean | null | undefined>;

image.png

ReturnType<T>
type Rtype1 = ReturnType<() =>  string>     // string
type Rtype1 = ReturnType<() =>  void>     // void

image.png

image.png

InstanceType<T>

T需要是一个构造函数或者是never、any类型。

type Itype1 = InstanceType<typeof Iclass> // 需要使用typeof标识这个Iclass是个类而不是一个值

type Itype2 = InstanceType<any>

type Itype3 = InstanceType<never>

image.png

TypeScript进阶(二).png


JJYin
3 声望1 粉丝