TS学习

183 阅读4分钟

TS3.7新特性

Optional Chaining

可选链操作符

a?.b()

等价于

if(a) a.b();
//或者
a && a.b();

多层嵌套,比如b也是一个对象,要继续调用c()

a?.b?.()

可选的意思是,它在类型的声明中,通过?来修饰,代表一个类型包含某个可为空的属性。

Nullish Coalescing

双问号操作符: 为一个空值,指定一个默认值.

let a = b ?? 'foo'

与||的区别,就是||对于空值的概念,并不仅仅指null和undefined,类似false,0等一系列逻辑上为false的值都会算作空值,这是有问题的.比如:

const b = 0;
let a = b || 'foo';
// a 为'foo'

??为真正意义上的空值(null和undefined)

高级类型

交叉类型

交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型。它包含了所需的所有类型的特性。例如,Person & Serializable & Loggable同时是Person和Serializable和Loggable。就是说这个类型的对象同时拥有了这三种类型的成员.

我们大多是在混入或其它不适合典型面向对象模型的地方看到交叉类型的使用。

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result){id} = (<any>first)[id];
    }
    
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { };
}

interface Loggable {
    log(): void;
}

class ConsoleLogger implements Loggable {
    log() {
        
    }
}

var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log()

联合类型

联合类型与交叉类型很有关联,但是使用上却完全不同。偶尔会遇到这种情况,一个代码库希望传入number或string类型的参数。例如下面的函数:

function padLeft(value: string, padding: any) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'`)
}
padLeft("Hello world", 4);

padLeft存在一个问题,padding参数的类型指定成了any。这就是说我们既可以传入一个既不是number也不是string类型的参数,但是TypeScript却不报错.

let indentedString = padLeft("Hello world", true);

代替any我们可以使用联合类型作为padding的参数

function padLeft(value: string, padding: string | number) { }

联合类型表示一个值可以是几种类型之一。如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员.

interface Bird {
    flv();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    
}

let pet = getSmallPet();
pet.layEggs();
pet.swim();

类型保护与区分类型

联合类型适合于那些值可以为不同类型的情况.

用户自定义的类型保护

类型保护是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词:

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

在这个例子里,pet is Fish就是类型谓词.谓词为parameterName is Type这种形式,parameter Name必须是来自于当前函数签名里的一个参数.

typeof类型保护

使用联合类型书写padLeft

function isNumber(x: any): x is number {
    return typeof x === "number";
}

function isString(x: any): x is string {
    return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding) {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

然而,必须要定义一个函数来判断类型是否是原始类型。幸运的是,现在我们不必将typeof x === "number"抽象成一个函数,因为TS可以将它识别为一个类型保护.也就是说我们可以直接在代码里检查类型了.

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

这些typeof类型保护只有两种形式能被识别: typeof v === "typename"和typeof v !== "typename",typename必须是"number","string","boolean"或"symbol"。但是TS并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护.

instanceof类型保护

instanceof类型保护是通过构造函数来细化类型的一种方式。

interface Padder {
    getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
    constructor(private numSpace: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }   
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder("  ");
}

let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder;
}

if (padder instanceof StringPadder) {
    padder;
}
类型保护和类型断言

由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除null。

function f(sn: string | null): string {
    if (sn == null) {
        return "default";
    }
    else {
        return sn;
    }
}

使用短路运算符去除null

function f(sn: string | null): string {
    return sn || "default";
}

如果编译器不能够去除null和undefined,可以使用类型断言手动去除。语法是添加!后缀: identifier!从identifier的类型里去除了null和undefined;

类型别名

类型别名会给一个类型起个新名字。但是可以作用于原始值,联合类型,元组以及其他任何你需要手写的类型.

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

起别名不会新建一个类型,它会创建一个新名字来引用那个类型。给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。

同接口一样,类型别名也可以是泛型。我们可以添加类型参数并且在别名声明的右侧传入;

type Container<T> = { value: T };

我们也可以使用类型别名来在属性里引用自己

type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
}

与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型:

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;

然而,类型别名不能出现在声明右侧的任何地方

type Yikes = Array<Yikes>;
接口vs类型别名