TypeScript 彻底手册

The Definitive TypeScript Handbook

Stack Overflow 在 90,000 名开发者中开展了一项调查,结果显示 TypeScript 是人们最想学习的工具之一。html

在过去几年中,TypeScript 的热门程度、社区规模和使用率都在不断提升。现在,甚至 Facebook 正将 Jest 项目转移至 TypeScriptreact

什么是 TypeScript?

TypeScript 是 JavaScript 的超集,具备静态类型特性,旨在简化大型 JavaScript 应用程序的开发,也被称为 JavaScript that scales可拓展的 JavaScript)。git

为何要用 TypeScript?

JavaScript 在过去几年中快速发展,成为客户端和服务器端最通用的跨平台语言。github

但 JavaScript 本意并不用于大型应用开发。它是一种没有类型系统的动态语言,也就是说,变量的值能够是任何类型(例如字符串或布尔值)。typescript

而类型系统可以提升代码质量和可读性,使代码库更易于维护或重构。更重要的是它能够在编译时就捕获错误,而不是在运行时才捕获。数组

而 JavaScript 并无类型系统,因此一个大型开发团队难以使用 JavaScript 构建复杂的应用程序。安全

而 TypeScript 能在编译时检查不一样部分代码的正确性。在编译时检查出错误,便于开发者发现错误的位置和具体问题。若是运行时才检查出错误,则须要跟踪复杂的堆栈,花费大量时间进行调试。bash

TypeScript 的优势

  1. 在开发周期中能更早地捕获潜在的错误。
  2. 管理大型代码库。
  3. 更易于重构。
  4. 更易于团队合做:代码的耦合性越强,不一样开发人员访问代码库时越不容易形成无心破坏。
  5. 文档特性:类型自己就是一种文档信息,方便往后开发者本人或者其余开发者查询。

TypeScript 的缺点

  1. 须要额外的学习:须要在短时间放缓进度与长期提升效率间进行权衡。
  2. 类型错误可能多种多样。
  3. 配置极大地影响运行。

类型

Boolean (布尔值)

const isLoading: boolean = false;

复制代码

Number (数字)

const decimal: number = 8;
const binary: number = 0b110;

复制代码

String (字符串)

const fruit: string = "orange";

复制代码

Array (数组)

数组能够写成下面两种形式:服务器

// 最多见的方式
let firstFivePrimes: number[] = [2, 3, 5, 7, 11];
// 不太常见的方式:使用泛型 (稍后介绍)
let firstFivePrimes2: Array<number> = [2, 3, 5, 7, 11];

复制代码

Tuple (元组)

Tuple 类型表示一种组织好的数组,元素的类型预先知道,而且数量固定。这意味着你有可能获得错误提示:dom

let contact: [string, number] = ['John', 954683];
contact = ['Ana', 842903, 'extra argument']  /* Error! 
Type '[string, number, string]' is not assignable to type '[string, number]'. */

复制代码

Any (任意值)

any 与类型系统中的任何类型都兼容。意味着能够将任何内容赋值给它,也能够将它赋值给任何类型。它能让你避开类型检查。

let variable: any = 'a string';
variable = 5;
variable = false;
variable.someRandomMethod(); /* 行吧,
也许运行的时候 someRandomMethod 是存在的 */

复制代码

Void (空值)

void 表示没有任何类型。它一般用做没有返回值的函数的返回类型。

function sayMyName(name: string): void {
  console.log(name);
}
sayMyName('Heisenberg');

复制代码

Never

never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些老是会抛出异常、或者根本就不会有返回值的函数的返回值类型。

// 抛出异常
function error(message: string): never {
  throw new Error(message);
}


复制代码

Null 和 Undefined

undefinednull 二者各自有本身的类型分别叫作 undefinednull。和 void 类似,它们的自己的类型用处不是很大,可是在联合类型中很是有用 (稍后介绍)

type someProp = string | null | undefined;

复制代码

Unknown

TypeScript 3.0 引入了 unknown (未知) 类型,它是与 any 类型对应的安全类型。任何东西均可以赋值给 unknown,但 unknown 不能赋值给除了它自己和 any 之外的任何东西。在没有先断言或指定到更具体类型的状况下,不容许对 unknown 进行任何操做。

type I1 = unknown & null;    // null
type I2 = unknown & string;  // string
type U1 = unknown | null;    // unknown
type U2 = unknown | string;  // unknown

复制代码

类型别名

类型别名能够为现有类型提供替代名称,以便某些地方使用。构造它的语法以下:

type Login = string;

复制代码

联合类型

TypeScript 容许让一个属性具备多种数据类型,名为 union (联合) 类型。

type Password = string | number;

复制代码

交叉类型

交叉类型是将多种类型叠加到一块儿成为一种类型。

interface Person {
  name: string;
  age: number;
}
interface Worker {
  companyId: string;
}
type Employee = Person & Worker;

复制代码

Interface (接口)

接口好似你和编译器定义契约,由你指定一个类型,预期它的属性应该是些什么类型。

边注:接口不受 JavaScript 运行时的特性影响,它只在类型检查中会用到。

  • 能够声明可选属性(带有 ? 标记),意味着接口的对象可能会、也可能不会定义这些属性。
  • 能够声明只读属性,意味着一旦为属性赋值,就没法更改。
interface ICircle {
  readonly id: string;
  center: {
    x: number;
    y: number;
  },
  radius: number;
  color?: string;  // 可选属性
}
const circle1: ICircle = {
  id: '001',
  center: { x: 0 },
  radius: 8,
};  /* Error! Property 'y' is missing in type '{ x: number; }' 
but required in type '{ x: number; y: number; }'. */

复制代码

扩展接口

接口能够扩展成另外一个接口,或者更多接口。这使得接口的编写更具备灵活性和复用性。

interface ICircleWithArea extends ICircle {
  getArea: () => number;
}

复制代码

实现接口

实现接口的类须要严格遵循接口的结构。

interface IClock {
  currentTime: Date;
  setTime(d: Date): void;
}

复制代码

枚举

enum (枚举) 用来组织一组的相关值,这些值能够是数值,也能够是字符串值。

enum CardSuit {
  Clubs,
  Diamonds,
  Hearts,
  Spades
}
let card = CardSuit.Clubs;

复制代码

默认状况下,枚举的本质是数字。enum 的取值从 0 开始,以 1 递增。

上一个例子所生成的 JavaScript 代码以下:

var CardSuit;
(function (CardSuit) {
  CardSuit[CardSuit["Clubs"] = 0] = "Clubs";
  CardSuit[CardSuit["Diamonds"] = 1] = "Diamonds";
  CardSuit[CardSuit["Hearts"] = 2] = "Hearts";
  CardSuit[CardSuit["Spades"] = 3] = "Spades";
})(CardSuit || (CardSuit = {}));

复制代码

或者,枚举能够用字符串值来初始化,这种方法更易读。

enum SocialMedia {
  Facebook = 'FACEBOOK',
  Twitter = 'TWITTER',
  Instagram = 'INSTAGRAM',
  LinkedIn = 'LINKEDIN'
}

复制代码

反向映射

enum 支持反向映射,也就是说,能够经过值来得到成员、成员名。

回顾以前 CardSuit 的例子:

const clubsAsNumber: number = CardSuit.Clubs; // 3
const clubsAsString: string = CardSuit[0];    // 'Clubs'

复制代码

函数

你能够为每一个参数指定一个类型,再为函数指定一个返回类型。

function add(x: number, y: number): number {
  return x + y;
}

复制代码

函数重载

TypeScript 容许声明 函数重载。简单来讲,可使用多个名称相同但参数类型和返回类型不一样的函数。参考下面的例子:

function padding(a: number, b?: number, c?: number, d?: any) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  }
  else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}

复制代码

参数的含义根据传递给函数的参数数量而变化。此外,该函数只接受一个、两个或四个参数。要构造函数重载,只需屡次声明函数头就能够了。最后一个函数头真正实现了函数体,但函数外部并不能直接调用最后一个函数头。

function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  }
  else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}

复制代码

你能够指定属性的类型和方法参数的类型。

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet(name: string) {
    return Hi <span class="hljs-subst" style="color: rgb(51, 51, 51); font-weight: normal;">${name}</span>, <span class="hljs-subst" style="color: rgb(51, 51, 51); font-weight: normal;">${<span class="hljs-keyword" style="color: rgb(51, 51, 51); font-weight: bold;">this</span>.greeting}</span>;
  }
}

复制代码

访问修饰符

Typescript 支持 public (公有), private (私有), protected (保护) 修饰符,它们决定了类成员的可访问性。

  • public (公有) 成员和纯 JavaScript 的成员同样,是默认的修饰符。
  • private (私有) 成员对外界来讲不可访问。
  • protected(保护) 成员和私有成员的区别在于,它可以被继承类访问。
| 具备访问权限     | public | protected | private |
| :------------- | :----: | :-------: | :-----: |
| 类自己          |   yes  |    yes    |   yes   |
| 派生类          |   yes  |    yes    |    no   |
| 类实例          |   yes  |     no    |    no   |

复制代码

只读修饰符

readonly (只读) 变量必须在它声明或构造时初始化。

class Spider {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor (theName: string) {
    this.name = theName;
  }
}

复制代码

参数属性

参数属性 能够放在一个地方建立并初始化成员。它经过给构造函数参数添加一个访问限定符来声明。

class Spider {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {
  }
}

复制代码

抽象

abstract (抽象) 这个关键字能够用在抽象类上,也能够用在抽象类方法上。

  • 抽象类 不会直接被实例化。抽象类主要用于继承,继承抽象类必须实现它全部的抽象方法。
  • 抽象成员 不包含具体实现,所以不能被直接访问。这些成员必须在派生类中实现。 (相似接口)

类型断言

TypeScript 容许你以任何方式覆盖其推断的类型。当你比编译器自己能更好地理解变量类型时,可使用它。

const friend = {};
friend.name = 'John';  // Error! Property 'name' does not exist on type '{}'
interface Person {
  name: string;
  age: number;
}

复制代码

最初,类型断言的语法是 <type>

let person = <Person> {};

复制代码

但这在 JSX 中使用时产生了歧义。所以建议使用 as 代替。

类型断言一般在从 JavaScript 迁移代码时使用,你对变量的类型了解可能比当前指派的更准确。

但断言也会 被认为有害。

咱们来看看上一个示例中的 Person 接口,你注意到了什么问题吗?若是你注意到丢失了 age 属性,恭喜,你对了!编译器可能会帮助你自动完成 Person 的属性,但若是您遗漏了任何属性,它也不会报错。

类型推论

没有明确指定出类型时,TypeScript 会推断变量类型。

/**

变量声明
/
let a = "some string";
let b = 1;
a = b;  // Error! Type 'number' is not assignable to type 'string'.


复制代码

类型兼容性

类型兼容性是基于结构类型的,结构类型只使用其成员来描述类型。

结构化类型系统的基本规则是:若是 x 要兼容 y,那么 y 至少具备与 x 相同的属性。

interface Person {
name: string;
}

复制代码

因为 y 有一个成员 name: string 匹配 Person 接口所需的属性,这意味着 xy 的子类型。所以这个赋值是合法的。

函数

参数数量
在函数调用中,至少须要传入足够的参数,多余的参数不会致使任何错误。

function consoleName(person: Person) {
  console.log(person.name);
}
consoleName({ name: 'John' });           // 正确
consoleName({ name: 'John', age: 20 });  // 多余的参数也合法

复制代码

返回值类型
返回值类型必须至少包含足够的数据。

let x = () => ({name: 'John'});
let y = () => ({name: 'John', age: 20 });
x = y;  // 正确
y = x;  /* Error! Property 'age' is missing in type '{ name: string; }'
but required in type '{ name: string; age: number; }' */

复制代码

类型保护

类型保护能够在条件块中缩小对象类型的范围。

typeof

在条件里使用 typeof,编译器会知道变量的类型会不一致。在下面的示例中,TypeScript 会知道:在条件块以外,x 多是布尔值,而布尔值上没法调用函数 toFixed

function example(x: number | boolean) {
  if (typeof x === 'number') {
    return x.toFixed(2);
  }
  return x.toFixed(2); // Error! Property 'toFixed' does not exist on type 'boolean'.
}

复制代码

instanceof

class MyResponse {
  header = 'header example';
  result = 'result example';
  // ...
}
class MyError {
  header = 'header example';
  message = 'message example';
  // ...
}
function example(x: MyResponse | MyError) {
  if (x instanceof MyResponse) {
    console.log(x.message); // Error! Property 'message' does not exist on type 'MyResponse'.
    console.log(x.result);  // 正确
  } else {
    // TypeScript 知道这里必定是 MyError
    console.log(x.message); // 正确
    console.log(x.result);  // Error! Property 'result' does not exist on type 'MyError'.
  }
}

复制代码

in

in 运算符会检查一个属性在某对象上是否存在。

interface Person {
  name: string;
  age: number;
}
const person: Person = {
  name: 'John',
  age: 28,
};

复制代码

Literal Types (字面量类型)

字面量正是 JavaScript 原始数据类型具体的值,它们能够与 union (联合) 类型搭配使用,构造一些实用的概念。

type Orientation = 'landscape' | 'portrait';
function changeOrientation(x: Orientation) {
  // ...
}
changeOrientation('portrait'); // 正确
changeOrientation('vertical'); /* Error! Argument of type '"vertical"' is not 
assignable to parameter of type 'Orientation'. /

复制代码

条件类型

条件类型表示类型关系的测试,并根据测试的结果选择两种可能类型中的一种。

type X = A extends B ? C : D;

复制代码

若是 A 类型能够赋值给 B 类型,那么 XC 类型;不然 XD 类型。

泛型

泛型是必须包含或引用其余类型才能完成的类型。它增强了变量之间有意义的约束。

下面例子中的函数会返回所传入的任何类型的数组。

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}
reverse([1, 2, 3]); // number[]
reverse([0, true]); // (number | boolean)[]

复制代码

keyof

keyof 运算符会查询给定类型的键集。

interface Person {
  name: string;
  age: number;
}
type PersonKeys = keyof Person; // 'name' | 'age'

复制代码

映射类型

映射类型,经过在属性类型上创建映射,从现有的类型建立新类型。具备已知类型的每一个属性都会根据你指定的规则进行转换。

Partial

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

复制代码
  • 泛型 Partial 类型被定义时只有一个类型参数 T
  • keyof T 表示全部 T 类型属性的名字(字符串字面类型)的联合。
  • [P in keyof T]?: T[P] 表示全部 T 类型的属性 P 的类型都应该是可选的,而且都会被转换为 T[P]
  • T[P] 表示 T 类型的属性 P 的类型。

Readonly (只读)

正如在接口部分中所介绍的,TypeScript 中能够建立只读属性。 Readonly 类型接受一个类型 T,并将其全部属性设置为只读。

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

复制代码

Exclude

Exclude 能够从其余类型中排除某些类型。排除的是能够赋值给 T 的属性。

/**

type Exclude<T, U> = T extends U ? never : T;
/
type User = {
_id: number;
name: string;
email: string;
created: number;
};


复制代码

Pick

Pick 能够从其余类型中选取某些类型。 挑选的是能够赋值给 T 的属性。

/**

复制代码

infer

你可使用 infer 关键字来推断条件类型的 extends 子句中的类型变量。这样的推断类型变量只能用于条件类型的 true 分支。

ReturnType

获取函数的返回类型。

/**
原版的 TypeScript's ReturnType type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; / type MyReturnType<T> = T extends (...args: any) => infer R ? R : any; 复制代码

咱们来拆解 MyReturnType

  • T 的返回类型是 …
  • 首先,T 是否是一个函数?
  • 若是是,那么类型解析为推断出的返回类型 R
  • 若是不是,类型解析为 any

参考资料与实用连接

basarat.gitbooks.io/typescript/

www.typescriptlang.org/docs/home.h…

www.tutorialsteacher.com/typescript

github.com/dzharii/awe…

github.com/typescript-…


为了达到学习和实践 TypeScript 的目的,我用 TS 和 React-Native(用了 hooks)构建了一个简单的 CurrencyConverter (汇率转换) 程序。你能够在 这里 查看这个项目。

感谢、祝贺你阅读到这里!若是你对此有任何想法,请随时发表评论。

相关文章
相关标签/搜索