TypeScript 简介 ts 是什么 TypeScript 数据类型 TypeScript Assertion (typescript 类型断言,类型推断) Typescript 高级类型

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript 简介
ts 是什么
TypeScript 数据类型
TypeScript Assertion (typescript 类型断言,类型推断)
Typescript 高级类型
typescript 基础知识简介

安装 TypeScript

npm install -g typescript  

编译 TypeScript 文件

tsc app.ts

TypeScript 数据类型

Boolean 类型

let isDone: boolean = false; // tsc => var isDone = false;

Number 类型

let count: number = 10;  // tsc => var count = 10

String 类型

let name: string = 'Semliker'; // tsc => var name = 'Semlinker'  

Array 类型

第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

let list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>:

let list: Array<number> = [1,2,3];

Enum 类型

enum Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
}; 

let dir: Direction = Direction.NORTH;

Any (动态类型)

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; 

=> tsc =>  

var notSure = 4;
notSure = "maybe a string instead";
notSure = false;

Void

某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:

// 声明函数返回值为void
function warnUser(): void { 
    console.log("This is my warning message");
}

=> tsc =>  

function warnUser() {
    console.log("This is my warning message");
}  

需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined 或 null:

let unusable: void = undefined;

Tuple 元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为 string 和 number 类型的元组。

let x: [string, number];

x = ['semlinker', 10]; // 正常赋值

x = [10, 'semlinker']; // 类型不匹配

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)); // OK

// Error, 'number' does not have 'substr' method
console.log(x[1].substr(1)); 

当访问一个越界的元素,会使用联合类型替代:

x[3] = 'world'; // OK, 字符串可以赋值给(string | number) 类型

console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString 方法

x[6] = true; // Error, 布尔不是(string | number) 类型

Null 和 Undefined

TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。 和 void相似,它们的本身的类型用处不是很大:

let u: undefined = undefined;
let n: null = null;

注意,按照JavaScript的语义,TypeScript会把 null和 undefined区别对待。 string | null, string | undefined和 string | undefined | null是不同的类型。

TypeScript Assertion (typescript 类型断言,类型推断)

类型断言的第一种方式:"尖括号"语法

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

类型断言的第二种方式:as 语法

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

Typescript 高级类型

Union Types (联合类型)我们用竖线( |)分隔每个类型 A | B

type Message = string | string[];
let greet = (message: Message) => { 
  // dosomething
};

Type Aliases (类型别名)

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。

type Message = string | string[];
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

泛型的类型别名:

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>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

接口 vs. 类型别名
另一个重要区别是类型别名不能被 extends和 implements(自己也不能 extends和 implements其它类型)

交叉类型 (Intersection Types)

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, 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;
}

类型保护与区分类型(Type Guards and Differentiating Types)

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

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

每当使用一些变量调用 isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

typeof类型保护

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}'.`);
}

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'
}

可选参数和可选属性

使用了 --strictNullChecks,可选参数会被自动地加上 | undefined:

function f(x: number, y?: number) {
    return x + (y || 0);
}
如果编译器不能够去除 null或 undefined,你可以使用类型断言手动去除。 语法是添加 !后缀: identifier!从 identifier的类型里去除了 null和 undefined:

字符串字面量类型

字符串字面量类型允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。

type Easing = "ease-in" | "ease-out" | "ease-in-out";

数字字面量类型

function rollDie(): 1 | 2 | 3 | 4 | 5 | 6 {
    // ...
}

索引类型(Index types)

使用索引类型,编译器就能够检查使用了动态属性名的代码。
下面是如何在TypeScript里使用此函数,通过 索引类型查询和 索引访问操作符:

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]
}

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

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

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

映射类型

Partial 的作用是将传入的属性变成可选项,原理就是使用keyof拿到所有属性名,然后再使用in遍历,T[P]拿到相应的值。

type Partial<T> = { [P in keyof T]?: T[P]; };

案例

interface PullDownRefreshConfig {
  threshold: number;
  stop: number;
}

/**
 * type PullDownRefreshOptions = {
 *   threshold?: number | undefined;
 *   stop?: number | undefined;
 * }
 */ 
type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

Required 的作用是将传入的属性变为必选项,原理是使用-?将可选项的?去掉。与之对应的还有个+?。

type Required<T> = { [P in keyof T]-?: T[P]; };

案例

interface PullDownRefreshConfig {
  threshold: number;
  stop: number;
}

type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

/**
 * type PullDownRefresh = {
 *   threshold: number;
 *   stop: number;
 * }
 */
type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>


Readonly 的作用是将传入的属性变为只读选项

type Readonly<T> = { readonly [P in keyof T]: T[P]; };

Mutable 的作用是将传入属性的readonly移除。

type Mutable<T> = { -readonly [P in keyof T]: T[P]; }

Record 的作用是将K中所有属性的值转化成T类型

type Record<K extends keyof any , T > = { [P in K]: T[P] }

Pick 的作用是从T中取出一系列K的属性

type Pick<T , K extends keyof T > = { [P in K ]: T[P]  }

Exclude 的作用是从T中找出U中没有的元素

type Exclude<T ,U> = T extends U ? never : T

Omit 的作用是忽略对象的某些属性功能

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

ReturnType 我们可以用 infer 声明一个类型变量并且对它进行使用

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;