TypeScript学习笔记

高级类型

2020-06-30  本文已影响0人  shuaiutopia

交叉类型

把多个类型合并成一个大而全的类型, 运算符为 &

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

interface obj2 {
  addr: string,
  fav: string[]
}

type obj = obj1 & obj2
/**
 * {
 *  name: string
 *  age: number
 *  addr: string
 *  fav: string[]
 * }
*/

联合类型

多个类型的"或"关系, 表示一个值可以是几种类型之一, 运算符为 |

type UnionType = string | number

const num: UnionType = 123 // OK
const str: UnionType = 'I am string' // OK

类型保护

why: 联合类型带了一个问题, 即我们只能访问此联合类型的所有类型里共有的成员

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

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

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

这很好理解, 因为不知道用户调用时会用到具体哪个类型,所以规定只能使用共同成员.

你可能会说我们可以用 if 条件判断啊. 是的,可以!

let pet = getSmallPet();

// 每一个成员访问都会报错
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}

正确姿势是使用类型断言:

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
} else {
    (<Bird>pet).fly();
}

既然有了 if 的类型判断, else 里面是不是可以省略了? 这就用到类型保护.

自定义类型保护

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

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

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

// 'swim' 和 'fly' 调用都没有问题了

if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
}

typeof 类型保护

类型保护虽然香, 但要多写一个判断函数太费力了, 况且我们平时用到的联合类型大多是基本数据类型. 幸运的是, 现在我们不必将 typeof x === "number" 抽象成一个函数, 因为TypeScript 可以将它识别为一个类型保护.

function getLength(value: string | number): number {
  if (typeof value === 'string') return value.length
  else return value.toString().length
}

这些 typeof类型保护只有两种形式能被识别:

typename 必须是

但是TypeScript 并不会阻止你与其它字符串比较, 语言不会把那些表达式识别为类型保护.

instanceof 类型保护

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

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: 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("  ");
}

// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder'
}

null 类型保护

由于可以为 null 的类型是通过联合类型实现, 那么你需要使用类型保护来去除 null. 幸运地是这与在JavaScript 里写的代码一致:

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

这里很明显地去除了 null, 你也可以使用短路运算符:

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

如果编译器不能够去除 nullundefined, 你可以使用类型断言手动去除. 语法是添加 ! 后缀: identifier!identifier 的类型里去除了 nullundefined:

// ts报错
function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // error, 'name' is possibly null
  }
  name = name || "Bob";
  return postfix("great");
}
// 修改后
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}

枚举成员类型

当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的.

索引类型

通过 索引类型查询索引访问操作符

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'Jarid',
    age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]

keyof T 索引类型查询操作符

对于任何类型 T, keyof T 的结果为 T 上已知的公共属性名的联合. 例如:

let personProps: keyof Person; // 'name' | 'age'

T[K] 索引访问操作符

这里, 类型语法反映了表达式语法. 这意味着 person['name'] 具有类型 Person['name'] — 在我们的例子里则为 string类型. 然而, 就像索引类型查询一样, 你可以在普通的上下文里使用 T[K], 这正是它的强大所在. 你只要确保类型变量 K extends keyof T 就可以了. 例如下面 getProperty函数的例子:

function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
    return o[name]; // o[name] is of type T[K]
}

getProperty 里的 o: Tname: K, 意味着 o[name]: T[K]. 当你返回 T[K]的结果, 编译器会实例化键的真实类型, 因此 getProperty 的返回值类型会随着你需要的属性改变。

let name: string = getProperty(person, 'name');
let age: number = getProperty(person, 'age');
let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age'

索引类型和字符串索引签名

keyofT[K] 与字符串索引签名进行交互. 如果你有一个带有字符串索引签名的类型, 那么 keyof T 会是 string . 并且 T[string] 为索引签名的类型:

interface Map<T> {
    [key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number

类型映射

见之前总结的 "内置类型", 是同一个概念

上一篇下一篇

猜你喜欢

热点阅读